From 2a8b36ecfc28ca686441161c82157869e9b60bfc Mon Sep 17 00:00:00 2001 From: Sergey Vinokurov Date: Wed, 19 Jul 2017 22:04:57 +0300 Subject: [PATCH 1/2] Automatically invoke flamegraph.pl to get pretty graph Also bundle flamegraph.pl scripts together with ghc-prof-flamegraph executable and add new command-line flags to control behavior of flamegraph.pl and redirect output. --- .gitmodules | 3 ++ FlameGraph | 1 + ghc-prof-flamegraph.cabal | 13 ++++--- ghc-prof-flamegraph.hs | 76 +++++++++++++++++++++++++++++++++------ 4 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 .gitmodules create mode 160000 FlameGraph diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..da14e0e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "FlameGraph"] + path = FlameGraph + url = https://github.com/brendangregg/FlameGraph.git diff --git a/FlameGraph b/FlameGraph new file mode 160000 index 0000000..a93d905 --- /dev/null +++ b/FlameGraph @@ -0,0 +1 @@ +Subproject commit a93d905911c07c96a73b35ddbcb5ddb2f39da4b6 diff --git a/ghc-prof-flamegraph.cabal b/ghc-prof-flamegraph.cabal index 479aa9d..bd092f0 100644 --- a/ghc-prof-flamegraph.cabal +++ b/ghc-prof-flamegraph.cabal @@ -29,20 +29,19 @@ description: bytes allocated using @--bytes@. In order to use @--bytes@ or @--ticks@ flag one have to run program with @+RTS -P -RTS@ in order to get those measurements. +data-files: + FlameGraph/flamegraph.pl + source-repository head type: git location: https://github.com/fpco/ghc-prof-flamegraph -library - build-depends: base >=4 && <5 - exposed-modules: ProfFile - ghc-options: -Wall - default-language: Haskell2010 - executable ghc-prof-flamegraph main-is: ghc-prof-flamegraph.hs build-depends: base >=4.6 && <5 - , ghc-prof-flamegraph + , filepath , optparse-applicative + , process + other-modules: ProfFile default-language: Haskell2010 ghc-options: -Wall diff --git a/ghc-prof-flamegraph.hs b/ghc-prof-flamegraph.hs index 6f00f17..2f9bef8 100644 --- a/ghc-prof-flamegraph.hs +++ b/ghc-prof-flamegraph.hs @@ -1,20 +1,29 @@ +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -module Main where -import Control.Applicative ((<*>), (<|>)) +module Main (main) where + +import Control.Applicative ((<*>), (<|>), optional, many, pure) +import Data.Foldable (traverse_) import Data.Functor ((<$>)) import Data.List (intercalate) import Data.Monoid ((<>)) import qualified Options.Applicative as Opts -import System.Exit (exitFailure) -import System.IO (stderr, hPutStrLn) import qualified ProfFile as Prof +import System.Exit (ExitCode(..), exitFailure) +import System.FilePath ((), replaceExtension) +import System.IO (stderr, stdout, hPutStrLn, hPutStr, hGetContents, IOMode(..), hClose, openFile) +import System.Process (proc, createProcess, CreateProcess(..), StdStream(..), waitForProcess) + +import Paths_ghc_prof_flamegraph (getDataDir) data Options = Options - { optionsReportType :: ReportType + { optionsReportType :: ReportType + , optionsProfFile :: Maybe FilePath + , optionsOutputFile :: Maybe FilePath + , optionsFlamegraphFlags :: [String] } deriving (Eq, Show) - data ReportType = Alloc -- ^ Report allocations, percent | Entries -- ^ Report entries, number | Time -- ^ Report time spent in closure, percent @@ -29,6 +38,21 @@ optionsParser = Options <|> Opts.flag' Bytes (Opts.long "bytes" <> Opts.help "Memory measurements in bytes (+RTS -P -RTS)") <|> Opts.flag' Ticks (Opts.long "ticks" <> Opts.help "Time measurements in ticks (+RTS -P -RTS)") <|> Opts.flag Time Time (Opts.long "time" <> Opts.help "Uses time measurements")) + <*> optional + (Opts.strArgument + (Opts.metavar "PROF-FILE" <> + Opts.help "Profiling output to format as flame graph")) + <*> optional + (Opts.strOption + (Opts.short 'o' <> + Opts.long "output" <> + Opts.metavar "SVG-FILE" <> + Opts.help "Optional output file")) + <*> many + (Opts.strOption + (Opts.long "flamegraph-option" <> + Opts.metavar "STR" <> + Opts.help "Options to pass to flamegraph.pl")) checkNames :: ReportType -> [String] -> Maybe String checkNames Alloc _ = Nothing @@ -84,11 +108,43 @@ main :: IO () main = do options <- Opts.execParser $ Opts.info (Opts.helper <*> optionsParser) Opts.fullDesc - s <- getContents + s <- maybe getContents readFile $ optionsProfFile options case Prof.parse s of Left err -> error err Right (names, ls) -> case checkNames (optionsReportType options) names of - Just problem -> do hPutStrLn stderr problem - exitFailure - Nothing -> putStr $ unlines $ generateFrames options ls + Just problem -> do + hPutStrLn stderr problem + exitFailure + Nothing -> do + dataDir <- getDataDir + let flamegraphPath = dataDir "FlameGraph" "flamegraph.pl" + flamegraphProc = (proc "perl" (flamegraphPath : optionsFlamegraphFlags options)) + { std_in = CreatePipe + , std_out = CreatePipe + , std_err = Inherit + } + (outputHandle, outputFileName, closeOutputHandle) <- + case (optionsOutputFile options, optionsProfFile options) of + (Just path, _) -> do + h <- openFile path WriteMode + pure (h, Just path, hClose h) + (Nothing, Just path) -> do + let path' = path `replaceExtension` "svg" + h <- openFile path' WriteMode + pure (h, Just path', hClose h) + _ -> + pure (stdout, Nothing, pure ()) + (Just input, Just flamegraphResult, Nothing, procHandle) <- createProcess flamegraphProc + traverse_ (hPutStrLn input) $ generateFrames options ls + hClose input + hGetContents flamegraphResult >>= hPutStr outputHandle + exitCode <- waitForProcess procHandle + closeOutputHandle + case exitCode of + ExitSuccess -> + case outputFileName of + Nothing -> pure () + Just path -> putStrLn $ "Output written to " <> path + ExitFailure{} -> + hPutStrLn stderr $ "Call to flamegraph.pl at " <> flamegraphPath <> " failed" From 2af4819aa21e3daec832dd691aa2c3b0219bc4e0 Mon Sep 17 00:00:00 2001 From: Sergey Vinokurov Date: Tue, 18 Jul 2017 10:18:24 +0300 Subject: [PATCH 2/2] Reflect changes in README --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 24c5b88..c4023b5 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,23 @@ understandable by the ## Usage -First convert a `.prof` file into the flame graph format using -`ghc-prof-flamegraph`: +First convert a `.prof` file into the flame graph svg: - $ cat ~/src/packdeps/packdeps.prof | ghc-prof-flamegraph > packdeps.prof.folded + $ cat ~/src/packdeps/packdeps.prof | ghc-prof-flamegraph > packdeps.prof.svg -Then you can use the file to produce an svg image, using the -[`flamegraph.pl`](https://github.com/brendangregg/FlameGraph) script: +Or, alternatively, just pass the `.prof` file as an argument. The tool will +then create corresponing `.svg` file: - $ cat packdeps.prof.folded | ~/src/FlameGraph/flamegraph.pl > packdeps.prof.svg + $ ghc-prof-flamegraph ~/src/packdeps/packdeps.prof + Output written to ~/src/packdeps/packdeps.svg + +The previous command will produce `~/src/packdeps/packdeps.svg` file. + +You can customize the behavior of the underlying `flamegraph.pl` by passing +options via `–framegraph-option`. For example, you can customize the title: + + $ ghc-prof-flamegraph ~/src/packdeps/packdeps.prof '--flamegraph-option=--title=Package dependencies' + Output written to ~/src/packdeps/packdeps.svg You can also generate a flamegraph using the allocation measurements, using the `--alloc` flag.