-
Notifications
You must be signed in to change notification settings - Fork 12
/
ghc-prof-flamegraph.hs
150 lines (138 loc) · 6.2 KB
/
ghc-prof-flamegraph.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
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 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
, 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
| Ticks -- ^ Report ticks, number
| Bytes -- ^ Report bytes allocated, number
deriving (Eq, Show)
optionsParser :: Opts.Parser Options
optionsParser = Options
<$> (Opts.flag' Alloc (Opts.long "alloc" <> Opts.help "Uses the allocation measurements instead of time measurements")
<|> Opts.flag' Entries (Opts.long "entry" <> Opts.help "Uses entries the measurements instead of time measurements")
<|> 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
checkNames Entries _ = Nothing
checkNames Time _ = Nothing
checkNames Ticks n
| "ticks" `elem` n = Nothing
| otherwise = Just "No ticks information, please run program with +RTS -P"
checkNames Bytes n
| "bytes" `elem` n = Nothing
| otherwise = Just "No ticks information, please run program with +RTS -P"
normalize :: ReportType -> Double -> Int
normalize Alloc = round . (10 *)
normalize Time = round . (10 *)
normalize _ = round
addUnknown :: ReportType -> (Int, [String]) -> [String]
addUnknown Time = \(entries, frames) ->
let unknown = 1000 - entries
in if unknown > 0
then ("UNKNOWN " ++ show unknown) : frames
else frames
addUnknown Alloc = \(entries, frames) ->
let unknown = 1000 - entries
in if unknown > 0
then ("UNKNOWN " ++ show unknown) : frames
else frames
addUnknown _ = snd
generateFrames :: Options -> [Prof.Line] -> [String]
generateFrames options lines0 = addUnknown (optionsReportType options) $ go [] lines0
where
go :: [String] -> [Prof.Line] -> (Int, [String])
go _stack [] =
(0, [])
go stack (line : lines') =
let entries = normalize (optionsReportType options) (individualMeasure line)
symbol = Prof.lModule line ++ "." ++ Prof.lCostCentre line
frame = intercalate ";" (reverse (symbol : stack)) ++ " " ++ show entries
(childrenEntries, childrenFrames) = go (symbol : stack) (Prof.lChildren line)
(restEntries, restFrames) = go stack lines'
in (entries + childrenEntries + restEntries, frame : childrenFrames ++ restFrames)
individualMeasure = case optionsReportType options of
Alloc -> Prof.lIndividualAlloc
Time -> Prof.lIndividualTime
Entries -> fromIntegral . Prof.lEntries
Ticks -> fromIntegral . Prof.lTicks
Bytes -> fromIntegral . Prof.lBytes
main :: IO ()
main = do
options <- Opts.execParser $
Opts.info (Opts.helper <*> optionsParser) Opts.fullDesc
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 -> 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"