ghc-stack-profiler 0.2.0.0 → 0.3.0.0
raw patch · 8 files changed
+149/−149 lines, 8 filesdep ~binarydep ~ghc-experimentaldep ~ghc-heap
Dependency ranges changed: binary, ghc-experimental, ghc-heap, ghc-internal, ghc-stack-profiler-core
Files
- CHANGELOG.md +6/−0
- ghc-stack-profiler.cabal +12/−12
- src/GHC/Stack/Annotation/Experimental/Compat.hs +24/−6
- src/GHC/Stack/Profiler.hs +2/−2
- src/GHC/Stack/Profiler/Decode.hs +7/−10
- src/GHC/Stack/Profiler/Eventlog/Socket.hs +83/−0
- src/GHC/Stack/Profiler/FFI.hs +0/−96
- src/GHC/Stack/Profiler/Stack/Decode.hs +15/−23
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for ghc-stack-profiler +## 0.3.0.0 -- 2026-06-23++* Support GHC 10.1 [#29](https://github.com/well-typed/ghc-stack-profiler/pull/29)+ * Adds supports for optional source locations in stack annotations+* Add eventlog-socket tests to ghc-stack-profiler [#25](https://github.com/well-typed/ghc-stack-profiler/pull/25)+ ## 0.2.0.0 -- 2026-04-10 * Backport stack decoding segmentation fault fix from GHC HEAD [#20](https://github.com/well-typed/ghc-stack-profiler/pull/20)
ghc-stack-profiler.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.8 name: ghc-stack-profiler-version: 0.2.0.0+version: 0.3.0.0 license: BSD-3-Clause author: Hannes Siebenhandl, Wen Kokke, Matthew Pickering maintainer: hannes@well-typed.com@@ -14,11 +14,13 @@ To profile a program it needs to be compiled and instrumented with the 'ghc-stack-profiler' package via: @- import GHC.Stack.Profiler.Sampler+ import GHC.Stack.Profiler main :: IO ()- main = 'withStackProfilerForMyThread' ('SampleIntervalMs' 10) $ do- ...+ main = + 'withRootStackProfiler' True $ \ manager -> + 'withStackProfilerForMyThread' manager ('SampleIntervalMs' 10) $ do+ ... @ This will spawn a profiling thread that will periodically take a snapshot of the current RTS callstack of your program and serialises it to the eventlog.@@ -54,9 +56,7 @@ extra-doc-files: CHANGELOG.md category: Profiling, Benchmarking, Development tested-with:- ghc ==9.10.3- ghc ==9.12.2- ghc ==9.14.1+ ghc ==10.1 || ==9.14.1 || ==9.12.2 || ==9.10.3 common warnings ghc-options:@@ -106,7 +106,7 @@ GHC.Stack.Profiler GHC.Stack.Profiler.Commands GHC.Stack.Profiler.Decode- GHC.Stack.Profiler.FFI+ GHC.Stack.Profiler.Eventlog.Socket GHC.Stack.Profiler.Manager GHC.Stack.Profiler.Stack.Compat GHC.Stack.Profiler.Stack.Decode@@ -119,9 +119,9 @@ binary >=0.8.9.3 && <0.11, bytestring >=0.11 && <0.13, containers >=0.6.8 && <0.9,- ghc-heap >=9.10.1 && <9.16,- ghc-internal >=9.1001 && <9.1600,- ghc-stack-profiler-core ==0.2.0.0,+ ghc-heap >=9.10.1 && <10.2,+ ghc-internal >=9.1001 && <10.200,+ ghc-stack-profiler-core ==0.3.0.0, stm ^>=2.5.3.0 || ^>=2.5.0.0, text >=2 && <2.2, @@ -129,7 +129,7 @@ src if impl(ghc >=9.14)- build-depends: ghc-experimental >=9.1400 && <9.1600+ build-depends: ghc-experimental >=9.1400 && <10.200 if flag(use-ghc-trace-events) || impl(ghc <9.12) cpp-options:
src/GHC/Stack/Annotation/Experimental/Compat.hs view
@@ -3,21 +3,39 @@ module GHC.Stack.Annotation.Experimental.Compat ( SomeStackAnnotation (..),- CallStackAnnotation (..),- StringAnnotation (..),+ showStackAnnotationLocation,+ showStackAnnotationDescription, ) where #if MIN_VERSION_ghc_internal(9,1400,0) import GHC.Stack.Annotation.Experimental #else import Data.Typeable-import GHC.Stack.Types (CallStack)+#endif+import GHC.Stack.Types (SrcLoc) ++#if !MIN_VERSION_ghc_internal(9,1400,0) data SomeStackAnnotation where SomeStackAnnotation :: forall a. (Typeable a) => a -> SomeStackAnnotation+#endif -data StringAnnotation where- StringAnnotation :: String -> StringAnnotation -newtype CallStackAnnotation = CallStackAnnotation CallStack+showStackAnnotationLocation :: SomeStackAnnotation -> Maybe SrcLoc+showStackAnnotationLocation =+#if MIN_VERSION_ghc_internal(9,1402,0)+ stackAnnotationSourceLocation+#else+ \ _ann -> Nothing+#endif+++showStackAnnotationDescription :: SomeStackAnnotation -> String+showStackAnnotationDescription =+#if MIN_VERSION_ghc_internal(9,1402,0)+ displayStackAnnotationShort+#elif MIN_VERSION_ghc_internal(9,1400,0)+ displayStackAnnotation+#else+ \ _ann -> "showStackAnnotationLocation: Impossible, no value should be created with ghc-internal < 9.1400" #endif
src/GHC/Stack/Profiler.hs view
@@ -49,7 +49,7 @@ import GHC.Stack.Profiler.Core.Util import GHC.Stack.Profiler.Decode import qualified GHC.Stack.Profiler.Decode as Decode-import qualified GHC.Stack.Profiler.FFI as FFI+import qualified GHC.Stack.Profiler.Eventlog.Socket as EventlogSocket import GHC.Stack.Profiler.Manager import GHC.Stack.Profiler.SymbolTable (readSymbolTable) @@ -117,7 +117,7 @@ runNewStackProfilerManager shouldRun = do manager <- newStackProfilerManager shouldRun startEventLoopThread manager- FFI.installEventlogSocketHandlers manager `catches` FFI.defaultErrorHandlers+ EventlogSocket.registerWithEventlogSocket manager pure manager shutdownStackProfilerManager :: StackProfilerManager -> IO ()
src/GHC/Stack/Profiler/Decode.hs view
@@ -82,16 +82,13 @@ go :: SourceLocationId -> SourceLocation -> BinarySourceLocationMessage go sid s = let- (funcId, newFuncName, _) = lookupOrInsertText table (writerTable table) (functionName s) (fileId, newFileName, _) = lookupOrInsertText table (writerTable table) (fileName s) in -- These should always be found- assert (not newFuncName) $- assert (not newFileName) $- MkBinarySourceLocationMessage- { binarySourceLocationMessageId = sid- , binarySourceLocationRow = line s- , binarySourceLocationColumn = column s- , binarySourceLocationFunctionId = funcId- , binarySourceLocationFilename = fileId- }+ assert (not newFileName) $+ MkBinarySourceLocationMessage+ { binarySourceLocationMessageId = sid+ , binarySourceLocationRow = line s+ , binarySourceLocationColumn = column s+ , binarySourceLocationFilename = fileId+ }
+ src/GHC/Stack/Profiler/Eventlog/Socket.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE CPP #-}++module GHC.Stack.Profiler.Eventlog.Socket (+ registerWithEventlogSocket,+) where++import GHC.Stack.Profiler.Manager (StackProfilerManager)++#ifdef EVENTLOG_SOCKET_SUPPORT+import qualified Control.Monad.STM as STM+import GHC.Eventlog.Socket (CommandId (..), Hook (..), registerCommand, registerHook, registerNamespace)+import GHC.Stack.Profiler.Commands (startProfiling, stopProfiling, sendEnableEventlogMessage, sendDisableEventlogMessage, sendPublishInitEventMessages)+import GHC.Stack.Profiler.Manager (disableEventLogging)+import Debug.Trace (traceMarkerIO)+#endif++-- | Register the @eventlog-socket@ custom command handlers and lifecycle hooks.+--+-- This adds support for the following @eventlog-socket@ custom commands:+--+-- * @0x01@: Start profiling.+-- * @0x02@: Stop profiling.+--+-- If built with @+control@, this may throw an [@EventlogSocketControlError@](https://hackage-content.haskell.org/package/eventlog-socket/docs/GHC-Eventlog-Socket.html#t:EventlogSocketControlError).+registerWithEventlogSocket :: StackProfilerManager -> IO ()+#ifdef EVENTLOG_SOCKET_SUPPORT+registerWithEventlogSocket = registerWithEventlogSocketIfSupported+#else+registerWithEventlogSocket = const $ pure ()+#endif++#ifdef EVENTLOG_SOCKET_SUPPORT+-- The real implementation of @registerWithEventlogSocket@.+registerWithEventlogSocketIfSupported :: StackProfilerManager -> IO ()+registerWithEventlogSocketIfSupported manager = do+ -- Register the PostStartEventLogging and PreEndEventLogging hooks.+ registerHook HookPostStartEventLogging $ startEventLoggingHook manager+ registerHook HookPreEndEventLogging $ endEventLoggingHook manager++ -- Register the custom commands under the ghc-stack-profiler namespace.+ ns <- registerNamespace "ghc-stack-profiler"+ registerCommand ns startProfilerCommandId (startProfilerCommand manager)+ registerCommand ns stopProfilerCommandId (stopProfilerCommand manager)++-- The @startProfiler@ command ID.+startProfilerCommandId :: CommandId+startProfilerCommandId = CommandId 0x1++-- The @stopProfiler@ command ID.+stopProfilerCommandId :: CommandId+stopProfilerCommandId = CommandId 0x2++-- | The handler for @eventlog-socket@'s @PostStartEventLogging@ hook.+--+-- This publishes the init events, flushes the eventlog, informs the manager+-- that the eventlog is enabled, and blocks until this message is processed.+startEventLoggingHook :: StackProfilerManager -> IO ()+startEventLoggingHook manager = do+ sendPublishInitEventMessages manager+ sendEnableEventlogMessage manager++-- | The handler for @eventlog-socket@'s @PreEndEventLogging@ hook.+--+-- This stops all profiler threads from writing to the eventlog, which stops+-- all sampler threads, informs the manager that the eventlog is disabled, and+-- blocks until this message is processed.+endEventLoggingHook :: StackProfilerManager -> IO ()+endEventLoggingHook manager = do+ STM.atomically $ disableEventLogging manager+ sendDisableEventlogMessage manager++-- | The handler for the @StartProfiling@ custom command.+startProfilerCommand :: StackProfilerManager -> IO ()+startProfilerCommand manager = do+ traceMarkerIO "ghc-stack-profiler: Start profiling"+ startProfiling manager++-- | The handler for the @StopProfiling@ custom command.+stopProfilerCommand :: StackProfilerManager -> IO ()+stopProfilerCommand manager = do+ stopProfiling manager+ traceMarkerIO "ghc-stack-profiler: Stop profiling"+#endif
− src/GHC/Stack/Profiler/FFI.hs
@@ -1,96 +0,0 @@-{-# LANGUAGE CApiFFI #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE ForeignFunctionInterface #-}--module GHC.Stack.Profiler.FFI (- installEventlogSocketHandlers,- defaultErrorHandlers,-) where--#if defined(EVENTLOG_SOCKET_SUPPORT)-import GHC.Eventlog.Socket-import System.IO (hPutStrLn, stderr)-#endif--import Control.Exception-import qualified Control.Monad.STM as STM-import GHC.Stack.Profiler.Manager-import GHC.Stack.Profiler.Commands--#if defined(EVENTLOG_SOCKET_SUPPORT)-startProfilerCommandId :: CommandId-startProfilerCommandId = CommandId 0x1--stopProfilerCommandId :: CommandId-stopProfilerCommandId = CommandId 0x2-#endif---- | Install the @eventlog-socket@ custom command handlers and lifecycle hooks.------ The supported custom commands are:--- * Start the profiler--- * Stop the profiler------ We implement the lifecycle hooks for stopping the eventlog and starting--- writing to the eventlog.--- When we start eventlogging, we post the definitions of the existing callstack--- definitions, e.g., string and source locations.------ May throw 'EventlogSocketControlError' when registering @eventlog-socket@--- hooks fails.-installEventlogSocketHandlers :: StackProfilerManager -> IO ()-installEventlogSocketHandlers =-#if defined(EVENTLOG_SOCKET_SUPPORT)- \ manager -> do- registerEventlogSocketHooks manager- where- registerEventlogSocketHooks manager = do- registerHook HookPostStartEventLogging $- startEventLoggingHook manager- registerHook HookPreEndEventLogging $- endEventLoggingHook manager- ns <- registerNamespace "ghc-stack-profiler"- registerCommand ns startProfilerCommandId (startProfilerCommand manager)- registerCommand ns stopProfilerCommandId (stopProfilerCommand manager)-#else- \ _manager ->- pure ()-#endif--defaultErrorHandlers :: [Handler ()]-defaultErrorHandlers =- [-#if defined(EVENTLOG_SOCKET_SUPPORT)- Handler $ \ (e :: EventlogSocketControlError) -> do- hPutStrLn stderr "Failed to register eventlog-socket commands"- hPutStrLn stderr (displayException e)-#endif- ]---- | Post-start EventLogging hook.------ 1. Publish init events and flush the eventlog.--- 2. Inform the main loop that the eventlog is ready for messages now.-startEventLoggingHook :: StackProfilerManager -> IO ()-startEventLoggingHook manager = do- sendPublishInitEventMessages manager- -- Block until start message has been processed- sendEnableEventlogMessage manager---- | Pre-end EventLogging hook.-endEventLoggingHook :: StackProfilerManager -> IO ()-endEventLoggingHook manager = do- -- Disallow logging any more messages to the eventlog.- -- Stops the profiler sampling threads.- STM.atomically $ disableEventLogging manager-- -- Block until all messages have been processed- sendDisableEventlogMessage manager--startProfilerCommand :: StackProfilerManager -> IO ()-startProfilerCommand manager = do- startProfiling manager--stopProfilerCommand :: StackProfilerManager -> IO ()-stopProfilerCommand manager = do- stopProfiling manager
src/GHC/Stack/Profiler/Stack/Decode.hs view
@@ -6,7 +6,6 @@ import Data.Maybe (catMaybes) import qualified Data.Text as Text-import Data.Typeable (cast) import Unsafe.Coerce (unsafeCoerce) import GHC.Internal.ClosureTypes.Compat@@ -55,28 +54,21 @@ let Box annotation = getClosureBox stack# (index + offsetStgAnnFrameAnn) in- pure $ stackAnnotationToStackItem (unsafeCoerce annotation)+ pure $ Just $ stackAnnotationToStackItem (unsafeCoerce annotation) _ -> fmap (IpeId . MkIpeId) <$> lookupIpeIdForStackFrame stackItbl -stackAnnotationToStackItem :: SomeStackAnnotation -> Maybe StackItem-stackAnnotationToStackItem = \case- SomeStackAnnotation ann ->- case cast ann of- Just (CallStackAnnotation cs) ->- case getCallStack cs of- [] -> Nothing- ((name, sourceLoc) : _) ->- Just $- SourceLocation $- MkSourceLocation- { line = intToWord32 $ srcLocStartLine sourceLoc- , column = intToWord32 $ srcLocStartCol sourceLoc- , functionName = Text.pack $ name- , fileName = Text.pack $ srcLocFile sourceLoc- }- Nothing -> case cast ann of- Just (StringAnnotation msg) ->- Just $ UserMessage msg- Nothing ->- Nothing+stackAnnotationToStackItem :: SomeStackAnnotation -> StackItem+stackAnnotationToStackItem someStackAnnotation =+ let+ message = showStackAnnotationDescription someStackAnnotation+ sourceLoc = do+ srcLoc <- showStackAnnotationLocation someStackAnnotation+ Just $+ MkSourceLocation+ { line = intToWord32 $ srcLocStartLine srcLoc+ , column = intToWord32 $ srcLocStartCol srcLoc+ , fileName = Text.pack $ srcLocFile srcLoc+ }+ in+ UserAnnotation message sourceLoc