packages feed

eventlog2html 0.3.0 → 0.4.0

raw patch · 10 files changed

+158/−103 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Eventlog.Args: [tracePercent] :: Args -> Double
- Eventlog.Prune: cmpName :: (Compare (Text, (Double, Double)), Compare (Text, (Double, Double)))
- Eventlog.Prune: cmpSize :: (Compare (Text, (Double, Double)), Compare (Text, (Double, Double)))
- Eventlog.Prune: cmpStdDev :: (Compare (Text, (Double, Double)), Compare (Text, (Double, Double)))
- Eventlog.Prune: type Compare a = a -> a -> Ordering
+ Eventlog.Args: [excludeStr] :: Args -> [Text]
+ Eventlog.Args: [includeStr] :: Args -> [Text]
+ Eventlog.Args: [traceEvents] :: Args -> Bool
+ Eventlog.Data: generateJsonValidate :: (ProfData -> IO ()) -> FilePath -> Args -> IO (Header, Value)
+ Eventlog.Events: instance GHC.Show.Show Eventlog.Events.FrameEL
+ Eventlog.Types: ProfData :: Header -> Map Text (Double, Double) -> [Frame] -> [Trace] -> ProfData
+ Eventlog.Types: data ProfData
- Eventlog.Args: Args :: Sort -> Bool -> Double -> Int -> Bool -> Bool -> Bool -> Bool -> Text -> Maybe String -> [String] -> Args
+ Eventlog.Args: Args :: Sort -> Bool -> Int -> Bool -> Bool -> Bool -> Bool -> Bool -> Text -> [Text] -> [Text] -> Maybe String -> [String] -> Args
- Eventlog.Events: chunk :: FilePath -> IO (Header, Map Text (Double, Double), [Frame], [Trace])
+ Eventlog.Events: chunk :: Args -> FilePath -> IO ProfData
- Eventlog.HeapProf: chunk :: FilePath -> IO (Header, Map Text (Double, Double), [Frame], [Trace])
+ Eventlog.HeapProf: chunk :: FilePath -> IO ProfData
- Eventlog.Prune: prune :: Compare (Text, (Double, Double)) -> Double -> Int -> Map Text (Double, Double) -> Map Text Int
+ Eventlog.Prune: prune :: Args -> Map Text (Double, Double) -> Map Text Int

Files

CHANGELOG view
@@ -1,3 +1,14 @@+0.4, released 2019-09-18++*  BREAKING CHANGE: eventlog2html no longer includes traces which have been generated by "traceEvent" or "traceEventIO" from "Debug.Trace" by default.+  "traceEvent" and "traceEventIO" are supposed to be used for high-frequency events.+  If you want to trace low-frequency events, especially in order to relate phases of your program to memory usage patterns,  use "traceMarker" and "traceMarkerIO" instead.+  If you want to return to the old behaviour, add the "--include-trace-events" option on the commandline.+* Removed "trace PERCENT" option, which had no effect in the code.+* Added warning about eventlogs with a lot of traces.+* Added option to filter the traces which should be included in the+  generated output.+ 0.3, released 2019-09-08  * Added warnings if eventlog2html is used on eventlogs generated by GHC Version without profiling support.
eventlog2html.cabal view
@@ -1,5 +1,5 @@ Name:                eventlog2html-Version:             0.3.0+Version:             0.4.0 Synopsis:            Visualise an eventlog Description:         eventlog2html is a library for visualising eventlogs.                      At the moment, the intended use is to visualise eventlogs@@ -31,7 +31,7 @@ cabal-version: 1.18 extra-doc-files: README.md                  CHANGELOG-Tested-With:         GHC ==8.6.4+Tested-With:         GHC ==8.6.5  Library   Build-depends:
main/Main.hs view
@@ -6,17 +6,15 @@ import Data.Aeson (encodeFile, Value, toJSON) import System.FilePath import System.Exit+import System.IO -import Eventlog.Args (args, Args(..), Sort(..))+import Eventlog.Args (args, Args(..)) import Eventlog.Bands (bands)-import qualified Eventlog.Events as E-import qualified Eventlog.HeapProf as H import Eventlog.HtmlTemplate-import Eventlog.Prune (prune, cmpName, cmpSize, cmpStdDev)-import Eventlog.Total (total) import Eventlog.Data import Eventlog.Vega import Eventlog.VegaTemplate+import Eventlog.Types  main :: IO () main = do@@ -33,7 +31,7 @@      | otherwise -> doOneHtml a fin fout argsToOutput _ =   die "When the -o option is specified, exactly one eventlog file has to be passed."-  + doOneJson :: Args -> FilePath -> FilePath -> IO () doOneJson a fin fout = do   (_, val) <- generateJson fin a@@ -41,7 +39,14 @@  doOneHtml :: Args -> FilePath -> FilePath -> IO () doOneHtml a fin fout = do-  (header, data_json) <- generateJson fin a+  (header, data_json) <- generateJsonValidate checkTraces fin a   let html = templateString header data_json a   writeFile fout html+  where+    checkTraces :: ProfData -> IO ()+    checkTraces (ProfData _ _ _ ts) =+      if length ts > 1000+        then hPutStrLn stderr+              "More than 1000 traces, consider reducing using -i or -x"+        else return () 
src/Eventlog/Args.hs view
@@ -18,13 +18,16 @@   {     sorting      :: Sort   , reversing    :: Bool-  , tracePercent :: Double   , nBands       :: Int   , heapProfile  :: Bool   , noIncludejs    :: Bool   , json         :: Bool   , noTraces     :: Bool+  , traceEvents  :: Bool -- ^ By default, only traceMarkers are included.+                         -- This option enables the inclusion of traceEvents.   , userColourScheme :: Text+  , includeStr :: [Text]+  , excludeStr :: [Text]   , outputFile :: Maybe String   , files        :: [String]   }@@ -40,12 +43,6 @@           ( long "reverse"          <> help "Reverse the order of bands." )       <*> option auto-          ( long "trace"-         <> help "Percentage of trace elements to combine."-         <> value 1-         <> showDefault-         <> metavar "PERCENT" )-      <*> option auto           ( long "bands"          <> help "Maximum number of bands to draw (0 for unlimited)."          <> value 15@@ -65,10 +62,30 @@       <*> switch           ( long "no-traces"           <> help "Don't display traces on chart")+      <*> switch+          ( long "include-trace-events"+          <> help ("Enables the inclusion of traces emitted using `traceEvent`"+                   ++ ", which should only be used for high-frequency events. "+                   ++ "For low frequency events, use `traceMarker` instead.")+          <> showDefault)       <*> option str           ( long "colour-scheme"           <> value "category20b"           <> help "The name of the colour scheme. See the vega documentation (https://vega.github.io/vega/docs/schemes/#reference) for a complete list. Examples include \"category10\" \"dark2\" \"tableau10\". ")+      <*> many (option str+          (short 'i'+          <> long "include"+          <> help ("Specify the traces which should be included in the output. Only traces which contain SUBSTRING "+                    ++ "in their name will be included. Multiple different traces can be included "+                    ++ "with \"-i foo -i bar\".")+          <> metavar "SUBSTRING"))+      <*> many (option str+          (short 'x'+          <> long "exclude"+          <> help ("Specify the traces which should be excluded in the output. All traces which contain SUBSTRING "+                    ++ "in their name will be excluded. Multiple different traces can be excluded "+                    ++ "with \"-x foo -x bar\".")+          <> metavar "SUBSTRING"))       <*> (optional $ option str           (short 'o'           <> help "Write the output to the given filename."
src/Eventlog/Data.hs view
@@ -1,37 +1,29 @@ {-# LANGUAGE OverloadedStrings #-}-module Eventlog.Data (generateJson) where+module Eventlog.Data (generateJson, generateJsonValidate ) where  import Prelude hiding (print, readFile) import Data.Aeson (Value(..), (.=), object)-import Data.Tuple (swap) -import Eventlog.Args (Args(..), Sort(..))+import Eventlog.Args (Args(..)) import Eventlog.Bands (bands) import qualified Eventlog.Events as E import qualified Eventlog.HeapProf as H-import Eventlog.Prune (prune, cmpName, cmpSize, cmpStdDev)+import Eventlog.Prune (prune) import Eventlog.Vega-import Eventlog.Types (Header)+import Eventlog.Types (Header, ProfData(..)) -generateJson :: FilePath -> Args -> IO (Header, Value)-generateJson file a = do-  let chunk = if heapProfile a then H.chunk else E.chunk-      cmp = fst $ reversing' sorting'-      sorting' = case sorting a of-        Name -> cmpName-        Size -> cmpSize-        StdDev -> cmpStdDev-      reversing' = if reversing a then swap else id-  (h,totals, fs, traces) <- chunk file-  let keeps = prune cmp 0 (bound $ nBands a) totals-  let combinedJson = object [+generateJsonValidate :: (ProfData -> IO ()) -> FilePath -> Args -> IO (Header, Value)+generateJsonValidate validate file a = do+  let chunk = if heapProfile a then H.chunk else E.chunk a+  dat@(ProfData h totals fs traces) <- chunk file+  validate dat+  let keeps = prune a totals+      combinedJson = object [           "samples" .= bandsToVega keeps (bands h keeps fs)         , "traces"  .= tracesToVega traces         ]   return (h, combinedJson) -bound :: Int -> Int-bound n-  | n <= 0 = maxBound-  | otherwise = n+generateJson :: FilePath -> Args -> IO (Header, Value)+generateJson = generateJsonValidate (const (return ())) 
src/Eventlog/Events.hs view
@@ -11,10 +11,10 @@ import Prelude hiding (init, lookup) import qualified Data.Text as T import Data.Text (Text)-import Data.Map (Map)  import Eventlog.Types import Eventlog.Total+import Eventlog.Args (Args(..)) import Data.List import Data.Function import Data.Word@@ -34,40 +34,40 @@ fromNano :: Word64 -> Double fromNano e = fromIntegral e * 1e-9 -chunk :: FilePath -> IO (Header,Map Text (Double, Double), [Frame], [Trace])-chunk f = do+chunk :: Args -> FilePath -> IO ProfData+chunk a f = do   (EventLog _ e) <- either error id <$> readEventLogFromFile f-  (ph, frames, traces) <- eventsToHP e+  (ph, frames, traces) <- eventsToHP a e   let (counts, totals) = total frames-  return $ (ph counts, totals, frames, traces)+  return $ (ProfData (ph counts) totals frames traces)  checkGHCVersion :: EL -> Maybe String checkGHCVersion EL { ident = Just (version,_)}   | version <= makeVersion [8,4,4]  =-      Just $ "Warning: The eventlog has been generated with GHC version "-           ++ show version+      Just $ "Warning: The eventlog has been generated with ghc-"+           ++ showVersion version            ++ ", which does not support profiling events in the eventlog." checkGHCVersion EL { pargs = Just args, ident = Just (version,_)}   | version > makeVersion [8,4,4] &&     version <= makeVersion [8,9,0] &&     ("-hr" `elem` args || "-hb" `elem` args) =-     Just $ "Warning: The eventlog has been generated with GHC version"-            ++ show version+     Just $ "Warning: The eventlog has been generated with ghc-"+            ++ showVersion version             ++ ", which does not support biographical or retainer profiling." checkGHCVersion _ = Nothing  -eventsToHP :: Data -> IO (PartialHeader, [Frame], [Trace])-eventsToHP (Data es) = do+eventsToHP :: Args -> Data -> IO (PartialHeader, [Frame], [Trace])+eventsToHP a (Data es) = do   let-      el@EL{..} = foldEvents es+      el@EL{..} = foldEvents a es       fir = Frame (fromNano start) []       las = Frame (fromNano end) []   mapM_ (hPutStrLn stderr) (checkGHCVersion el)   return $ (elHeader el, fir : reverse (las: normalise frames) , traces) -normalise :: [(Word64, [Sample])] -> [Frame]-normalise fs = map (\(t, ss) -> Frame (fromNano t) ss) fs+normalise :: [FrameEL] -> [Frame]+normalise fs = map (\(FrameEL t ss) -> Frame (fromNano t) ss) fs   data EL = EL@@ -75,12 +75,14 @@   , ident :: Maybe (Version, String)   , ccMap :: !(Map.Map Word32 CostCentre)   , clocktimeSec :: !Word64-  , samples :: !(Maybe (Word64, [Sample]))-  , frames :: ![(Word64, [Sample])]+  , samples :: !(Maybe FrameEL)+  , frames :: ![FrameEL]   , traces :: ![Trace]   , start :: !Word64   , end :: !Word64 } deriving Show +data FrameEL = FrameEL Word64 [Sample] deriving Show+ data CostCentre = CC { cid :: Word32                      , label :: Text                      , modul :: Text@@ -99,27 +101,33 @@   , ccMap = Map.empty   } -foldEvents :: [Event] -> EL-foldEvents (e:es) =-  let res = foldl' folder  (initEL (evTime e)) (e:es)+foldEvents :: Args -> [Event] -> EL+foldEvents a (e:es) =+  let res = foldl' (folder a)  (initEL (evTime e)) (e:es)   in addFrame 0 res-foldEvents [] = error "Empty event log"+foldEvents _ [] = error "Empty event log" -folder :: EL -> Event -> EL-folder el (Event t e _) = el &+folder :: Args -> EL -> Event -> EL+folder a el (Event t e _) = el &   updateLast t .     case e of       -- Traces+      -- Messages and UserMessages correspond to high-frequency "traceEvent" or "traceEventIO" events from Debug.Trace and+      -- are only included if "--include-trace-events" has been specified.+      -- For low-frequency events "traceMarker" or "traceMarkerIO" should be used, which generate "UserMarker" events.+      Message s -> if traceEvents a then addTrace a (Trace (fromNano t) (T.pack s)) else id+      UserMessage s -> if traceEvents a then addTrace a (Trace (fromNano t) (T.pack s)) else id+      UserMarker s -> addTrace a (Trace (fromNano t) (T.pack s))+      -- Information about the program       RtsIdentifier _ ident -> addIdent ident-      Message s -> addTrace (Trace (fromNano t) (T.pack s))-      UserMessage s -> addTrace (Trace (fromNano t) (T.pack s))+      ProgramArgs _ as -> addArgs as+      WallClockTime _ s _ -> addClocktime s+      -- Profiling Events       HeapProfBegin {} -> addFrame t       HeapProfCostCentre cid l m loc _  -> addCostCentre cid (CC cid l m loc)       HeapProfSampleBegin {} -> addFrame t       HeapProfSampleCostCentre _hid r d s -> addCCSample r d s       HeapProfSampleString _hid res k -> addSample (Sample k (fromIntegral res))-      ProgramArgs _ as -> addArgs as-      WallClockTime _ s _ -> addClocktime s       _ -> id  addIdent :: String -> EL -> EL@@ -155,23 +163,49 @@ addArgs :: [String] -> EL -> EL addArgs as el = el { pargs = Just as } -addTrace :: Trace -> EL -> EL-addTrace t el = el { traces = t : traces el } +-- | Decide whether to include a trace based on the "includes" and+-- "excludes" options.+--+-- If a trace satisfies an `-i` flag then it is certainly included.+--+-- For example for a trace called "eventlog2html" then `-i eventlog -x+-- html` will still include the trace because the `-i` option matches.+--+-- If a trace doesn't match an `-i` flag then it is excluded if it matches+-- a `-x` flag.+--+filterTrace :: [Text] -> [Text] -> Trace -> Bool+filterTrace []       []       _             = True+filterTrace []       excludes (Trace _ trc) =+  not (any (flip T.isInfixOf trc) excludes)+filterTrace includes []       (Trace _ trc) =+  any (flip T.isInfixOf trc) includes+filterTrace includes excludes (Trace _ trc) =+  any (flip T.isInfixOf trc) includes+    || not (any (flip T.isInfixOf trc) excludes)++addTrace :: Args -> Trace -> EL -> EL+addTrace a t el | noTraces a = el+                | prop t     = el { traces = t : traces el }+                | otherwise  = el+  where+    prop = filterTrace (includeStr a) (excludeStr a)+ addFrame :: Word64 -> EL -> EL addFrame t el =-  el { samples = Just (t, [])+  el { samples = Just (FrameEL t [])      , frames = sampleToFrames (samples el) (frames el) } -sampleToFrames :: Maybe (Word64, [Sample]) -> [(Word64, [Sample])]-                                           -> [(Word64, [Sample])]-sampleToFrames (Just (t, ss)) fs = (t, (reverse ss)) : fs+sampleToFrames :: Maybe FrameEL -> [FrameEL]+                                -> [FrameEL]+sampleToFrames (Just (FrameEL t ss)) fs = FrameEL t (reverse ss) : fs sampleToFrames Nothing fs = fs  addSample :: Sample -> EL -> EL addSample s el = el { samples = go <$> (samples el) }   where-    go (t, ss) = (t, (s:ss))+    go (FrameEL t ss) = FrameEL t (s:ss)  updateLast :: Word64 -> EL -> EL updateLast t el = el { end = t }
src/Eventlog/HeapProf.hs view
@@ -5,17 +5,16 @@ import Data.Text (Text, lines, init, drop, length, isPrefixOf, unpack, words, pack) import Data.Text.IO (readFile) import Data.Attoparsec.Text (parseOnly, double)-import Data.Map (Map)  import Eventlog.Total import Eventlog.Types -chunk :: FilePath -> IO (Header, Map Text (Double, Double), [Frame], [Trace])+chunk :: FilePath -> IO ProfData chunk f = do   (ph, fs) <- chunkT <$> readFile f   let (counts, totals) = total fs   -- Heap profiles do not support traces-  return (ph counts, totals, fs, [])+  return (ProfData (ph counts) totals fs [])  chunkT :: Text -> (Int -> Header, [Frame]) chunkT s =
src/Eventlog/Prune.hs view
@@ -1,23 +1,23 @@ module Eventlog.Prune   ( prune-  , Compare-  , cmpName-  , cmpSize-  , cmpStdDev   ) where  import Data.Text (Text)-import Data.List (foldl', sortBy)+import Data.List (sortBy) import Data.Ord (comparing) import Data.Map.Strict (Map, toList, fromList) +import Eventlog.Args (Args(..), Sort(..))+ type Compare a = a -> a -> Ordering -cmpName, cmpSize, cmpStdDev-  :: (Compare (Text, (Double, Double)), Compare (Text, (Double, Double)))-cmpName = (cmpNameAscending, cmpNameDescending)-cmpSize = (cmpSizeDescending, cmpSizeAscending)-cmpStdDev = (cmpStdDevDescending, cmpStdDevAscending)+getComparison :: Args -> Compare (Text, (Double, Double))+getComparison Args { sorting = Size,   reversing = False }  = cmpSizeDescending+getComparison Args { sorting = Size,   reversing = True } = cmpSizeAscending+getComparison Args { sorting = StdDev, reversing = False }  = cmpStdDevDescending+getComparison Args { sorting = StdDev, reversing = True } = cmpStdDevAscending+getComparison Args { sorting = Name,   reversing = True }  = cmpNameDescending+getComparison Args { sorting = Name,   reversing = False } = cmpNameAscending  cmpNameAscending, cmpNameDescending,   cmpStdDevAscending, cmpStdDevDescending,@@ -29,17 +29,18 @@ cmpSizeAscending = comparing (fst . snd) cmpSizeDescending = flip cmpSizeAscending -prune :: Compare (Text, (Double, Double)) -> Double -> Int -> Map Text (Double, Double) -> Map Text Int-prune cmp tracePercent nBands ts =+prune :: Args -> Map Text (Double, Double) -> Map Text Int+prune args ts =   let ccTotals = sortBy cmpSizeDescending (toList ts)       sizes = map (fst . snd) ccTotals-      total = sum' sizes-      limit = if tracePercent == 0 then total-                                   else (1 - tracePercent / 100) * total+      limit = sum sizes       bigs = takeWhile (< limit) . scanl (+) 0 $ sizes-      bands = zipWith const ccTotals $ take nBands bigs-      ccs = map fst (sortBy cmp bands)+      bands = zipWith const ccTotals $ take (bound $ nBands args) bigs+      ccs = map fst (sortBy (getComparison args) bands)   in  fromList (reverse ccs `zip` [1 ..]) -sum' :: [Double] -> Double-sum' = foldl' (+) 0+bound :: Int -> Int+bound n+  | n <= 0 = maxBound+  | otherwise = n+
src/Eventlog/Total.hs view
@@ -2,7 +2,7 @@ module Eventlog.Total (total) where  import Control.Monad.State.Strict (State(), execState, get, put, modify)-import Data.Map (Map, empty, lookup, insert, alter)+import Data.Map (Map, empty, alter) import Prelude hiding (init, lookup, lines, words, drop, length, readFile) import Data.Text (Text) @@ -11,13 +11,12 @@  data Parse =   Parse-  { symbols   :: !(Map Text Text) -- intern symbols to save RAM-  , totals    :: !(Map Text (Double, Double)) -- compute running totass and total of squares+  { totals    :: !(Map Text (Double, Double)) -- compute running totass and total of squares   , count     :: !Int                         -- number of frames   }  parse0 :: Parse-parse0 = Parse{ symbols = empty, totals = empty, count = 0 }+parse0 = Parse{ totals = empty, count = 0 }  total :: [Frame] -> (Int, Map Text (Double, Double)) total fs =@@ -39,13 +38,7 @@ inserter :: Sample -> State Parse Double inserter (Sample k v) = do   p <- get-  k' <- case lookup k (symbols p) of-    Nothing -> do-      put $! p{ symbols = insert k k (symbols p) }-      return k-    Just kk -> return kk-  p' <- get-  put $! p'{ totals = alter (accum  v) k' (totals p') }+  put $! p { totals = alter (accum  v) k (totals p) }   return $! v  accum :: Double -> Maybe (Double, Double) -> Maybe (Double, Double)
src/Eventlog/Types.hs view
@@ -1,6 +1,7 @@ module Eventlog.Types where  import Data.Text (Text)+import Data.Map (Map)  data Header =   Header@@ -17,3 +18,5 @@  -- | A trace we also want to show on the graph data Trace = Trace Double Text deriving Show++data ProfData = ProfData Header (Map Text (Double, Double)) [Frame] [Trace]