packages feed

ghc-stack-profiler-speedscope 0.4.0.0 → 0.5.0.0

raw patch · 5 files changed

+59/−56 lines, 5 filesdep ~basedep ~ghc-stack-profiler-core

Dependency ranges changed: base, ghc-stack-profiler-core

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for ghc-stack-profiler-speedscope +## 0.5.0.0 -- 2026-09-14++Major version number changed to match `ghc-stack-profiler` and `ghc-stack-profiler-core`.+ ## 0.4.0.0 -- 2026-07-14  - Switch to `ipedb` version 0.2.0.0 and the new database format.
ghc-stack-profiler-speedscope.cabal view
@@ -1,16 +1,18 @@ cabal-version: 3.8 name: ghc-stack-profiler-speedscope-version: 0.4.0.0+version: 0.5.0.0 license: BSD-3-Clause author: Hannes Siebenhandl, Wen Kokke, Matthew Pickering maintainer: hannes@well-typed.com build-type: Simple-synopsis: Convert eventlog messages from `ghc-stack-profiler` into a speedscope json.+synopsis:+  Export a ghc-stack-profiler call-stack profile from an eventlog to speedscope+ description:-  Convert eventlog messages from `ghc-stack-profiler` into a speedscope json.-  Produce a flame graph for the RTS callstack samples obtained from a program instrumented with-  'ghc-stack-profiler'.+  Export a @ghc-stack-profiler@ call-stack profile from an eventlog to speedscope. +  For details, see [@ghc-stack-profiler@](https://hackage.haskell.org/package/ghc-stack-profiler).+ extra-doc-files: CHANGELOG.md category: Profiling, Benchmarking, Development tested-with:@@ -51,14 +53,14 @@    build-depends:     aeson >=2.2 && <2.3,-    base >=4.20 && <4.23,+    base >=4.20 && <5,     bytestring >=0.11 && <0.13,     containers ^>=0.7 || ^>=0.8,     data-default >=0.2 && <0.9,     extra ^>=1.8.1,     filepath ^>=1.4 || ^>=1.5,     ghc-events ^>=0.20,-    ghc-stack-profiler-core >=0.3 && <0.5,+    ghc-stack-profiler-core ==0.5.0.0,     hs-speedscope ^>=0.3,     ipedb ^>=0.2.0.0,     machines ^>=0.7.4,
src/GHC/Stack/Profiler/Speedscope.hs view
@@ -29,10 +29,7 @@ import Data.Word (Word64) import qualified GHC.RTS.Events as E import qualified GHC.RTS.Events.Incremental as E-import qualified GHC.Stack.Profiler.Core.Eventlog as GSP-import qualified GHC.Stack.Profiler.Core.SymbolTable as GSP-import qualified GHC.Stack.Profiler.Core.ThreadSample as GSP-import qualified GHC.Stack.Profiler.Core.Util as GSP (word64ToInt)+import qualified GHC.Stack.Profiler.Core as GSPC import GHC.Stack.Profiler.Speedscope.Options import GHC.Stack.Profiler.Speedscope.Types import qualified IpeDB.Database as DB@@ -190,20 +187,22 @@     E.RtsIdentifier{rtsident} ->       pure st{maybeRtsVersion = parseIdent rtsident}     E.UserBinaryMessage bs ->-      case GSP.deserializeEventlogMessage $ BSL.fromStrict bs of+      case GSPC.deserializeEventlogMessage $ BSL.fromStrict bs of         Left _err ->           pure st         Right evMsg -> case evMsg of-          GSP.CallStackFinal msg -> do+          GSPC.ProtocolVersion _ver -> do+            pure st+          GSPC.CallStackFinal msg -> do             let               (callStackMessage, elProf1) = hydrateBinaryEventlog st msg             processCallStackMessage infoProvTable elProf1 callStackMessage-          GSP.CallStackChunk msg ->+          GSPC.CallStackChunk msg ->             pure st{current_callstack_chunks = msg : current_callstack_chunks st}-          GSP.StringDef msg ->-            pure st{hydration_table = GSP.insertTextMessage msg (hydration_table st)}-          GSP.SourceLocationDef msg ->-            case GSP.insertSourceLocationMessage msg (hydration_table st) of+          GSPC.StringDef msg ->+            pure st{hydration_table = GSPC.insertTextMessage msg (hydration_table st)}+          GSPC.SourceLocationDef msg ->+            case GSPC.insertSourceLocationMessage msg (hydration_table st) of               Left err ->                 pure $ addDecodingErrorsForStack [fromMissingKeyError err] st               Right newTable ->@@ -214,16 +213,16 @@ processCallStackMessage ::   DB.Table IP.InfoProvId IP.InfoProv ->   EventlogProfileState ->-  GSP.CallStackMessage ->+  GSPC.CallStack ->   IO EventlogProfileState-processCallStackMessage infoProvTable st0 GSP.MkCallStackMessage{callThreadId, callCapabilityId, callStack} = do+processCallStackMessage infoProvTable st0 GSPC.MkCallStack{callThreadId, callCapabilityId, callStack} = do   stackFramesOrErrors <- traverse (toStackFrame infoProvTable) callStack   let     (processingErrors, stackFrames) = partitionEithers stackFramesOrErrors     (st1, stackFrameIds) = mapAccumR processStackFrame st0 stackFrames     sample =       Sample-        { sampleThreadId = callThreadId+        { sampleThreadId = GSPC.getThreadId callThreadId         , sampleCapabilityId = callCapabilityId         , -- TODO: Don't use an arbitrary cutoff, but reduce cycles within the stack.           -- TODO: Perform cycle reduction or cutoff before the database lookups.@@ -232,7 +231,7 @@     st2 = addDecodingErrorsForStack processingErrors st1   pure $ st2{samples = sample : st2.samples} -hydrateBinaryEventlog :: EventlogProfileState -> GSP.BinaryCallStackMessage -> (GSP.CallStackMessage, EventlogProfileState)+hydrateBinaryEventlog :: EventlogProfileState -> GSPC.CallStackChunk -> (GSPC.CallStack, EventlogProfileState) hydrateBinaryEventlog st msg =   let     chunks = current_callstack_chunks st@@ -253,12 +252,12 @@     --    [2,1]     -- 3. When reading the eventlog, we store prepend later messages, resulting in:     --    [2,1] [4,3] [6,5]-    -- 4. 'catCallStackMessage' reverses the individual callstack chunks to be the inverse of 'chunkCallStackMessage'+    -- 4. 'joinCallStackChunks' reverses the individual callstack chunks to be the inverse of 'chunkCallStack'     orderedChunks = NonEmpty.reverse $ msg :| chunks-    fullBinaryCallStackMessage = GSP.catCallStackMessage orderedChunks+    fullBinaryCallStackMessage = GSPC.joinCallStackChunks orderedChunks     (callStackMessage, errs) =-      GSP.hydrateEventlogCallStackMessage-        (GSP.mkIntMapSymbolTableReader (hydration_table st))+      GSPC.hydrateEventlogCallStackMessage+        (GSPC.mkIntMapSymbolTableReader (hydration_table st))         fullBinaryCallStackMessage   in     ( callStackMessage@@ -270,16 +269,16 @@ -- | Convert a `StackItem` into a `StackFrame`. toStackFrame ::   DB.Table IP.InfoProvId IP.InfoProv ->-  GSP.StackItem ->+  GSPC.StackItem ->   IO (Either EventlogError StackFrame) toStackFrame infoProvTable = \case-  GSP.IpeId (toInfoProvId -> infoProvId) -> do+  GSPC.IpeId (toInfoProvId -> infoProvId) -> do     maybeInfoProv <- DB.lookup infoProvTable infoProvId     pure $       case maybeInfoProv of         Nothing -> Left $! UnknownInfoProvId infoProvId         Just infoProv -> Right $! StackFrameInfoProv infoProv-  GSP.UserAnnotation (Text.pack -> message) (toSrcLoc -> srcLoc) ->+  GSPC.UserAnnotation (Text.pack -> message) (toSrcLoc -> srcLoc) ->     pure . Right $! StackFrameMessage message srcLoc  -- | Ensure a `StackFrame` has a `StackFrameId` in the `EventlogProfileState`.@@ -327,11 +326,11 @@   -- ^ A unique counter for stack frames.   , samples :: [Sample]   -- ^ All samples in the reverse order of finding them in the eventlog.-  , hydration_table :: !GSP.IntMapTable+  , hydration_table :: !GSPC.IntMapTable   -- ^ The symbol table storing 'Text' and 'SourceLocation' symbols-  -- for hydrating a 'BinaryCallStackMessage' into a 'CallStackMessage'.-  , current_callstack_chunks :: [GSP.BinaryCallStackMessage]-  -- ^ Chunks of 'BinaryCallStackMessage' we are currently decoding.+  -- for hydrating a 'CallStackChunk' into a 'CallStack'.+  , current_callstack_chunks :: [GSPC.CallStackChunk]+  -- ^ Chunks of 'CallStackChunk' we are currently decoding.   -- All chunks are assumed to be from the same callstack and will be decoded once a   -- 'CallStackFinal' message is encountered.   , processingErrors :: [[EventlogError]]@@ -345,7 +344,7 @@     , stackFrames = Map.empty     , stackFrameCounter = 0     , samples = []-    , hydration_table = GSP.emptyIntMapTable+    , hydration_table = GSPC.emptyIntMapTable     , current_callstack_chunks = []     , processingErrors = []     }@@ -361,19 +360,19 @@  data EventlogError   = UnknownInfoProvId IP.InfoProvId-  | UnknownStringId GSP.StringId-  | UnknownSrcLocId GSP.SourceLocationId-  | SourceLocationPartUndefined GSP.SourceLocationId GSP.StringId+  | UnknownStringId GSPC.StringId+  | UnknownSrcLocId GSPC.SourceLocationId+  | SourceLocationPartUndefined GSPC.SourceLocationId GSPC.StringId   deriving (Show, Eq, Ord) -fromBinaryError :: GSP.BinaryCallStackDecodeError -> EventlogError+fromBinaryError :: GSPC.BinaryCallStackDecodeError -> EventlogError fromBinaryError = \case-  GSP.StringIdNotFound sid -> UnknownStringId sid-  GSP.SourceLocationIdNotFound sid -> UnknownSrcLocId sid+  GSPC.StringIdNotFound sid -> UnknownStringId sid+  GSPC.SourceLocationIdNotFound sid -> UnknownSrcLocId sid -fromMissingKeyError :: GSP.MissingKeyError -> EventlogError+fromMissingKeyError :: GSPC.MissingKeyError -> EventlogError fromMissingKeyError = \case-  GSP.KeyStringIdNotFound sourceLocId stringId -> SourceLocationPartUndefined sourceLocId stringId+  GSPC.KeyStringIdNotFound sourceLocId stringId -> SourceLocationPartUndefined sourceLocId stringId  prettyEventlogError :: EventlogError -> String prettyEventlogError = \case@@ -412,8 +411,8 @@         { unit = Speedscope.Nanoseconds         , name = programName <> " " <> Text.show sampleId         , startValue = 0-        , endValue = length samples-        , weights = replicate (length samples) 1+        , endValue = length sampleStack+        , weights = replicate (length sampleStack) 1         , samples = sampleStack         } @@ -423,16 +422,16 @@       -- NOTE: groupSort is assumed to be stable       PerThread -> groupSort $ toThreadSample <$> reverse samples       PerCapability -> groupSort $ toCapabilitySample <$> reverse samples-      NoAggregation -> [(1, toSingleProfileSample <$> reverse samples)]+      NoAggregation -> [(0, toSingleProfileSample <$> reverse samples)]    where     toThreadSample :: Sample -> (Word64, [Int])-    toThreadSample sample = (sample.sampleThreadId, toSingleProfileSample sample)+    toThreadSample sample = (fromIntegral . sampleThreadId $ sample, toSingleProfileSample sample)      toCapabilitySample :: Sample -> (Word64, [Int])-    toCapabilitySample sample = (coerce sample.sampleCapabilityId, toSingleProfileSample sample)+    toCapabilitySample sample = (fromIntegral . GSPC.getCapabilityId $ sample.sampleCapabilityId, toSingleProfileSample sample)      toSingleProfileSample :: Sample -> [Int]-    toSingleProfileSample sample = map (GSP.word64ToInt . coerce) sample.sampleStack+    toSingleProfileSample sample = map (fromIntegral @Word64 @Int . coerce) sample.sampleStack  toSpeedscopeFrame :: StackFrame -> Speedscope.Frame toSpeedscopeFrame = \case
src/GHC/Stack/Profiler/Speedscope/Options.hs view
@@ -142,9 +142,9 @@ aggregationModeParser :: O.Parser AggregationMode aggregationModeParser =   asum-    [ O.flag' PerThread (O.long "per-thread" <> O.help "Group the profile per thread (default)")-    , O.flag' PerCapability (O.long "per-capability" <> O.help "Group the profile per capability")-    , O.flag' NoAggregation (O.long "no-aggregation" <> O.help "Perform no grouping, single view")+    [ O.flag' PerThread (O.long "per-thread" <> O.help "Group the profiles by thread. (Default.)")+    , O.flag' PerCapability (O.long "per-capability" <> O.help "Group the profiles by capability")+    , O.flag' NoAggregation (O.long "no-aggregation" <> O.help "Do not aggregate the profiles.")     , pure PerThread     ] 
src/GHC/Stack/Profiler/Speedscope/Types.hs view
@@ -3,9 +3,7 @@ import Data.Text (Text) import qualified Data.Text as Text import Data.Word (Word64)-import qualified GHC.Stack.Profiler.Core.Eventlog as GSP (CapabilityId, IpeId (..))-import qualified GHC.Stack.Profiler.Core.SourceLocation as GSP (SourceLocation (..))-import qualified GHC.Stack.Profiler.Core.Util as GSP+import qualified GHC.Stack.Profiler.Core as GSP (CapabilityId, IpeId (..), SourceLocation (..)) import IpeDB.Types.InfoProv (InfoProv (..), InfoProvId (..)) import IpeDB.Types.SrcLoc (Point (..), Range (..), SrcLoc (..)) @@ -47,9 +45,9 @@   | otherwise = Just (Text.pack srcFilePath)  srcLocColumn :: SrcLoc -> Maybe Int-srcLocColumn SrcLoc{srcRange = Just Range{start = Point{column}}} = Just (GSP.word32ToInt column)+srcLocColumn SrcLoc{srcRange = Just Range{start = Point{column}}} = Just (fromIntegral column) srcLocColumn _ = Nothing  srcLocLine :: SrcLoc -> Maybe Int-srcLocLine SrcLoc{srcRange = Just Range{start = Point{line}}} = Just (GSP.word32ToInt line)+srcLocLine SrcLoc{srcRange = Just Range{start = Point{line}}} = Just (fromIntegral line) srcLocLine _ = Nothing