{- stackcollapse-ghc - fold GHC prof files into flamegraph input Copyright (C) 2020 Marcin Rzeźnicki This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module Main (main) where import Config import UserModule import StackCollapse import System.Console.GetOpt import System.Environment (getArgs, getProgName) import System.Exit import System.IO (Handle, hPutStrLn, stderr, hPutChar, stdout) import Data.Functor ((<&>)) import Data.Function ((&)) import Control.Monad (foldM) import Control.Exception import qualified Data.ByteString.Lazy as Lazy cmdLineOpts :: [OptDescr (StackCollapseConfig -> Either String StackCollapseConfig)] cmdLineOpts = [ Option ['P'] [] (NoArg $ \config -> Right config { inputType = Detailed }) "Process detailed prof file ('-P' or '-pa' options) (default)" , Option ['p'] [] (NoArg $ \config -> Right config { inputType = Standard }) "Process standard prof file ('-p' option)" , Option ['u'] ["user-module"] (ReqArg (\input config -> parseUserModule input <&> prependUserModule config) "module_name") "Name of an user module (may be repeated to add more than one). Matches the whole \"hierarchy\", so `MyModule` matches `MyModule` as well as `MyModule.Internal`" , Option ['t'] ["time"] (NoArg $ \config -> Right config { operationMode = Time }) "Collapse with respect to time (default)" , Option ['a'] ["alloc"] (NoArg $ \config -> Right config { operationMode = Alloc }) "Collapse with respect to allocations" , Option ['S'] [] (NoArg $ \config -> Right config { sourceMode = SourceAlways }) "Append source location to every function name" , Option ['s'] [] (NoArg $ \config -> Right config { sourceMode = SourceUser }) "Append source location to functions defined in user modules" , Option [] ["no-source"] (NoArg $ \config -> Right config { sourceMode = SourceNever }) "Do not append source location (default)" , Option ['Q'] [] (NoArg $ \config -> Right config { functionNameMode = QualifiedAlways }) "Always use qualified functon names" , Option [] ["no-qualified"] (NoArg $ \config -> Right config { functionNameMode = QualifiedNever }) "Do not use qualified function names" , Option ['q'] [] (NoArg $ \config -> Right config { functionNameMode = QualifiedKernel }) "Use qualified names for functions not defined in user modules (default)" , Option ['A'] ["annotations", "annot"] (NoArg $ \config -> Right config { annotationMode = WithAnnotations }) "Add annotations to output (j - user modules, k - GHC and System, i - CAFs)" , Option [] ["no-annotations", "no-annot"] (NoArg $ \config -> Right config { annotationMode = NoAnnotations }) "Do not add annotations to output"] main :: IO () main = getArgs >>= run run :: [String] -> IO () run [] = printUsage stdout run ["-h"] = printUsage stdout run ["--help"] = printUsage stdout run args = case getOpt RequireOrder cmdLineOpts args of (opts, [path], []) -> do let configOrError = foldM (&) defaultConfig opts either invalidArgumentError ((`catch` runtimeError) . runStackCollapse path) configOrError (_, _, errors) -> invalidUsageError errors runStackCollapse :: FilePath -> StackCollapseConfig -> IO () runStackCollapse inpath config = stackCollapseFromPath config inpath >>= either (runtimeError . userError) Lazy.putStrLn . collapseStack printUsage :: Handle -> IO () printUsage h = do exe <- getProgName hPutChar h '\n' hPutStrLn h $ usageInfo ("Usage: " ++ exe ++ " [OPTIONS] FILE\nOPTIONS:") cmdLineOpts hPutChar h '\n' invalidUsageError :: [String] -> IO a invalidUsageError errorMessages = do let errorMessage = concat errorMessages putErrLn errorMessage printUsage stderr exitFailure invalidArgumentError :: String -> IO a invalidArgumentError errorMessage = do putErrLn $ "Invalid argument: " ++ errorMessage exitFailure runtimeError :: IOError -> IO a runtimeError ioerror = do putErrLn $ displayException ioerror exitWith $ ExitFailure 2 putErrLn :: String -> IO () putErrLn = hPutStrLn stderr