packages feed

ghc-stack-profiler-core 0.4.0.0 → 0.5.0.0

raw patch · 15 files changed

+1597/−1095 lines, 15 filesdep +tasty-hunitdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: tasty-hunit

Dependency ranges changed: base

API changes (from Hackage documentation)

- GHC.Stack.Profiler.Core.Eventlog: BinaryIpe :: {-# UNPACK #-} !IpeId -> BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: BinaryMessage :: {-# UNPACK #-} !StringId -> {-# UNPACK #-} !Maybe SourceLocationId -> BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: CallStackChunk :: !BinaryCallStackMessage -> BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: CallStackFinal :: !BinaryCallStackMessage -> BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: MkBinaryCallStackMessage :: !Word64 -> !CapabilityId -> ![BinaryStackItem] -> BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: MkBinarySourceLocationMessage :: {-# UNPACK #-} !SourceLocationId -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !StringId -> BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: MkBinaryStringMessage :: !StringId -> !Text -> BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: MkCapabilityId :: Word64 -> CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: MkIpeId :: Word64 -> IpeId
- GHC.Stack.Profiler.Core.Eventlog: MkSourceLocationId :: Word64 -> SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: MkStringId :: Word64 -> StringId
- GHC.Stack.Profiler.Core.Eventlog: SourceLocationDef :: !BinarySourceLocationMessage -> BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: StringDef :: !BinaryStringMessage -> BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: [binaryCallCapabilityId] :: BinaryCallStackMessage -> !CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: [binaryCallStack] :: BinaryCallStackMessage -> ![BinaryStackItem]
- GHC.Stack.Profiler.Core.Eventlog: [binaryCallThreadId] :: BinaryCallStackMessage -> !Word64
- GHC.Stack.Profiler.Core.Eventlog: [binarySourceLocationColumn] :: BinarySourceLocationMessage -> {-# UNPACK #-} !Word32
- GHC.Stack.Profiler.Core.Eventlog: [binarySourceLocationFilename] :: BinarySourceLocationMessage -> {-# UNPACK #-} !StringId
- GHC.Stack.Profiler.Core.Eventlog: [binarySourceLocationMessageId] :: BinarySourceLocationMessage -> {-# UNPACK #-} !SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: [binarySourceLocationRow] :: BinarySourceLocationMessage -> {-# UNPACK #-} !Word32
- GHC.Stack.Profiler.Core.Eventlog: [binaryStringMessageId] :: BinaryStringMessage -> !StringId
- GHC.Stack.Profiler.Core.Eventlog: [binaryStringMessage] :: BinaryStringMessage -> !Text
- GHC.Stack.Profiler.Core.Eventlog: [getCapabilityId] :: CapabilityId -> Word64
- GHC.Stack.Profiler.Core.Eventlog: [getIpeId] :: IpeId -> Word64
- GHC.Stack.Profiler.Core.Eventlog: [getSourceLocationId] :: SourceLocationId -> Word64
- GHC.Stack.Profiler.Core.Eventlog: [getStringId] :: StringId -> Word64
- GHC.Stack.Profiler.Core.Eventlog: byteSizeOf :: BinaryStackItem -> Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackFinalMessageTag :: Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackMessageTags :: [Word16]
- GHC.Stack.Profiler.Core.Eventlog: callStackPartialMessageTag :: Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackSizeLimit :: Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackSizeLimit_ :: Word64 -> Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackSourceLocationMessageTag :: Word16
- GHC.Stack.Profiler.Core.Eventlog: callStackStringMessageTag :: Word16
- GHC.Stack.Profiler.Core.Eventlog: data BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: data BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: data BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: data BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: data BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: eventlogBufferSize :: Word64
- GHC.Stack.Profiler.Core.Eventlog: incrementSourceLocationId :: SourceLocationId -> SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: incrementStringLocationId :: StringId -> StringId
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance Data.Binary.Class.Binary GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Read.Read GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.BinaryCallStackMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.BinaryEventlogMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.BinarySourceLocationMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.BinaryStackItem
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.BinaryStringMessage
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.IpeId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: instance GHC.Show.Show GHC.Stack.Profiler.Core.Eventlog.StringId
- GHC.Stack.Profiler.Core.Eventlog: newtype CapabilityId
- GHC.Stack.Profiler.Core.Eventlog: newtype IpeId
- GHC.Stack.Profiler.Core.Eventlog: newtype SourceLocationId
- GHC.Stack.Profiler.Core.Eventlog: newtype StringId
- GHC.Stack.Profiler.Core.Eventlog: stringLengthLimit :: Word16
- GHC.Stack.Profiler.Core.SourceLocation: MkSourceLocation :: !Word32 -> !Word32 -> !Text -> SourceLocation
- GHC.Stack.Profiler.Core.SourceLocation: [column] :: SourceLocation -> !Word32
- GHC.Stack.Profiler.Core.SourceLocation: [fileName] :: SourceLocation -> !Text
- GHC.Stack.Profiler.Core.SourceLocation: [line] :: SourceLocation -> !Word32
- GHC.Stack.Profiler.Core.SourceLocation: data SourceLocation
- GHC.Stack.Profiler.Core.SourceLocation: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.SourceLocation.SourceLocation
- GHC.Stack.Profiler.Core.SourceLocation: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.SourceLocation.SourceLocation
- GHC.Stack.Profiler.Core.SourceLocation: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.SourceLocation.SourceLocation
- GHC.Stack.Profiler.Core.SourceLocation: instance GHC.Show.Show GHC.Stack.Profiler.Core.SourceLocation.SourceLocation
- GHC.Stack.Profiler.Core.SymbolTable: KeyStringIdNotFound :: SourceLocationId -> StringId -> MissingKeyError
- GHC.Stack.Profiler.Core.SymbolTable: MkSymbolTableReader :: (StringId -> Maybe Text) -> (SourceLocationId -> Maybe SourceLocation) -> SymbolTableReader
- GHC.Stack.Profiler.Core.SymbolTable: MkSymbolTableWriter :: !tbl -> (tbl -> Text -> (StringId, Bool, tbl)) -> (tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)) -> SymbolTableWriter tbl
- GHC.Stack.Profiler.Core.SymbolTable: [lookupOrInsertSourceLocation] :: SymbolTableWriter tbl -> tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)
- GHC.Stack.Profiler.Core.SymbolTable: [lookupOrInsertText] :: SymbolTableWriter tbl -> tbl -> Text -> (StringId, Bool, tbl)
- GHC.Stack.Profiler.Core.SymbolTable: [lookupSourceLocationId] :: SymbolTableReader -> SourceLocationId -> Maybe SourceLocation
- GHC.Stack.Profiler.Core.SymbolTable: [lookupStringId] :: SymbolTableReader -> StringId -> Maybe Text
- GHC.Stack.Profiler.Core.SymbolTable: [writerTable] :: SymbolTableWriter tbl -> !tbl
- GHC.Stack.Profiler.Core.SymbolTable: data IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: data MapTable
- GHC.Stack.Profiler.Core.SymbolTable: data MissingKeyError
- GHC.Stack.Profiler.Core.SymbolTable: data SymbolTableReader
- GHC.Stack.Profiler.Core.SymbolTable: data SymbolTableWriter tbl
- GHC.Stack.Profiler.Core.SymbolTable: emptyIntMapTable :: IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: emptyMapSymbolTableWriter :: SymbolTableWriter MapTable
- GHC.Stack.Profiler.Core.SymbolTable: getKnownSourceLocations :: MapTable -> [(SourceLocationId, SourceLocation)]
- GHC.Stack.Profiler.Core.SymbolTable: getKnownStrings :: MapTable -> [(StringId, Text)]
- GHC.Stack.Profiler.Core.SymbolTable: insertSourceLocationMessage :: BinarySourceLocationMessage -> IntMapTable -> Either MissingKeyError IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: insertTextMessage :: BinaryStringMessage -> IntMapTable -> IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.SymbolTable.IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.SymbolTable.MapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.SymbolTable.IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.SymbolTable.MapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Exception.Type.Exception GHC.Stack.Profiler.Core.SymbolTable.MissingKeyError
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Generics.Generic (GHC.Stack.Profiler.Core.SymbolTable.SymbolTableWriter tbl)
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.SymbolTable.IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.SymbolTable.MapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.SymbolTable.SymbolTableReader
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Show.Show GHC.Stack.Profiler.Core.SymbolTable.IntMapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Show.Show GHC.Stack.Profiler.Core.SymbolTable.MapTable
- GHC.Stack.Profiler.Core.SymbolTable: instance GHC.Show.Show GHC.Stack.Profiler.Core.SymbolTable.MissingKeyError
- GHC.Stack.Profiler.Core.SymbolTable: mkIntMapSymbolTableReader :: IntMapTable -> SymbolTableReader
- GHC.Stack.Profiler.Core.ThreadSample: IpeId :: !IpeId -> StackItem
- GHC.Stack.Profiler.Core.ThreadSample: MkCallStackMessage :: !Word64 -> !CapabilityId -> [StackItem] -> CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: MkSourceLocation :: !Word32 -> !Word32 -> !Text -> SourceLocation
- GHC.Stack.Profiler.Core.ThreadSample: MkSymbolTableReader :: (StringId -> Maybe Text) -> (SourceLocationId -> Maybe SourceLocation) -> SymbolTableReader
- GHC.Stack.Profiler.Core.ThreadSample: MkSymbolTableWriter :: !tbl -> (tbl -> Text -> (StringId, Bool, tbl)) -> (tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)) -> SymbolTableWriter tbl
- GHC.Stack.Profiler.Core.ThreadSample: SourceLocationIdNotFound :: SourceLocationId -> BinaryCallStackDecodeError
- GHC.Stack.Profiler.Core.ThreadSample: StringIdNotFound :: StringId -> BinaryCallStackDecodeError
- GHC.Stack.Profiler.Core.ThreadSample: ThreadSample :: !ThreadId -> !CapabilityId -> !StackSnapshot -> ThreadSample
- GHC.Stack.Profiler.Core.ThreadSample: UserAnnotation :: !String -> !Maybe SourceLocation -> StackItem
- GHC.Stack.Profiler.Core.ThreadSample: [callCapabilityId] :: CallStackMessage -> !CapabilityId
- GHC.Stack.Profiler.Core.ThreadSample: [callStack] :: CallStackMessage -> [StackItem]
- GHC.Stack.Profiler.Core.ThreadSample: [callThreadId] :: CallStackMessage -> !Word64
- GHC.Stack.Profiler.Core.ThreadSample: [column] :: SourceLocation -> !Word32
- GHC.Stack.Profiler.Core.ThreadSample: [fileName] :: SourceLocation -> !Text
- GHC.Stack.Profiler.Core.ThreadSample: [line] :: SourceLocation -> !Word32
- GHC.Stack.Profiler.Core.ThreadSample: [lookupOrInsertSourceLocation] :: SymbolTableWriter tbl -> tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)
- GHC.Stack.Profiler.Core.ThreadSample: [lookupOrInsertText] :: SymbolTableWriter tbl -> tbl -> Text -> (StringId, Bool, tbl)
- GHC.Stack.Profiler.Core.ThreadSample: [lookupSourceLocationId] :: SymbolTableReader -> SourceLocationId -> Maybe SourceLocation
- GHC.Stack.Profiler.Core.ThreadSample: [lookupStringId] :: SymbolTableReader -> StringId -> Maybe Text
- GHC.Stack.Profiler.Core.ThreadSample: [threadSampleCapability] :: ThreadSample -> !CapabilityId
- GHC.Stack.Profiler.Core.ThreadSample: [threadSampleId] :: ThreadSample -> !ThreadId
- GHC.Stack.Profiler.Core.ThreadSample: [threadSampleStackSnapshot] :: ThreadSample -> !StackSnapshot
- GHC.Stack.Profiler.Core.ThreadSample: [writerTable] :: SymbolTableWriter tbl -> !tbl
- GHC.Stack.Profiler.Core.ThreadSample: catCallStackMessage :: NonEmpty BinaryCallStackMessage -> BinaryCallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: chunkCallStackMessage :: BinaryCallStackMessage -> [BinaryEventlogMessage]
- GHC.Stack.Profiler.Core.ThreadSample: chunkCallStackMessage_ :: Word16 -> BinaryCallStackMessage -> [BinaryEventlogMessage]
- GHC.Stack.Profiler.Core.ThreadSample: data BinaryCallStackDecodeError
- GHC.Stack.Profiler.Core.ThreadSample: data CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: data EncodingState tbl
- GHC.Stack.Profiler.Core.ThreadSample: data SourceLocation
- GHC.Stack.Profiler.Core.ThreadSample: data StackItem
- GHC.Stack.Profiler.Core.ThreadSample: data SymbolTableReader
- GHC.Stack.Profiler.Core.ThreadSample: data SymbolTableWriter tbl
- GHC.Stack.Profiler.Core.ThreadSample: data ThreadSample
- GHC.Stack.Profiler.Core.ThreadSample: dehydrateCallStackMessage :: SymbolTableWriter table -> CallStackMessage -> ([BinaryEventlogMessage], SymbolTableWriter table)
- GHC.Stack.Profiler.Core.ThreadSample: deserializeEventlogMessage :: ByteString -> Either String BinaryEventlogMessage
- GHC.Stack.Profiler.Core.ThreadSample: hydrateEventlogCallStackMessage :: SymbolTableReader -> BinaryCallStackMessage -> (CallStackMessage, [BinaryCallStackDecodeError])
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.ThreadSample.CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Classes.Eq GHC.Stack.Profiler.Core.ThreadSample.StackItem
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.ThreadSample.CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Classes.Ord GHC.Stack.Profiler.Core.ThreadSample.StackItem
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Exception.Type.Exception GHC.Stack.Profiler.Core.ThreadSample.BinaryCallStackDecodeError
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Generics.Generic (GHC.Stack.Profiler.Core.ThreadSample.EncodingState tbl)
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.ThreadSample.CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.ThreadSample.StackItem
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Generics.Generic GHC.Stack.Profiler.Core.ThreadSample.ThreadSample
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Show.Show GHC.Stack.Profiler.Core.ThreadSample.BinaryCallStackDecodeError
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Show.Show GHC.Stack.Profiler.Core.ThreadSample.CallStackMessage
- GHC.Stack.Profiler.Core.ThreadSample: instance GHC.Show.Show GHC.Stack.Profiler.Core.ThreadSample.StackItem
- GHC.Stack.Profiler.Core.ThreadSample: lookupSourceLocationMessage :: SourceLocation -> State (EncodingState tbl) SourceLocationId
- GHC.Stack.Profiler.Core.ThreadSample: lookupTextMessage :: Text -> State (EncodingState tbl) StringId
- GHC.Stack.Profiler.Core.ThreadSample: newEncodingState :: SymbolTableWriter tbl -> EncodingState tbl
- GHC.Stack.Profiler.Core.ThreadSample: runWithEncodingState :: EncodingState tbl -> State (EncodingState tbl) a -> (a, EncodingState tbl)
- GHC.Stack.Profiler.Core.Util: getTextWord16 :: Get Text
- GHC.Stack.Profiler.Core.Util: getWord16 :: Get Word16
- GHC.Stack.Profiler.Core.Util: getWord32 :: Get Word32
- GHC.Stack.Profiler.Core.Util: getWord64 :: Get Word64
- GHC.Stack.Profiler.Core.Util: idToInt :: Coercible a Word64 => a -> Int
- GHC.Stack.Profiler.Core.Util: intToWord16 :: Int -> Word16
- GHC.Stack.Profiler.Core.Util: intToWord32 :: Int -> Word32
- GHC.Stack.Profiler.Core.Util: intToWord64 :: Int -> Word64
- GHC.Stack.Profiler.Core.Util: intToWord8 :: Int -> Word8
- GHC.Stack.Profiler.Core.Util: putTextWord16 :: Word16 -> Text -> Put
- GHC.Stack.Profiler.Core.Util: putWord16 :: Word16 -> Put
- GHC.Stack.Profiler.Core.Util: putWord32 :: Word32 -> Put
- GHC.Stack.Profiler.Core.Util: putWord64 :: Word64 -> Put
- GHC.Stack.Profiler.Core.Util: showAsHex :: Integral a => a -> String
- GHC.Stack.Profiler.Core.Util: word16ToInt :: Word16 -> Int
- GHC.Stack.Profiler.Core.Util: word32ToInt :: Word32 -> Int
- GHC.Stack.Profiler.Core.Util: word32ToWord64 :: Word32 -> Word64
- GHC.Stack.Profiler.Core.Util: word64ToInt :: Word64 -> Int
- GHC.Stack.Profiler.Core.Util: word64ToWord16 :: Word64 -> Word16
- GHC.Stack.Profiler.Core.Util: word64ToWord32 :: Word64 -> Word32
- GHC.Stack.Profiler.Core.Util: word8ToInt :: Word8 -> Int
+ GHC.Stack.Profiler.Core: CallStackChunk :: !CallStackChunk -> Message
+ GHC.Stack.Profiler.Core: CallStackFinal :: !CallStackChunk -> Message
+ GHC.Stack.Profiler.Core: CallStackFrameAnn :: {-# UNPACK #-} !StringId -> {-# UNPACK #-} !Maybe SourceLocationId -> CallStackFrame
+ GHC.Stack.Profiler.Core: CallStackFrameIpe :: {-# UNPACK #-} !IpeId -> CallStackFrame
+ GHC.Stack.Profiler.Core: IpeId :: !IpeId -> StackItem
+ GHC.Stack.Profiler.Core: KeyStringIdNotFound :: SourceLocationId -> StringId -> MissingKeyError
+ GHC.Stack.Profiler.Core: MkCallStack :: !ThreadId -> !CapabilityId -> [StackItem] -> CallStack
+ GHC.Stack.Profiler.Core: MkCallStackChunk :: !ThreadId -> !CapabilityId -> ![CallStackFrame] -> CallStackChunk
+ GHC.Stack.Profiler.Core: MkCapabilityId :: Word32 -> CapabilityId
+ GHC.Stack.Profiler.Core: MkIpeId :: Word64 -> IpeId
+ GHC.Stack.Profiler.Core: MkProtocolVersion :: Word8 -> ProtocolVersion
+ GHC.Stack.Profiler.Core: MkProtocolVersionMismatch :: !ProtocolVersion -> !ProtocolVersion -> ProtocolVersionMismatch
+ GHC.Stack.Profiler.Core: MkSourceLocation :: !Word32 -> !Word32 -> !Text -> SourceLocation
+ GHC.Stack.Profiler.Core: MkSourceLocationDef :: {-# UNPACK #-} !SourceLocationId -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !StringId -> SourceLocationDef
+ GHC.Stack.Profiler.Core: MkSourceLocationId :: Word64 -> SourceLocationId
+ GHC.Stack.Profiler.Core: MkStringDef :: !StringId -> !Text -> StringDef
+ GHC.Stack.Profiler.Core: MkStringId :: Word64 -> StringId
+ GHC.Stack.Profiler.Core: MkSymbolTableReader :: (StringId -> Maybe Text) -> (SourceLocationId -> Maybe SourceLocation) -> SymbolTableReader
+ GHC.Stack.Profiler.Core: MkSymbolTableWriter :: !tbl -> (tbl -> Text -> (StringId, Bool, tbl)) -> (tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)) -> SymbolTableWriter tbl
+ GHC.Stack.Profiler.Core: MkThreadId :: Word64 -> ThreadId
+ GHC.Stack.Profiler.Core: ProtocolVersion :: !ProtocolVersion -> Message
+ GHC.Stack.Profiler.Core: SourceLocationDef :: !SourceLocationDef -> Message
+ GHC.Stack.Profiler.Core: SourceLocationIdNotFound :: SourceLocationId -> BinaryCallStackDecodeError
+ GHC.Stack.Profiler.Core: StringDef :: !StringDef -> Message
+ GHC.Stack.Profiler.Core: StringIdNotFound :: StringId -> BinaryCallStackDecodeError
+ GHC.Stack.Profiler.Core: UserAnnotation :: !String -> !Maybe SourceLocation -> StackItem
+ GHC.Stack.Profiler.Core: [actualProtocolVersion] :: ProtocolVersionMismatch -> !ProtocolVersion
+ GHC.Stack.Profiler.Core: [callCapabilityId] :: CallStack -> !CapabilityId
+ GHC.Stack.Profiler.Core: [callStackChunkCapabilityId] :: CallStackChunk -> !CapabilityId
+ GHC.Stack.Profiler.Core: [callStackChunkThreadId] :: CallStackChunk -> !ThreadId
+ GHC.Stack.Profiler.Core: [callStackChunk] :: CallStackChunk -> ![CallStackFrame]
+ GHC.Stack.Profiler.Core: [callStack] :: CallStack -> [StackItem]
+ GHC.Stack.Profiler.Core: [callThreadId] :: CallStack -> !ThreadId
+ GHC.Stack.Profiler.Core: [column] :: SourceLocation -> !Word32
+ GHC.Stack.Profiler.Core: [expectProtocolVersion] :: ProtocolVersionMismatch -> !ProtocolVersion
+ GHC.Stack.Profiler.Core: [fileName] :: SourceLocation -> !Text
+ GHC.Stack.Profiler.Core: [getCapabilityId] :: CapabilityId -> Word32
+ GHC.Stack.Profiler.Core: [getIpeId] :: IpeId -> Word64
+ GHC.Stack.Profiler.Core: [getSourceLocationId] :: SourceLocationId -> Word64
+ GHC.Stack.Profiler.Core: [getStringId] :: StringId -> Word64
+ GHC.Stack.Profiler.Core: [getThreadId] :: ThreadId -> Word64
+ GHC.Stack.Profiler.Core: [line] :: SourceLocation -> !Word32
+ GHC.Stack.Profiler.Core: [lookupOrInsertSourceLocation] :: SymbolTableWriter tbl -> tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)
+ GHC.Stack.Profiler.Core: [lookupOrInsertText] :: SymbolTableWriter tbl -> tbl -> Text -> (StringId, Bool, tbl)
+ GHC.Stack.Profiler.Core: [lookupSourceLocationId] :: SymbolTableReader -> SourceLocationId -> Maybe SourceLocation
+ GHC.Stack.Profiler.Core: [lookupStringId] :: SymbolTableReader -> StringId -> Maybe Text
+ GHC.Stack.Profiler.Core: [sourceLocationDefColumn] :: SourceLocationDef -> {-# UNPACK #-} !Word32
+ GHC.Stack.Profiler.Core: [sourceLocationDefFilename] :: SourceLocationDef -> {-# UNPACK #-} !StringId
+ GHC.Stack.Profiler.Core: [sourceLocationDefId] :: SourceLocationDef -> {-# UNPACK #-} !SourceLocationId
+ GHC.Stack.Profiler.Core: [sourceLocationDefRow] :: SourceLocationDef -> {-# UNPACK #-} !Word32
+ GHC.Stack.Profiler.Core: [stringDefBody] :: StringDef -> !Text
+ GHC.Stack.Profiler.Core: [stringDefId] :: StringDef -> !StringId
+ GHC.Stack.Profiler.Core: [writerTable] :: SymbolTableWriter tbl -> !tbl
+ GHC.Stack.Profiler.Core: data BinaryCallStackDecodeError
+ GHC.Stack.Profiler.Core: data CallStack
+ GHC.Stack.Profiler.Core: data CallStackChunk
+ GHC.Stack.Profiler.Core: data CallStackFrame
+ GHC.Stack.Profiler.Core: data IntMapTable
+ GHC.Stack.Profiler.Core: data MapTable
+ GHC.Stack.Profiler.Core: data Message
+ GHC.Stack.Profiler.Core: data MissingKeyError
+ GHC.Stack.Profiler.Core: data ProtocolVersionMismatch
+ GHC.Stack.Profiler.Core: data SourceLocation
+ GHC.Stack.Profiler.Core: data SourceLocationDef
+ GHC.Stack.Profiler.Core: data StackItem
+ GHC.Stack.Profiler.Core: data StringDef
+ GHC.Stack.Profiler.Core: data SymbolTableReader
+ GHC.Stack.Profiler.Core: data SymbolTableWriter tbl
+ GHC.Stack.Profiler.Core: dehydrateCallStack :: SymbolTableWriter table -> CallStack -> ([Message], SymbolTableWriter table)
+ GHC.Stack.Profiler.Core: deserializeEventlogMessage :: ByteString -> Either String Message
+ GHC.Stack.Profiler.Core: emptyIntMapTable :: IntMapTable
+ GHC.Stack.Profiler.Core: emptyMapSymbolTableWriter :: SymbolTableWriter MapTable
+ GHC.Stack.Profiler.Core: getKnownSourceLocations :: MapTable -> [(SourceLocationId, SourceLocation)]
+ GHC.Stack.Profiler.Core: getKnownStrings :: MapTable -> [(StringId, Text)]
+ GHC.Stack.Profiler.Core: hydrateEventlogCallStackMessage :: SymbolTableReader -> CallStackChunk -> (CallStack, [BinaryCallStackDecodeError])
+ GHC.Stack.Profiler.Core: insertSourceLocationMessage :: SourceLocationDef -> IntMapTable -> Either MissingKeyError IntMapTable
+ GHC.Stack.Profiler.Core: insertTextMessage :: StringDef -> IntMapTable -> IntMapTable
+ GHC.Stack.Profiler.Core: joinCallStackChunks :: NonEmpty CallStackChunk -> CallStackChunk
+ GHC.Stack.Profiler.Core: mkIntMapSymbolTableReader :: IntMapTable -> SymbolTableReader
+ GHC.Stack.Profiler.Core: newtype CapabilityId
+ GHC.Stack.Profiler.Core: newtype IpeId
+ GHC.Stack.Profiler.Core: newtype ProtocolVersion
+ GHC.Stack.Profiler.Core: newtype SourceLocationId
+ GHC.Stack.Profiler.Core: newtype StringId
+ GHC.Stack.Profiler.Core: newtype ThreadId
+ GHC.Stack.Profiler.Core: pattern MyProtocolVersion :: ProtocolVersion

Files

CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for ghc-stack-profiler-core +## 0.5.0.0 -- 2026-09-15++Major revision of the public API.++- Add a `ProtocolVersion` message to detect protocol incompatibility.+- Change the format of `ThreadId` in the protocol to `Word64`.+  This matches the `uint64_t` used by the RTS.+- Change the format of `CapabilityId` in memory to `Word32`.+  This matches the `uint32_t` used by the RTS.+ ## 0.4.0.0 -- 2026-07-14  Major version number changed to match `ghc-stack-profiler-speedscope`.
ghc-stack-profiler-core.cabal view
@@ -1,15 +1,18 @@ cabal-version: 3.8 name: ghc-stack-profiler-core-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: Thread sample types and serialisation logic for `ghc-stack-profiler`.+synopsis:+  The eventlog protocol used by ghc-stack-profiler+ description:-  Thread sample types and serialisation logic for `ghc-stack-profiler`.-  Defines the interface and serialisation logic to turn an RTS Callstack into a binary message suitable for the eventlog.+  The eventlog protocol used by @ghc-stack-profiler@. +  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:@@ -28,6 +31,7 @@     LambdaCase     NamedFieldPuns     NoImportQualifiedPost+    PatternSynonyms     ViewPatterns  library@@ -35,14 +39,18 @@     warnings, exts    exposed-modules:-    GHC.Stack.Profiler.Core.Eventlog-    GHC.Stack.Profiler.Core.SourceLocation-    GHC.Stack.Profiler.Core.SymbolTable-    GHC.Stack.Profiler.Core.ThreadSample-    GHC.Stack.Profiler.Core.Util+    GHC.Stack.Profiler.Core+    GHC.Stack.Profiler.Core.Internal +  other-modules:+    GHC.Stack.Profiler.Core.Internal.CallStack+    GHC.Stack.Profiler.Core.Internal.Dehydrate+    GHC.Stack.Profiler.Core.Internal.Eventlog+    GHC.Stack.Profiler.Core.Internal.Hydrate+    GHC.Stack.Profiler.Core.Internal.Util+   build-depends:-    base >=4.17 && <4.23,+    base >=4.17 && <5,     binary >=0.8.9.3 && <0.11,     bytestring >=0.11 && <0.13,     containers >=0.6.8 && <0.9,@@ -54,7 +62,7 @@    default-language: GHC2021 -test-suite ghc-stack-profiler-core-tests+test-suite ghc-stack-profiler-tests   import: warnings, exts   type: exitcode-stdio-1.0   hs-source-dirs: test@@ -65,7 +73,9 @@     bytestring,     ghc-stack-profiler-core,     tasty >=1.5.4 && <1.6,+    tasty-hunit,     tasty-quickcheck >=0.10 && <0.12,+    text >=2 && <2.2,    default-language: GHC2021 
+ src/GHC/Stack/Profiler/Core.hs view
@@ -0,0 +1,94 @@+module GHC.Stack.Profiler.Core (+  -- * Eventlog Protocol Messages+  CallStack (..),+  ThreadId (..),+  CapabilityId (..),+  StackItem (..),+  IpeId (..),+  SourceLocation (..),++  -- * Binary Eventlog Protocol Messages+  Message (..),+  ProtocolVersion (MkProtocolVersion, MyProtocolVersion),+  ProtocolVersionMismatch (..),+  CallStackChunk (..),+  StringDef (..),+  SourceLocationDef (..),+  CallStackFrame (..),+  StringId (..),+  SourceLocationId (..),++  -- * Decode++  -- ** Deserialise+  deserializeEventlogMessage,+  joinCallStackChunks,++  -- ** Hydrate+  hydrateEventlogCallStackMessage,+  BinaryCallStackDecodeError (..),++  -- *** Symbol Table+  SymbolTableReader (..),+  IntMapTable,+  mkIntMapSymbolTableReader,+  emptyIntMapTable,+  insertTextMessage,+  insertSourceLocationMessage,+  MissingKeyError (..),++  -- * Encode++  -- ** Serialise++  -- ** Dehydrate+  dehydrateCallStack,++  -- *** Symbol Table+  SymbolTableWriter (..),+  emptyMapSymbolTableWriter,+  MapTable,+  getKnownStrings,+  getKnownSourceLocations,+) where++import GHC.Stack.Profiler.Core.Internal.CallStack (+  CallStack (..),+  SourceLocation (..),+  StackItem (..),+ )+import GHC.Stack.Profiler.Core.Internal.Dehydrate (+  MapTable,+  SymbolTableWriter (..),+  dehydrateCallStack,+  emptyMapSymbolTableWriter,+  getKnownSourceLocations,+  getKnownStrings,+ )+import GHC.Stack.Profiler.Core.Internal.Eventlog (+  CallStackChunk (..),+  CallStackFrame (..),+  CapabilityId (..),+  IpeId (..),+  Message (..),+  ProtocolVersion (MkProtocolVersion, MyProtocolVersion),+  ProtocolVersionMismatch (..),+  SourceLocationDef (..),+  SourceLocationId (..),+  StringDef (..),+  StringId (..),+  ThreadId (..),+  deserializeEventlogMessage,+  joinCallStackChunks,+ )+import GHC.Stack.Profiler.Core.Internal.Hydrate (+  BinaryCallStackDecodeError (..),+  IntMapTable,+  MissingKeyError (..),+  SymbolTableReader (..),+  emptyIntMapTable,+  hydrateEventlogCallStackMessage,+  insertSourceLocationMessage,+  insertTextMessage,+  mkIntMapSymbolTableReader,+ )
− src/GHC/Stack/Profiler/Core/Eventlog.hs
@@ -1,298 +0,0 @@-module GHC.Stack.Profiler.Core.Eventlog (-  -- * Eventlgog Message types-  BinaryEventlogMessage (..),-  BinaryCallStackMessage (..),-  BinaryStringMessage (..),-  BinarySourceLocationMessage (..),-  BinaryStackItem (..),-  CapabilityId (..),-  StringId (..),-  incrementStringLocationId,-  SourceLocationId (..),-  incrementSourceLocationId,-  IpeId (..),--  -- * Eventlog constants-  callStackFinalMessageTag,-  callStackPartialMessageTag,-  callStackStringMessageTag,-  callStackSourceLocationMessageTag,-  callStackMessageTags,-  callStackSizeLimit,-  callStackSizeLimit_,-  byteSizeOf,-  eventlogBufferSize,-  stringLengthLimit,-) where--import Control.Monad (replicateM)-import Data.Binary-import Data.Coerce (coerce)-import qualified Data.List as List-import Data.Text (Text)-import GHC.Generics--import GHC.Stack.Profiler.Core.Util---- ------------------------------------------------------------------------------- Eventlog Messages--- -------------------------------------------------------------------------------- | Efficient serialisation format of the GHC RTS callstack.------ Message format:------ @--- MESSAGE---  := FF CA (stack: STACK>---   | FF CB (prefix: STACK>---   | FF CC (stringId: 'Word64') (string: CStringLen)---   | FF CD (srcLocId: 'Word64') (row: 'Word32') (col: 'Word32') (functionId: 'Word64') (filename: 'Word64')------ STACK---  := (capability: 'Word32') (threadId: 'Word32') (length: 'Int16') (ENTRY)+---   # check that length < (2^16-8) / 9------ ENTRY---  := 01 (ipe: 'Word64')---   | 02 (stringId: 'Word64')---   | 03 (stringId: 'Word64') (srcLocId: 'Word64')------ CStringLen---   := (length: 'Int16') (Char)+---    # check that length < 2^16-8--- @-data BinaryEventlogMessage-  = CallStackFinal !BinaryCallStackMessage-  | CallStackChunk !BinaryCallStackMessage-  | StringDef !BinaryStringMessage-  | SourceLocationDef !BinarySourceLocationMessage-  deriving (Eq, Ord, Show, Read, Generic)--data BinaryCallStackMessage = MkBinaryCallStackMessage-  { binaryCallThreadId :: !Word64-  , binaryCallCapabilityId :: !CapabilityId-  , binaryCallStack :: ![BinaryStackItem]-  }-  deriving (Eq, Ord, Show, Read, Generic)--data BinaryStringMessage = MkBinaryStringMessage-  { binaryStringMessageId :: !StringId-  , binaryStringMessage :: !Text-  }-  deriving (Eq, Ord, Show, Read, Generic)--data BinarySourceLocationMessage = MkBinarySourceLocationMessage-  { binarySourceLocationMessageId :: {-# UNPACK #-} !SourceLocationId-  , binarySourceLocationRow :: {-# UNPACK #-} !Word32-  , binarySourceLocationColumn :: {-# UNPACK #-} !Word32-  , binarySourceLocationFilename :: {-# UNPACK #-} !StringId-  }-  deriving (Eq, Ord, Show, Read, Generic)--data BinaryStackItem-  = BinaryIpe {-# UNPACK #-} !IpeId-  | BinaryMessage-      {-# UNPACK #-} !StringId-      {-# UNPACK #-} !(Maybe SourceLocationId)-  deriving (Eq, Ord, Show, Read, Generic)---- | Simple newtype for the ID of a capability.-newtype CapabilityId-  = MkCapabilityId-  { getCapabilityId :: Word64-  }-  deriving (Show, Eq, Ord, Read, Generic)--newtype StringId = MkStringId-  { getStringId :: Word64-  }-  deriving (Eq, Ord, Show, Read, Generic)--incrementStringLocationId :: StringId -> StringId-incrementStringLocationId (MkStringId sid) = MkStringId (sid + 1)--newtype SourceLocationId = MkSourceLocationId-  { getSourceLocationId :: Word64-  }-  deriving (Eq, Ord, Show, Read, Generic)--incrementSourceLocationId :: SourceLocationId -> SourceLocationId-incrementSourceLocationId (MkSourceLocationId slId) = MkSourceLocationId (slId + 1)--newtype IpeId = MkIpeId-  { getIpeId :: Word64-  }-  deriving (Eq, Ord, Show, Read, Generic)---- ------------------------------------------------------------------------------- Binary instances--- ------------------------------------------------------------------------------callStackFinalMessageTag :: Word16-callStackFinalMessageTag = 0xFFCA--callStackPartialMessageTag :: Word16-callStackPartialMessageTag = 0xFFCB--callStackStringMessageTag :: Word16-callStackStringMessageTag = 0xFFCC--callStackSourceLocationMessageTag :: Word16-callStackSourceLocationMessageTag = 0xFFCD--callStackMessageTags :: [Word16]-callStackMessageTags =-  [ callStackFinalMessageTag-  , callStackPartialMessageTag-  , callStackStringMessageTag-  , callStackSourceLocationMessageTag-  ]---- | Each message in the eventlog can be at most 2^16 bytes-eventlogBufferSize :: Word64-eventlogBufferSize = (2 :: Word64) ^ (16 :: Word64)---- | Size limit of strings that can occur in the eventlog.-stringLengthLimit :: Word16-stringLengthLimit =-  word64ToWord16 $-    eventlogBufferSize-      - 2 {- 0xFFCC -}-      - 8 {- Word64 of 'StringId' -}-      - 2 {- Word16 for the length of the string to serialise -}---- | The limit of stack items that can go in one eventlog message in bytes.-callStackSizeLimit :: Word16-callStackSizeLimit =-  callStackSizeLimit_ eventlogBufferSize---- | The limit of stack items that can go in one eventlog message in bytes--- with configurable the eventlog message size.-callStackSizeLimit_ :: Word64 -> Word16-callStackSizeLimit_ eventlogSize =-  word64ToWord16-    ( eventlogSize-        - 2 {- 0xFFCA or 0xFFCB -}-        - 4 {- Word32 of 'CapabilityId' -}-        - 4 {- Word32 of 'ThreadId' -}-        - 2 {- Word16 for the length of stack entry -}-    )---- | Size in bytes of the given 'BinaryStackItem'-byteSizeOf :: BinaryStackItem -> Word16-byteSizeOf = \case-  BinaryIpe{} -> 1 + 8 {- 0x1 + Word64 of 'IpeId' -}-  BinaryMessage _ Nothing -> 1 + 8 {- 0x2 + Word64 of 'StringId' -}-  BinaryMessage _ (Just _) -> 1 + 8 + 8 {- 0x3 + Word64 of 'StringId' + Word64 of 'SourceLocationId' -}--instance Binary BinaryEventlogMessage where-  put = \case-    CallStackFinal msg ->-      putWithTag callStackFinalMessageTag msg-    CallStackChunk msg ->-      putWithTag callStackPartialMessageTag msg-    StringDef msg ->-      putWithTag callStackStringMessageTag msg-    SourceLocationDef msg ->-      putWithTag callStackSourceLocationMessageTag msg-   where-    putWithTag t msg = putWord16 t >> put msg--  get = do-    tag <- getWord16-    case tag of-      _-        | tag == callStackFinalMessageTag ->-            CallStackFinal <$> get-        | tag == callStackPartialMessageTag ->-            CallStackChunk <$> get-        | tag == callStackStringMessageTag ->-            StringDef <$> get-        | tag == callStackSourceLocationMessageTag ->-            SourceLocationDef <$> get-        | otherwise ->-            fail $-              "BinaryEventlogMessage.get: Unknown tag expected one of "-                ++ tags-                ++ " but got "-                ++ showAsHex tag-   where-    tags = List.intercalate ", " $ map showAsHex callStackMessageTags--instance Binary BinaryCallStackMessage where-  put msg = do-    putWord32 $ word64ToWord32 $ getCapabilityId $ binaryCallCapabilityId msg-    putWord32 $ word64ToWord32 $ binaryCallThreadId msg-    let-      items = binaryCallStack msg-    putWord16 $ intToWord16 $ length items-    mapM_ put items--  get = do-    capId <- getWord32-    tid <- getWord32-    len <- getWord16-    items <- replicateM (word16ToInt len) get-    pure-      MkBinaryCallStackMessage-        { binaryCallThreadId = word32ToWord64 tid-        , binaryCallCapabilityId = MkCapabilityId $ word32ToWord64 capId-        , binaryCallStack = items-        }--instance Binary BinaryStackItem where-  put = \case-    BinaryIpe ipeId -> do-      putWord8 0x1-      put ipeId-    BinaryMessage sid Nothing -> do-      putWord8 0x2-      put sid-    BinaryMessage sid (Just lid) -> do-      putWord8 0x3-      put sid-      put lid--  get = do-    getWord8 >>= \case-      0x1 -> BinaryIpe <$> get-      0x2 -> BinaryMessage <$> get <*> pure Nothing-      0x3 -> BinaryMessage <$> get <*> (Just <$> get)-      n -> fail $ "StackItem: Unexpected tag byte encounter: " <> show n--instance Binary BinarySourceLocationMessage where-  put msg = do-    put $ binarySourceLocationMessageId msg-    putWord32 (binarySourceLocationRow msg)-    putWord32 (binarySourceLocationColumn msg)-    put (binarySourceLocationFilename msg)--  get = do-    MkBinarySourceLocationMessage-      <$> get-      <*> getWord32-      <*> getWord32-      <*> get--instance Binary BinaryStringMessage where-  put msg = do-    put $ binaryStringMessageId msg-    putTextWord16 stringLengthLimit (binaryStringMessage msg)--  get = do-    MkBinaryStringMessage-      <$> get-      <*> getTextWord16--instance Binary SourceLocationId where-  put = putWord64 . coerce-  get = coerce getWord64--instance Binary StringId where-  put = putWord64 . coerce-  get = coerce getWord64--instance Binary IpeId where-  put = putWord64 . coerce-  get = coerce getWord64
+ src/GHC/Stack/Profiler/Core/Internal.hs view
@@ -0,0 +1,53 @@+{-# OPTIONS_HADDOCK hide #-}++module GHC.Stack.Profiler.Core.Internal (+  CallStackFrameTag (..),+  MessageTag (..),+  ShortText (..),+  callStackFrameMaxSize,+  callStackFrameSize,+  callStackFrameTagSize,+  callStackMaxLen,+  callStackMaxLen',+  capabilityIdSize,+  ipeIdSize,+  messageMaxSize,+  messageMinSize,+  messageTagSize,+  sourceLocationDefSize,+  sourceLocationIdSize,+  stringDefBodyMaxSize,+  stringIdSize,+  threadIdSize,+  toShortText,+  truncateTextToByteLimit,+  chunkCallStack,+  chunkCallStack_,+) where++import GHC.Stack.Profiler.Core.Internal.Dehydrate (+  chunkCallStack,+  chunkCallStack_,+ )+import GHC.Stack.Profiler.Core.Internal.Eventlog (+  CallStackFrameTag (..),+  MessageTag (..),+  ShortText (..),+  callStackFrameMaxSize,+  callStackFrameSize,+  callStackFrameTagSize,+  callStackMaxLen,+  callStackMaxLen',+  capabilityIdSize,+  ipeIdSize,+  messageMaxSize,+  messageMinSize,+  messageTagSize,+  sourceLocationDefSize,+  sourceLocationIdSize,+  stringDefBodyMaxSize,+  stringIdSize,+  threadIdSize,+  toShortText,+  truncateTextToByteLimit,+ )
+ src/GHC/Stack/Profiler/Core/Internal/CallStack.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}++module GHC.Stack.Profiler.Core.Internal.CallStack (+  CallStack (..),+  StackItem (..),+  SourceLocation (..),+) where++import Data.Text (Text)+import Data.Word (Word32)+import GHC.Generics+import GHC.Stack.Profiler.Core.Internal.Eventlog++-- ----------------------------------------------------------------------------+-- Decoded RTS CallStack+-- ----------------------------------------------------------------------------++-- | A decoded rts callstack that can be serialised to the EventLog.+data CallStack = MkCallStack+  { callThreadId :: !ThreadId+  , callCapabilityId :: !CapabilityId+  , callStack :: [StackItem]+  }+  deriving (Eq, Ord, Show, Generic)++data StackItem+  = IpeId !IpeId+  | UserAnnotation !String !(Maybe SourceLocation)+  deriving (Eq, Ord, Show, Generic)++-- | A Haskell source location.+data SourceLocation = MkSourceLocation+  { line :: !Word32+  , column :: !Word32+  , fileName :: !Text+  }+  deriving (Eq, Ord, Show, Generic)
+ src/GHC/Stack/Profiler/Core/Internal/Dehydrate.hs view
@@ -0,0 +1,299 @@+module GHC.Stack.Profiler.Core.Internal.Dehydrate where++import Control.Monad (when)+import Control.Monad.Trans.State.Strict (State, runState)+import qualified Control.Monad.Trans.State.Strict as State+import qualified Data.List as List+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Tuple as Tuple+import GHC.Generics+import GHC.Stack.Profiler.Core.Internal.CallStack+import GHC.Stack.Profiler.Core.Internal.Eventlog++-- | Generic implementation to turn 'CallStack' into '[Message]'.+--+-- Replaces already encountered text or source location information with unique ids.+-- If new text or source location messages are encountered, they are inserted into+-- the 'SymbolTableWriter'.+--+-- All new string values and source location messages are before 'CallStackChunk' and+-- 'CallStackFinal' messages.+-- For the result list @r :: ['Message']@, the following holds:+--+-- * 'StringDef' messages are the first elements in @r@. There might not be any.+-- * 'SourceLocationDef' are after 'StringDef' messages and before any 'CallStackChunk' or+--    'CallStackFinal' messages. There might not be any such messages.+-- * Then 'CallStackChunk' follow if there are any.+-- * The last message is always a 'CallStackFinal' message and it occurs exactly once in @r@.+dehydrateCallStack ::+  forall table.+  SymbolTableWriter table ->+  CallStack ->+  ([Message], SymbolTableWriter table)+dehydrateCallStack msgTbl0 msg =+  let+    (stackItems, finalState) =+      runWithEncodingState+        (newEncodingState msgTbl0)+        (mapM go (callStack msg))++    stringDefs =+      map StringDef $ stringMessages finalState++    sourceLocDefs =+      map SourceLocationDef $ sourceLocMessages finalState++    stackMsgChunks =+      chunkCallStack+        MkCallStackChunk+          { callStackChunkThreadId = callThreadId msg+          , callStackChunkCapabilityId = callCapabilityId msg+          , callStackChunk = stackItems+          }+  in+    ( stringDefs ++ sourceLocDefs ++ stackMsgChunks+    , symbolTableWriter finalState+    )+ where+  go :: StackItem -> State (EncodingState tbl) CallStackFrame+  go = \case+    IpeId ipeId ->+      pure $ CallStackFrameIpe ipeId+    UserAnnotation s mSrcLoc -> do+      srcLocId <- case mSrcLoc of+        Nothing -> pure Nothing+        Just srcLoc -> Just <$> lookupSourceLocationMessage srcLoc+      CallStackFrameAnn <$> lookupTextMessage (Text.pack s) <*> pure srcLocId++-- | Chunk the 'callStackChunk' of the 'CallStackChunk' by the given 'Int'.+-- If there are no items in 'CallStackChunk', then a singleton list is returned containing+-- the original element.+--+-- Post-condition for the result @r@:+--+-- * all elements in @init r @ are 'CallStackChunk's+-- * the element returned by @last r@ is a 'CallStackFinal' Message.+--+-- The resulting 'CallStackChunk' are in reverse order and so are the chunks themselves.+--+-- This means, for a stack @[1,2,3,4,5,6]@ and an assumed chunk size of 2,+-- we produce @[[6,5],[4,3],[2,1]]@.+chunkCallStack :: CallStackChunk -> [Message]+chunkCallStack = chunkCallStack_ callStackMaxLen++-- | Same as 'chunkCallStack', but allows to set the chunking size in bytes.+chunkCallStack_ :: Int -> CallStackChunk -> [Message]+chunkCallStack_ chunkLimit msg0 =+  let+    items = callStackChunk msg0+    chunked =+      let+        go (!size, curChunk, restChunk) item =+          let+            !bytes = callStackFrameSize item+          in+            if (size + bytes) < chunkLimit+              then (size + bytes, item : curChunk, restChunk)+              else (bytes, [item], curChunk : restChunk)+        (_, lastChunk, initChunk) = List.foldl' go (0, [], []) items+      in+        lastChunk : initChunk+  in+    mkEventlogMessages chunked+ where+  mkCallStack chunk =+    MkCallStackChunk+      { callStackChunkThreadId = callStackChunkThreadId msg0+      , callStackChunkCapabilityId = callStackChunkCapabilityId msg0+      , callStackChunk = chunk+      }++  mkEventlogMessages :: [[CallStackFrame]] -> [Message]+  mkEventlogMessages [] =+    -- If there are no chunks, we simply return the original message+    [ CallStackFinal msg0+    ]+  mkEventlogMessages [chunk] =+    [ CallStackFinal $ mkCallStack chunk+    ]+  mkEventlogMessages (chunk : chunks) =+    CallStackChunk (mkCallStack chunk) : mkEventlogMessages chunks++-- ----------------------------------------------------------------------------+-- Helper types and functions to implement the conversion to the binary+-- representation.+-- ----------------------------------------------------------------------------++data EncodingState tbl = MkEncodingState+  { symbolTableWriter :: !(SymbolTableWriter tbl)+  , stringMessages :: ![StringDef]+  , sourceLocMessages :: ![SourceLocationDef]+  }+  deriving (Generic)++runWithEncodingState :: EncodingState tbl -> State (EncodingState tbl) a -> (a, EncodingState tbl)+runWithEncodingState encodingState encoder =+  runState encoder encodingState++newEncodingState :: SymbolTableWriter tbl -> EncodingState tbl+newEncodingState msgTbl0 =+  MkEncodingState+    { symbolTableWriter = msgTbl0+    , stringMessages = []+    , sourceLocMessages = []+    }++setSymbolTableWriter :: tbl -> State.State (EncodingState tbl) ()+setSymbolTableWriter tbl = State.modify' (\st -> st{symbolTableWriter = (symbolTableWriter st){writerTable = tbl}})++addStringMessage :: StringDef -> State.State (EncodingState tbl) ()+addStringMessage msg = State.modify' (\st -> st{stringMessages = msg : stringMessages st})++addSourceLocationMessage :: SourceLocationDef -> State.State (EncodingState tbl) ()+addSourceLocationMessage msg = State.modify' (\st -> st{sourceLocMessages = msg : sourceLocMessages st})++lookupOrInsertTextMessage :: forall tbl. Text -> State (EncodingState tbl) (StringId, Bool)+lookupOrInsertTextMessage s = do+  tbl <- State.gets symbolTableWriter+  let+    (sid, new, tbl1) = lookupOrInsertText tbl (writerTable tbl) s+  setSymbolTableWriter tbl1+  pure (sid, new)++lookupOrInsertSrcLocMessage :: forall tbl. SourceLocation -> State (EncodingState tbl) (SourceLocationId, Bool)+lookupOrInsertSrcLocMessage s = do+  tbl <- State.gets symbolTableWriter+  let+    (sid, new, tbl1) = lookupOrInsertSourceLocation tbl (writerTable tbl) s+  setSymbolTableWriter tbl1+  pure (sid, new)++lookupTextMessage :: forall tbl. Text -> State (EncodingState tbl) StringId+lookupTextMessage s = do+  (sid, new) <- lookupOrInsertTextMessage s+  when new $+    addStringMessage $+      MkStringDef sid s+  pure sid++lookupSourceLocationMessage :: forall tbl. SourceLocation -> State (EncodingState tbl) SourceLocationId+lookupSourceLocationMessage s = do+  (sid, new) <- lookupOrInsertSrcLocMessage s+  when new $ do+    fileId <- lookupTextMessage $ fileName s+    addSourceLocationMessage $+      MkSourceLocationDef+        { sourceLocationDefId = sid+        , sourceLocationDefRow = line s+        , sourceLocationDefColumn = column s+        , sourceLocationDefFilename = fileId+        }+  pure sid++-- | Implementation agnostic symbol table supposed to be used to deduplicate symbols+-- in 'CallStack'.+--+-- When transforming 'CallStack' to ['Message'] we replace some+-- symbols with identifiers.+-- In particular arbitrary length symbols, such as 'Text's and 'SourceLocation's.+-- As these symbols are discovered while encoding the callstack, the 'SymbolTableWriter'+-- needs to be extended, which is why we thread the 'tbl' parameter through the+-- lookup or insertion operations.+data SymbolTableWriter tbl = MkSymbolTableWriter+  { writerTable :: !tbl+  -- ^ Symbol table for symbols we replace with unique identifiers.+  , lookupOrInsertText :: tbl -> Text -> (StringId, Bool, tbl)+  -- ^ Lookup up the given 'Text' in the 'tbl' Symbol table.+  -- If the 'Text' can't be found, we insert it into the table and generate a+  -- new 'StringId.+  -- Returns 'True', if the given 'Text' was inserted and 'False' otherwise.+  , lookupOrInsertSourceLocation :: tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)+  -- ^ Lookup up the given 'SourceLocation' in the 'tbl' Symbol table.+  -- If the 'SourceLocation' can't be found, we insert it into the table and generate a+  -- new 'SourceLocationId.+  -- Returns 'True', if the given 'Text' was inserted and 'False' otherwise.+  }+  deriving (Generic)++data MapTable = MkMapTable+  { stringTable :: !(Map Text StringId)+  , srcLocTable :: !(Map SourceLocation SourceLocationId)+  , stringUniqueSupply :: {-# UNPACK #-} !StringId+  , srcLocUniqueSupply :: {-# UNPACK #-} !SourceLocationId+  }+  deriving (Show, Eq, Ord, Generic)++{-# INLINEABLE emptyMapSymbolTableWriter #-}+emptyMapSymbolTableWriter :: SymbolTableWriter MapTable+emptyMapSymbolTableWriter =+  MkSymbolTableWriter+    { writerTable =+        MkMapTable+          { stringTable = Map.empty+          , srcLocTable = Map.empty+          , stringUniqueSupply = MkStringId 0+          , srcLocUniqueSupply = MkSourceLocationId 0+          }+    , lookupOrInsertText = alterStringMap+    , lookupOrInsertSourceLocation = alterSrcLocTable+    }+ where+  nextSrcLocUnique tbl =+    ( srcLocUniqueSupply tbl+    , tbl+        { srcLocUniqueSupply =+            nextSourceLocationId $ srcLocUniqueSupply tbl+        }+    )++  nextStringUnique tbl =+    ( stringUniqueSupply tbl+    , tbl+        { stringUniqueSupply =+            nextStringId $ stringUniqueSupply tbl+        }+    )++  updateEntry tbl0 nextKey Nothing =+    let+      (sid, tbl) = nextKey tbl0+    in+      ((sid, True, tbl), Just sid)+  updateEntry tbl _ (Just val) =+    ((val, False, tbl), Just val)++  swapAround set ((sid, new, tbl), hm) =+    (sid, new, set tbl hm)++  alterStringMap = \tbl str ->+    swapAround setStringTable $+      Map.alterF (updateEntry tbl nextStringUnique) str (stringTable tbl)++  alterSrcLocTable = \tbl srcLoc ->+    swapAround setSourceLocationTable $+      Map.alterF (updateEntry tbl nextSrcLocUnique) srcLoc (srcLocTable tbl)++setSourceLocationTable :: MapTable -> Map SourceLocation SourceLocationId -> MapTable+setSourceLocationTable tbl hm =+  tbl+    { srcLocTable = hm+    }++setStringTable :: MapTable -> Map Text StringId -> MapTable+setStringTable tbl hm =+  tbl+    { stringTable = hm+    }++getKnownStrings :: MapTable -> [(StringId, Text)]+{-# INLINEABLE getKnownStrings #-}+getKnownStrings table =+  List.map Tuple.swap $ Map.assocs (stringTable table)++getKnownSourceLocations :: MapTable -> [(SourceLocationId, SourceLocation)]+{-# INLINEABLE getKnownSourceLocations #-}+getKnownSourceLocations table =+  List.map Tuple.swap $ Map.assocs (srcLocTable table)
+ src/GHC/Stack/Profiler/Core/Internal/Eventlog.hs view
@@ -0,0 +1,598 @@+{-# LANGUAGE MultiWayIf #-}++module GHC.Stack.Profiler.Core.Internal.Eventlog (+  -- * Eventlog Message types+  Message (..),+  ProtocolVersion (MkProtocolVersion, MyProtocolVersion),+  ProtocolVersionMismatch (..),+  CallStackChunk (..),+  StringDef (..),+  SourceLocationDef (..),+  CallStackFrame (..),+  ThreadId (..),+  CapabilityId (..),+  StringId (..),+  nextStringId,+  SourceLocationId (..),+  nextSourceLocationId,+  IpeId (..),+  deserializeEventlogMessage,+  joinCallStackChunks,++  -- * Low-level API+  MessageTag (..),+  messageTagSize,+  CallStackFrameTag (..),+  ipeIdSize,+  stringIdSize,+  sourceLocationIdSize,+  messageMaxSize,+  messageMinSize,+  stringDefBodyMaxSize,+  sourceLocationDefSize,+  ShortText (..),+  toShortText,+  truncateTextToByteLimit,+  callStackFrameTagSize,+  callStackMaxLen,+  callStackMaxLen',+  callStackFrameSize,+  callStackFrameMaxSize,+  capabilityIdSize,+  threadIdSize,+) where++import Control.Exception (Exception (..), assert, throw)+import Control.Monad (replicateM, when)+import Data.Binary+import Data.Binary.Get (getByteString, runGetOrFail)+import Data.Binary.Put (putByteString)+import qualified Data.ByteString.Lazy as LBS+import Data.Coerce (coerce)+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Foreign as TF+import GHC.Generics+import GHC.Stack.Profiler.Core.Internal.Util+import Text.Printf (printf)++-- ----------------------------------------------------------------------------+-- Eventlog Messages+-- ----------------------------------------------------------------------------++-- | Efficient serialisation format of the GHC RTS callstack.+--+-- Message format:+--+-- @+-- 'Message'+--  := FF CA (stackFinal: 'CallStackChunk')+--   | FF CB (stackChunk: 'CallStackChunk')+--   | FF CC (stringDef: 'StringDef')+--   | FF CD (sourceLocationDef: 'SourceLocationDef')+--+-- 'CallStackChunk'+--  := (capabilityId: 'Word32') (threadId: 'Word32') (callStackLen: 'Word16') (callStack: 'CallStackFrame'{callStackLen})+--  -- NOTE: callStackLen must be smaller than (2^16 - 8) / 9+--+-- 'CallStackFrame'+--  := 01 (ipe: 'Word64')+--   | 02 (stringId: 'Word64')+--   | 03 (stringId: 'Word64') (sourceLocationId: 'Word64')+--+-- 'StringDef'+--  := (stringId: 'Word64') (stringLen: 'Word16') (string: 'Char'{stringLen})+--  -- NOTE: stringLen must be smaller than 2^16 - 8+--+-- 'SourceLocationDef'+--  := (sourceLocationId: 'Word64') (row: 'Word32') (column: 'Word32') (functionId: 'Word64') (filename: 'Word64')+-- @+data Message+  = -- | The version of the protocol.+    ProtocolVersion !ProtocolVersion+  | -- | A chunk of the call-stack, indicated by the prefix @FF CA@.+    --+    --   This variant indicates that no further 'CallStackChunk' or 'CallStackFinal' will follow.+    CallStackFinal !CallStackChunk+  | -- | A chunk of the call-stack, indicated by the prefix @FF CB@.+    --+    --   This variant indicates that another 'CallStackChunk' or 'CallStackFinal' will follow.+    CallStackChunk !CallStackChunk+  | -- | A string definition, indicated by the prefix @FF CC@.+    --+    --   This messages associates the string ID @stringId@ with the string+    --   @strLen@, for future use in call-stack messages and source location+    --   definitions.+    StringDef !StringDef+  | -- | A source location definition, indicated by the prefix @FF CD@.+    --+    --   This message associates the source location ID @srcLocId@ with the+    --   source location specified by @row@, @col@, @functionId@, and+    --   @filename@, for future use in call-stack messages.+    SourceLocationDef !SourceLocationDef+  deriving (Eq, Ord, Show, Read, Generic)++-- | The version of the protocol implemented by the `Message` type.+newtype ProtocolVersion+  = MkProtocolVersion {getProtocolVersion :: Word8}+  deriving (Eq, Ord, Show, Read, Generic)+  deriving newtype (Binary)++-- | The version of the protocol implemented by this package.+--+--   __Note:__ This should always match the super-major version number of the+--             @ghc-stack-profiler-core@ package. If the package version is+--             @A.B.C.D@, the protocol version is @A@.+pattern MyProtocolVersion :: ProtocolVersion+pattern MyProtocolVersion = MkProtocolVersion 0++data ProtocolVersionMismatch+  = MkProtocolVersionMismatch+  { expectProtocolVersion :: !ProtocolVersion+  , actualProtocolVersion :: !ProtocolVersion+  }+  deriving (Eq, Ord, Show, Read, Generic)++instance Exception ProtocolVersionMismatch where+  displayException :: ProtocolVersionMismatch -> String+  displayException e =+    let+      expect = getProtocolVersion (expectProtocolVersion e)+      actual = getProtocolVersion (actualProtocolVersion e)+    in+      concat+        [ "The protocol version of the input ("+        , show actual+        , ") does not match the version implemented by this package ("+        , show expect+        , ")."+        ]++data CallStackChunk = MkCallStackChunk+  { callStackChunkThreadId :: !ThreadId+  , callStackChunkCapabilityId :: !CapabilityId+  , callStackChunk :: ![CallStackFrame]+  }+  deriving (Eq, Ord, Show, Read, Generic)++data StringDef = MkStringDef+  { stringDefId :: !StringId+  , stringDefBody :: !Text+  }+  deriving (Eq, Ord, Show, Read, Generic)++data SourceLocationDef = MkSourceLocationDef+  { sourceLocationDefId :: {-# UNPACK #-} !SourceLocationId+  , sourceLocationDefRow :: {-# UNPACK #-} !Word32+  , sourceLocationDefColumn :: {-# UNPACK #-} !Word32+  , sourceLocationDefFilename :: {-# UNPACK #-} !StringId+  }+  deriving (Eq, Ord, Show, Read, Generic)++data CallStackFrame+  = CallStackFrameIpe {-# UNPACK #-} !IpeId+  | CallStackFrameAnn {-# UNPACK #-} !StringId {-# UNPACK #-} !(Maybe SourceLocationId)+  deriving (Eq, Ord, Show, Read, Generic)++-- | The ID of a thread.+newtype ThreadId+  = MkThreadId+  { getThreadId :: Word64+  }+  deriving (Show, Eq, Ord, Read, Generic)+  deriving newtype (Binary)++-- | The ID of a capability.+newtype CapabilityId+  = MkCapabilityId+  { getCapabilityId :: Word32+  }+  deriving (Show, Eq, Ord, Read, Generic)+  deriving newtype (Binary)++newtype StringId = MkStringId+  { getStringId :: Word64+  }+  deriving (Eq, Ord, Show, Read, Generic)++nextStringId :: StringId -> StringId+nextStringId (MkStringId sid) = MkStringId (sid + 1)++newtype SourceLocationId = MkSourceLocationId+  { getSourceLocationId :: Word64+  }+  deriving (Eq, Ord, Show, Read, Generic)++nextSourceLocationId :: SourceLocationId -> SourceLocationId+nextSourceLocationId (MkSourceLocationId slId) = MkSourceLocationId (slId + 1)++newtype IpeId = MkIpeId+  { getIpeId :: Word64+  }+  deriving (Eq, Ord, Show, Read, Generic)++-- | Deserialise a `Message`.+--+--   __Warning:__ This function may throw `ProtocolVersionMismatch`.+deserializeEventlogMessage :: LBS.ByteString -> Either String Message+deserializeEventlogMessage msg = case runGetOrFail get msg of+  Left (_, _, errMsg) -> Left errMsg+  Right (_, _, callStackMessage) -> Right callStackMessage++-- | Combine all 'CallStackChunk's into a single 'CallStackChunk'.+-- We assume that all 'CallStackChunk' only differ in their 'callStackChunk' values.+--+-- 'joinCallStackChunks' is the conceptually inverse of 'chunkCallStack'.+joinCallStackChunks :: NonEmpty CallStackChunk -> CallStackChunk+joinCallStackChunks msgs =+  MkCallStackChunk+    { callStackChunkThreadId = callStackChunkThreadId $ NonEmpty.head msgs+    , callStackChunkCapabilityId = callStackChunkCapabilityId $ NonEmpty.head msgs+    , callStackChunk = concatMap (reverse . callStackChunk) . reverse $ NonEmpty.toList msgs+    }++-------------------------------------------------------------------------------+-- Binary instances+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Message Tags++data MessageTag+  = ProtocolVersionTag+  | CallStackFinalTag+  | CallStackChunkTag+  | StringDefTag+  | SourceLocationDefTag+  deriving (Bounded, Enum, Eq, Show)++messageTagSize :: Int+messageTagSize = 2++messageTagToWord16 :: MessageTag -> Word16+messageTagToWord16 = \case+  ProtocolVersionTag -> 0xFFC0+  CallStackFinalTag -> 0xFFCA+  CallStackChunkTag -> 0xFFCB+  StringDefTag -> 0xFFCC+  SourceLocationDefTag -> 0xFFCD++instance Binary MessageTag where+  put :: MessageTag -> Put+  put = putWord16 . messageTagToWord16++  get :: Get MessageTag+  get =+    getWord16 >>= \case+      0xFFC0 -> pure ProtocolVersionTag+      0xFFCA -> pure CallStackFinalTag+      0xFFCB -> pure CallStackChunkTag+      0xFFCC -> pure StringDefTag+      0xFFCD -> pure SourceLocationDefTag+      badTag ->+        fail $+          printf+            "Found invalid message tag %s. Expected one of %s."+            (showAsHex badTag)+            (List.intercalate ", " messageTags)+   where+    messageTags :: [String]+    messageTags = [showAsHex (messageTagToWord16 tag) | tag <- [minBound .. maxBound]]++-------------------------------------------------------------------------------+-- Messages++-- | __Warning:__ `get` may throw `ProtocolVersionMismatch`.+instance Binary Message where+  put :: Message -> Put+  put = \case+    ProtocolVersion protocolVersion -> do+      put ProtocolVersionTag+      put protocolVersion+    CallStackFinal callStackChunk -> do+      put CallStackFinalTag+      put callStackChunk+    CallStackChunk callStackChunk -> do+      put CallStackChunkTag+      put callStackChunk+    StringDef stringDef -> do+      put StringDefTag+      put stringDef+    SourceLocationDef sourceLocationDef -> do+      put SourceLocationDefTag+      put sourceLocationDef++  get :: Get Message+  get =+    get >>= \case+      ProtocolVersionTag -> do+        protocolVersion <- get+        when (protocolVersion /= MyProtocolVersion) $+          throw $+            MkProtocolVersionMismatch MyProtocolVersion protocolVersion+        pure $ ProtocolVersion protocolVersion+      CallStackFinalTag ->+        CallStackFinal <$> get+      CallStackChunkTag ->+        CallStackChunk <$> get+      StringDefTag ->+        StringDef <$> get+      SourceLocationDefTag ->+        SourceLocationDef <$> get++messageMaxSize :: Int+messageMaxSize =+  fromIntegral (maxBound @Word16)++messageMinSize :: Int+messageMinSize =+  messageTagSize+    + minimum+      [ {- CallStackChunk/CallStackFinal -}+        capabilityIdSize + threadIdSize + callStackLenSize+      , {- StringDef -}+        stringIdSize + stringDefBodyLenSize+      , {- SourceLocationDef -}+        sourceLocationDefSize+      ]++-------------------------------------------------------------------------------+-- CallStackChunks++instance Binary CallStackChunk where+  put :: CallStackChunk -> Put+  put MkCallStackChunk{callStackChunkCapabilityId, callStackChunkThreadId, callStackChunk} = do+    put callStackChunkCapabilityId+    put callStackChunkThreadId+    let+      callStackChunkLength = length callStackChunk+    putWord16 $ fromIntegral callStackChunkLength+    mapM_ put callStackChunk++  get :: Get CallStackChunk+  get = do+    callStackChunkCapabilityId <- get+    callStackChunkThreadId <- get+    callStackChunkLength <- fromIntegral <$> getWord16+    callStackChunk <- replicateM callStackChunkLength get+    pure MkCallStackChunk{callStackChunkThreadId, callStackChunkCapabilityId, callStackChunk}++-------------------------------------------------------------------------------+-- CallStackFrameTags++data CallStackFrameTag+  = CallStackFrameIpeTag+  | CallStackFrameAnnWithNothingTag+  | CallStackFrameAnnWithJustSourceLocationTag+  deriving (Bounded, Enum, Eq, Show)++callStackFrameTagSize :: Int+callStackFrameTagSize = 1++callStackFrameTagToWord8 :: CallStackFrameTag -> Word8+callStackFrameTagToWord8 = \case+  CallStackFrameIpeTag -> 0x1+  CallStackFrameAnnWithNothingTag -> 0x2+  CallStackFrameAnnWithJustSourceLocationTag -> 0x3++instance Binary CallStackFrameTag where+  put :: CallStackFrameTag -> Put+  put = putWord8 . callStackFrameTagToWord8++  get :: Get CallStackFrameTag+  get =+    getWord8 >>= \case+      0x1 -> pure CallStackFrameIpeTag+      0x2 -> pure CallStackFrameAnnWithNothingTag+      0x3 -> pure CallStackFrameAnnWithJustSourceLocationTag+      badTag ->+        fail $+          printf+            "Found invalid call-stack frame tag %s. Expected one of %s."+            (showAsHex badTag)+            (List.intercalate ", " callStackFrameTags)+   where+    callStackFrameTags :: [String]+    callStackFrameTags = [showAsHex (callStackFrameTagToWord8 tag) | tag <- [minBound .. maxBound]]++-------------------------------------------------------------------------------+-- CallStackFrames++instance Binary IpeId where+  put :: IpeId -> Put+  put = putWord64 . coerce++  get :: Get IpeId+  get = coerce getWord64++ipeIdSize :: Int+ipeIdSize = 8++instance Binary CallStackFrame where+  put :: CallStackFrame -> Put+  put = \case+    CallStackFrameIpe ipeId -> do+      put CallStackFrameIpeTag+      put ipeId+    CallStackFrameAnn stringId Nothing -> do+      put CallStackFrameAnnWithNothingTag+      put stringId+    CallStackFrameAnn stringId (Just sourceLocationId) -> do+      put CallStackFrameAnnWithJustSourceLocationTag+      put stringId+      put sourceLocationId++  get :: Get CallStackFrame+  get = do+    get >>= \case+      CallStackFrameIpeTag ->+        CallStackFrameIpe <$> get+      CallStackFrameAnnWithNothingTag ->+        CallStackFrameAnn <$> get <*> pure Nothing+      CallStackFrameAnnWithJustSourceLocationTag ->+        CallStackFrameAnn <$> get <*> (Just <$> get)++-------------------------------------------------------------------------------+-- StringDefs++instance Binary StringId where+  put :: StringId -> Put+  put = putWord64 . coerce++  get :: Get StringId+  get = coerce getWord64++stringIdSize :: Int+stringIdSize = 8++instance Binary StringDef where+  put :: StringDef -> Put+  put MkStringDef{stringDefId, stringDefBody} = do+    put stringDefId+    put $ MkUnsafeShortText (truncateTextToByteLimit stringDefBodyMaxSize stringDefBody)++  get :: Get StringDef+  get = do+    stringDefId <- get+    -- NOTE: This allows reading stringDefBody with a lengthWord8 of up to+    --       the maxBound of Word16, which is bigger than stringDefBodyMaxSize.+    --       This causes a slight mismatch between the size of stringDefBody+    --       read by get and written by put, which means that get followed by+    --       put is not the identity. However, this would only truncate the+    --       stringDefBody if that binary representation was created manually,+    --     rather than via put, so this is likely not an issue.+    MkUnsafeShortText stringDefBody <- get+    pure MkStringDef{stringDefId, stringDefBody}++stringDefBodyLenSize :: Int+stringDefBodyLenSize = 2++stringDefBodyMaxSize :: Int+stringDefBodyMaxSize =+  messageMaxSize+    - messageTagSize+    - stringIdSize+    - stringDefBodyLenSize++-------------------------------------------------------------------------------+-- SourceLocationDefs++instance Binary SourceLocationId where+  put :: SourceLocationId -> Put+  put = putWord64 . coerce++  get :: Get SourceLocationId+  get = coerce getWord64++sourceLocationIdSize :: Int+sourceLocationIdSize = 8++instance Binary SourceLocationDef where+  put :: SourceLocationDef -> Put+  put msg = do+    put $ sourceLocationDefId msg+    putWord32 (sourceLocationDefRow msg)+    putWord32 (sourceLocationDefColumn msg)+    put (sourceLocationDefFilename msg)++  get :: Get SourceLocationDef+  get = MkSourceLocationDef <$> get <*> getWord32 <*> getWord32 <*> get++sourceLocationDefSize :: Int+sourceLocationDefSize =+  sourceLocationIdSize {- sourceLocationDefId -}+    + 4 {- sourceLocationDefRow -}+    + 4 {- sourceLocationDefColumn -}+    + stringIdSize {- sourceLocationDefFilename -}++-------------------------------------------------------------------------------+-- Trim Text to a byte-size limit++-- | A 'Text' whose 'TF.lengthWord8' is at most @'maxBound' :: 'Word16'@ bytes.+newtype ShortText = MkUnsafeShortText Text+  deriving (Eq, Show)++toShortText :: Text -> ShortText+toShortText text =+  MkUnsafeShortText (truncateTextToByteLimit maxBoundWord16 text)+ where+  maxBoundWord16 = fromIntegral (maxBound @Word16)++instance Binary ShortText where+  put :: ShortText -> Put+  put (MkUnsafeShortText text) = do+    putWord16 (fromIntegral (TF.lengthWord8 text))+    putByteString (TE.encodeUtf8 text)++  get :: Get ShortText+  get = do+    lengthWord8 <- fromIntegral <$> getWord16+    bytes <- getByteString lengthWord8+    pure $ MkUnsafeShortText (TE.decodeUtf8Lenient bytes)++-- | @'truncateTextToByteLimit' byteLimit text@ truncates @text@ such that its+--   UTF-8 serialisation fits within @byteLimit@ bytes.+truncateTextToByteLimit :: Int -> Text -> Text+truncateTextToByteLimit byteLimit text+  | TF.lengthWord8 text <= byteLimit = text+  | TF.lengthWord8 text' <= byteLimit = text'+  | otherwise =+      -- @'takeWord8' n@ takes the first n bytes and _expands_ to complete the+      -- last code point, which means it may return up to n+3 bytes. Hence, if+      -- this happens, we drop the final code point.+      assert (byteLimit < TF.lengthWord8 text' && TF.lengthWord8 text' <= byteLimit + 3) $+        T.dropEnd 1 text'+ where+  text' = TF.takeWord8 (fromIntegral byteLimit) text++-------------------------------------------------------------------------------+-- Size Invariants++callStackLenSize :: Int+callStackLenSize = 2++-- | The maximum number of `CallStackFrame`s in a single `Message`.+callStackMaxLen :: Int+callStackMaxLen = callStackMaxLen' messageMaxSize++-- | The size of a serialised `CapabilityId`.+capabilityIdSize :: Int+capabilityIdSize = 4++-- | The size of a serialised `ThreadId`.+threadIdSize :: Int+threadIdSize = 8++-- | The maximum number of `CallStackFrame`s in a single `Message`,+--   with a variable `messageMaxSize`. Used for testing.+callStackMaxLen' :: Int -> Int+callStackMaxLen' messageMaxSize' =+  fromIntegral+    ( messageMaxSize'+        - messageTagSize+        - capabilityIdSize+        - threadIdSize+        - callStackLenSize+    )++-- | Size in bytes of the given 'CallStackFrame'+callStackFrameSize :: CallStackFrame -> Int+callStackFrameSize = \case+  CallStackFrameIpe{} ->+    1 {- CallStackFrameTag -}+      + 8 {- IpeId -}+  CallStackFrameAnn _ Nothing ->+    1 {- CallStackFrameTag -}+      + 8 {- StringId -}+  CallStackFrameAnn _ (Just _) ->+    1 {- CallStackFrameTag -}+      + 8 {- StringId -}+      + 8 {- SourceLocationId -}++callStackFrameMaxSize :: Int+callStackFrameMaxSize =+  17 {- see case for CallStackFrameAnn in callStackFrameSize -}
+ src/GHC/Stack/Profiler/Core/Internal/Hydrate.hs view
@@ -0,0 +1,154 @@+module GHC.Stack.Profiler.Core.Internal.Hydrate where++import Control.Exception+import Data.Either (partitionEithers)+import Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as IntMap+import Data.Text (Text)+import qualified Data.Text as Text+import GHC.Generics+import GHC.Stack.Profiler.Core.Internal.CallStack+import GHC.Stack.Profiler.Core.Internal.Eventlog+import GHC.Stack.Profiler.Core.Internal.Util++data BinaryCallStackDecodeError+  = StringIdNotFound StringId+  | SourceLocationIdNotFound SourceLocationId+  deriving (Show)++instance Exception BinaryCallStackDecodeError where+  displayException = \case+    StringIdNotFound sid ->+      "Failed to decode a CallStackChunk. Failed to find a String with the key: " ++ show (getStringId sid)+    SourceLocationIdNotFound sid ->+      "Failed to decode a CallStackChunk. Failed to find a SourceLocation with the key: " ++ show (getSourceLocationId sid)++-- | Generic implementation to turn 'CallStackChunk' into the much richer+-- 'CallStack'.+hydrateEventlogCallStackMessage :: SymbolTableReader -> CallStackChunk -> (CallStack, [BinaryCallStackDecodeError])+hydrateEventlogCallStackMessage decodeTable msg =+  let+    decodeItem :: CallStackFrame -> Either BinaryCallStackDecodeError StackItem+    decodeItem = \case+      CallStackFrameIpe ipeId ->+        Right $ IpeId ipeId+      CallStackFrameAnn stringId mSrcLocId -> do+        str <-+          maybe+            (Left $ StringIdNotFound stringId)+            (Right . Text.unpack)+            (lookupStringId decodeTable stringId)+        srcLoc <- case mSrcLocId of+          Nothing -> pure Nothing+          Just srcLocId ->+            maybe+              (Left $ SourceLocationIdNotFound srcLocId)+              (Right . Just)+              (lookupSourceLocationId decodeTable srcLocId)+        pure $ UserAnnotation str srcLoc++    itemsOrErrors = map decodeItem (callStackChunk msg)+    (errors, items) = partitionEithers itemsOrErrors+  in+    ( MkCallStack+        { callCapabilityId = callStackChunkCapabilityId msg+        , callThreadId = callStackChunkThreadId msg+        , callStack = items+        }+    , errors+    )++-- | Implementation agnostic symbol table reader helping consumers to decode+-- 'Message's into a 'CallStack'.+--+-- As during deserialisation, we do not discover new Messages, the abstract 'SymbolTableReader'+-- doesn't need to thread the implementation through the lookup operations.+data SymbolTableReader = MkSymbolTableReader+  { lookupStringId :: StringId -> Maybe Text+  -- ^ Lookup the 'StringId' in the symbol table.+  -- This operation throws an exception if the 'StringId' is unknown.+  , lookupSourceLocationId :: SourceLocationId -> Maybe SourceLocation+  -- ^ Lookup the 'SourceLocationId' in the symbol table.+  -- This operation throws an exception if the 'SourceLocationId' is unknown.+  }+  deriving (Generic)++data MissingKeyError+  = -- | We failed to find the 'StringId' to fully decode the 'SourceLocationId'.+    KeyStringIdNotFound SourceLocationId StringId+  deriving (Show)++instance Exception MissingKeyError where+  displayException = \case+    KeyStringIdNotFound srcLocId stringId ->+      "While decoding the Source Location ("+        ++ show (getSourceLocationId srcLocId)+        ++ "), "+        ++ "the String ("+        ++ show (getStringId stringId)+        ++ ") couldn't be found"++data IntMapTable = MkIntMapTable+  { stringLookupTable :: !(IntMap Text)+  , srcLocLookupTable :: !(IntMap SourceLocation)+  }+  deriving (Eq, Ord, Show, Generic)++emptyIntMapTable :: IntMapTable+emptyIntMapTable =+  MkIntMapTable+    { stringLookupTable = IntMap.empty+    , srcLocLookupTable = IntMap.empty+    }++mkIntMapSymbolTableReader :: IntMapTable -> SymbolTableReader+mkIntMapSymbolTableReader tbl =+  MkSymbolTableReader+    { lookupStringId = flip lookupTextMessage tbl+    , lookupSourceLocationId = flip lookupSourceLocationMessage tbl+    }++{-# INLINEABLE insertTextMessage #-}+insertTextMessage :: StringDef -> IntMapTable -> IntMapTable+insertTextMessage msg tbl =+  tbl+    { stringLookupTable =+        IntMap.insert+          (idToInt $ stringDefId msg)+          (stringDefBody msg)+          (stringLookupTable tbl)+    }++{-# INLINEABLE insertSourceLocationMessage #-}+insertSourceLocationMessage :: SourceLocationDef -> IntMapTable -> Either MissingKeyError IntMapTable+insertSourceLocationMessage msg tbl = do+  let+    srcLocId = sourceLocationDefId msg+    fileId = sourceLocationDefFilename msg++  fileName <-+    maybe (Left $ KeyStringIdNotFound srcLocId fileId) Right $ lookupTextMessage fileId tbl++  pure+    tbl+      { srcLocLookupTable =+          IntMap.insert+            (idToInt srcLocId)+            (mkSourceLocation fileName)+            (srcLocLookupTable tbl)+      }+ where+  mkSourceLocation fileName =+    MkSourceLocation+      { line = sourceLocationDefRow msg+      , column = sourceLocationDefColumn msg+      , fileName = fileName+      }++{-# INLINEABLE lookupTextMessage #-}+lookupTextMessage :: StringId -> IntMapTable -> Maybe Text+lookupTextMessage sid tbl = IntMap.lookup (idToInt sid) (stringLookupTable tbl)++{-# INLINEABLE lookupSourceLocationMessage #-}+lookupSourceLocationMessage :: SourceLocationId -> IntMapTable -> Maybe SourceLocation+lookupSourceLocationMessage sid tbl = IntMap.lookup (idToInt sid) (srcLocLookupTable tbl)
+ src/GHC/Stack/Profiler/Core/Internal/Util.hs view
@@ -0,0 +1,84 @@+module GHC.Stack.Profiler.Core.Internal.Util (+  idToInt,+  showAsHex,+  putWord64,+  putWord32,+  putWord16,+  getWord64,+  getWord32,+  getWord16,+  word64ToWord32,+  word32ToWord64,+  word64ToWord16,+  word32ToInt,+  word64ToInt,+  intToWord64,+  intToWord32,+  intToWord16,+  word16ToInt,+  intToWord8,+  word8ToInt,+) where++import Data.Binary+import Data.Binary.Get+import Data.Binary.Put+import Data.Coerce (Coercible, coerce)+import qualified Numeric++idToInt :: (Coercible a Word64) => a -> Int+idToInt = word64ToInt . coerce++showAsHex :: (Integral a) => a -> String+showAsHex d = "0x" ++ Numeric.showHex d ""++putWord64 :: Word64 -> Put+putWord64 = putWord64be++putWord32 :: Word32 -> Put+putWord32 = putWord32be++putWord16 :: Word16 -> Put+putWord16 = putWord16be++getWord64 :: Get Word64+getWord64 = getWord64be++getWord32 :: Get Word32+getWord32 = getWord32be++getWord16 :: Get Word16+getWord16 = getWord16be++word64ToWord32 :: Word64 -> Word32+word64ToWord32 = fromIntegral++word32ToWord64 :: Word32 -> Word64+word32ToWord64 = fromIntegral++word64ToWord16 :: Word64 -> Word16+word64ToWord16 = fromIntegral++word32ToInt :: Word32 -> Int+word32ToInt = fromIntegral++word64ToInt :: Word64 -> Int+word64ToInt = fromIntegral++intToWord64 :: Int -> Word64+intToWord64 = fromIntegral++intToWord32 :: Int -> Word32+intToWord32 = fromIntegral++intToWord16 :: Int -> Word16+intToWord16 = fromIntegral++word16ToInt :: Word16 -> Int+word16ToInt = fromIntegral++intToWord8 :: Int -> Word8+intToWord8 = fromIntegral++word8ToInt :: Word8 -> Int+word8ToInt = fromIntegral
− src/GHC/Stack/Profiler/Core/SourceLocation.hs
@@ -1,13 +0,0 @@-module GHC.Stack.Profiler.Core.SourceLocation where--import Data.Text (Text)-import Data.Word (Word32)-import GHC.Generics (Generic)---- | A Haskell source location.-data SourceLocation = MkSourceLocation-  { line :: !Word32-  , column :: !Word32-  , fileName :: !Text-  }-  deriving (Eq, Ord, Show, Generic)
− src/GHC/Stack/Profiler/Core/SymbolTable.hs
@@ -1,247 +0,0 @@-module GHC.Stack.Profiler.Core.SymbolTable (-  -- * Abstract interfaces for transforming 'CallStackMessage's and--  -- 'BinaryEventlogMessage' into each other.-  SymbolTableWriter (..),-  SymbolTableReader (..),--  -- * A 'Map' implementation for the 'SymbolTableWriter' interface.-  MapTable,-  emptyMapSymbolTableWriter,-  getKnownStrings,-  getKnownSourceLocations,--  -- * An 'IntMap' implementation for the 'SymbolTableReader' interface.-  IntMapTable,-  MissingKeyError (..),-  mkIntMapSymbolTableReader,-  emptyIntMapTable,-  insertSourceLocationMessage,-  insertTextMessage,-) where--import Control.Exception-import Data.IntMap.Strict (IntMap)-import qualified Data.IntMap.Strict as IntMap-import qualified Data.List as List-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Tuple as Tuple-import GHC.Generics (Generic)-import GHC.Stack.Profiler.Core.Eventlog-import GHC.Stack.Profiler.Core.SourceLocation-import GHC.Stack.Profiler.Core.Util---- ------------------------------------------------------------------------------- Abstract interfaces for writing and reading to the symbol tables for deduplicating--- the symbols for 'Text' and 'SourceLocation'.--- -------------------------------------------------------------------------------- | Implementation agnostic symbol table supposed to be used to deduplicate symbols--- in 'CallStackMessage'.------ When transforming 'CallStackMessage' to ['BinaryEventlogMessage'] we replace some--- symbols with identifiers.--- In particular arbitrary length symbols, such as 'Text's and 'SourceLocation's.--- As these symbols are discovered while encoding the callstack, the 'SymbolTableWriter'--- needs to be extended, which is why we thread the 'tbl' parameter through the--- lookup or insertion operations.-data SymbolTableWriter tbl = MkSymbolTableWriter-  { writerTable :: !tbl-  -- ^ Symbol table for symbols we replace with unique identifiers.-  , lookupOrInsertText :: tbl -> Text -> (StringId, Bool, tbl)-  -- ^ Lookup up the given 'Text' in the 'tbl' Symbol table.-  -- If the 'Text' can't be found, we insert it into the table and generate a-  -- new 'StringId.-  -- Returns 'True', if the given 'Text' was inserted and 'False' otherwise.-  , lookupOrInsertSourceLocation :: tbl -> SourceLocation -> (SourceLocationId, Bool, tbl)-  -- ^ Lookup up the given 'SourceLocation' in the 'tbl' Symbol table.-  -- If the 'SourceLocation' can't be found, we insert it into the table and generate a-  -- new 'SourceLocationId.-  -- Returns 'True', if the given 'Text' was inserted and 'False' otherwise.-  }-  deriving (Generic)---- | Implementation agnostic symbol table reader helping consumers to decode--- 'BinaryEventlogMessage's into a 'CallStackMessage'.------ As during deserialisation, we do not discover new Messages, the abstract 'SymbolTableReader'--- doesn't need to thread the implementation through the lookup operations.-data SymbolTableReader = MkSymbolTableReader-  { lookupStringId :: StringId -> Maybe Text-  -- ^ Lookup the 'StringId' in the symbol table.-  -- This operation throws an exception if the 'StringId' is unknown.-  , lookupSourceLocationId :: SourceLocationId -> Maybe SourceLocation-  -- ^ Lookup the 'SourceLocationId' in the symbol table.-  -- This operation throws an exception if the 'SourceLocationId' is unknown.-  }-  deriving (Generic)---- ------------------------------------------------------------------------------- Implementation backend for 'SymbolTableWriter'--- ------------------------------------------------------------------------------data MapTable = MkMapTable-  { stringTable :: !(Map Text StringId)-  , srcLocTable :: !(Map SourceLocation SourceLocationId)-  , stringUniqueSupply :: {-# UNPACK #-} !StringId-  , srcLocUniqueSupply :: {-# UNPACK #-} !SourceLocationId-  }-  deriving (Show, Eq, Ord, Generic)--{-# INLINEABLE emptyMapSymbolTableWriter #-}-emptyMapSymbolTableWriter :: SymbolTableWriter MapTable-emptyMapSymbolTableWriter =-  MkSymbolTableWriter-    { writerTable =-        MkMapTable-          { stringTable = Map.empty-          , srcLocTable = Map.empty-          , stringUniqueSupply = MkStringId 0-          , srcLocUniqueSupply = MkSourceLocationId 0-          }-    , lookupOrInsertText = alterStringMap-    , lookupOrInsertSourceLocation = alterSrcLocTable-    }- where-  nextSrcLocUnique tbl =-    ( srcLocUniqueSupply tbl-    , tbl-        { srcLocUniqueSupply =-            incrementSourceLocationId $ srcLocUniqueSupply tbl-        }-    )--  nextStringUnique tbl =-    ( stringUniqueSupply tbl-    , tbl-        { stringUniqueSupply =-            incrementStringLocationId $ stringUniqueSupply tbl-        }-    )--  updateEntry tbl0 nextKey Nothing =-    let-      (sid, tbl) = nextKey tbl0-    in-      ((sid, True, tbl), Just sid)-  updateEntry tbl _ (Just val) =-    ((val, False, tbl), Just val)--  swapAround set ((sid, new, tbl), hm) =-    (sid, new, set tbl hm)--  alterStringMap = \tbl str ->-    swapAround setStringTable $-      Map.alterF (updateEntry tbl nextStringUnique) str (stringTable tbl)--  alterSrcLocTable = \tbl srcLoc ->-    swapAround setSourceLocationTable $-      Map.alterF (updateEntry tbl nextSrcLocUnique) srcLoc (srcLocTable tbl)--setSourceLocationTable :: MapTable -> Map SourceLocation SourceLocationId -> MapTable-setSourceLocationTable tbl hm =-  tbl-    { srcLocTable = hm-    }--setStringTable :: MapTable -> Map Text StringId -> MapTable-setStringTable tbl hm =-  tbl-    { stringTable = hm-    }--getKnownStrings :: MapTable -> [(StringId, Text)]-{-# INLINEABLE getKnownStrings #-}-getKnownStrings table =-  List.map Tuple.swap $ Map.assocs (stringTable table)--getKnownSourceLocations :: MapTable -> [(SourceLocationId, SourceLocation)]-{-# INLINEABLE getKnownSourceLocations #-}-getKnownSourceLocations table =-  List.map Tuple.swap $ Map.assocs (srcLocTable table)---- ------------------------------------------------------------------------------- Implementation backend for 'SymbolTableReader'--- ------------------------------------------------------------------------------data MissingKeyError-  = -- | We failed to find the 'StringId' to fully decode the 'SourceLocationId'.-    KeyStringIdNotFound SourceLocationId StringId-  deriving (Show)--instance Exception MissingKeyError where-  displayException = \case-    KeyStringIdNotFound srcLocId stringId ->-      "While decoding the Source Location ("-        ++ show (getSourceLocationId srcLocId)-        ++ "), "-        ++ "the String ("-        ++ show (getStringId stringId)-        ++ ") couldn't be found"--data IntMapTable = MkIntMapTable-  { stringLookupTable :: !(IntMap Text)-  , srcLocLookupTable :: !(IntMap SourceLocation)-  }-  deriving (Eq, Ord, Show, Generic)--emptyIntMapTable :: IntMapTable-emptyIntMapTable =-  MkIntMapTable-    { stringLookupTable = IntMap.empty-    , srcLocLookupTable = IntMap.empty-    }--mkIntMapSymbolTableReader :: IntMapTable -> SymbolTableReader-mkIntMapSymbolTableReader tbl =-  MkSymbolTableReader-    { lookupStringId = flip lookupTextMessage tbl-    , lookupSourceLocationId = flip lookupSourceLocationMessage tbl-    }--{-# INLINEABLE insertTextMessage #-}-insertTextMessage :: BinaryStringMessage -> IntMapTable -> IntMapTable-insertTextMessage msg tbl =-  tbl-    { stringLookupTable =-        IntMap.insert-          (idToInt $ binaryStringMessageId msg)-          (binaryStringMessage msg)-          (stringLookupTable tbl)-    }--{-# INLINEABLE insertSourceLocationMessage #-}-insertSourceLocationMessage :: BinarySourceLocationMessage -> IntMapTable -> Either MissingKeyError IntMapTable-insertSourceLocationMessage msg tbl = do-  let-    srcLocId = binarySourceLocationMessageId msg-    fileId = binarySourceLocationFilename msg--  fileName <--    maybe (Left $ KeyStringIdNotFound srcLocId fileId) Right $ lookupTextMessage fileId tbl--  pure-    tbl-      { srcLocLookupTable =-          IntMap.insert-            (idToInt srcLocId)-            (mkSourceLocation fileName)-            (srcLocLookupTable tbl)-      }- where-  mkSourceLocation fileName =-    MkSourceLocation-      { line = binarySourceLocationRow msg-      , column = binarySourceLocationColumn msg-      , fileName = fileName-      }--{-# INLINEABLE lookupTextMessage #-}-lookupTextMessage :: StringId -> IntMapTable -> Maybe Text-lookupTextMessage sid tbl = IntMap.lookup (idToInt sid) (stringLookupTable tbl)--{-# INLINEABLE lookupSourceLocationMessage #-}-lookupSourceLocationMessage :: SourceLocationId -> IntMapTable -> Maybe SourceLocation-lookupSourceLocationMessage sid tbl = IntMap.lookup (idToInt sid) (srcLocLookupTable tbl)
− src/GHC/Stack/Profiler/Core/ThreadSample.hs
@@ -1,336 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module GHC.Stack.Profiler.Core.ThreadSample (-  -- * High-level API-  ThreadSample (..),-  deserializeEventlogMessage,--  -- * Serialisable 'ThreadSample'-  CallStackMessage (..),-  StackItem (..),-  SourceLocation (..),--  -- * Serialisation of 'CallStackMessage'-  SymbolTableWriter (..),-  SymbolTableReader (..),-  dehydrateCallStackMessage,-  BinaryCallStackDecodeError (..),-  hydrateEventlogCallStackMessage,-  catCallStackMessage,-  chunkCallStackMessage,-  chunkCallStackMessage_,--  -- * Message dehydration helpers-  EncodingState,-  runWithEncodingState,-  newEncodingState,-  lookupSourceLocationMessage,-  lookupTextMessage,-) where--import Control.Concurrent-import Control.Exception (Exception (..))-import Control.Monad (when)-import Control.Monad.Trans.State.Strict (State, runState)-import qualified Control.Monad.Trans.State.Strict as State-import Data.Binary-import Data.Binary.Get-import qualified Data.ByteString.Lazy as LBS-import Data.Either (partitionEithers)-import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Text (Text)-import qualified Data.Text as Text-import GHC.Generics--import GHC.Stack.CloneStack (StackSnapshot)-import GHC.Stack.Profiler.Core.Eventlog-import GHC.Stack.Profiler.Core.SourceLocation-import GHC.Stack.Profiler.Core.SymbolTable-import GHC.Stack.Profiler.Core.Util (word16ToInt)---- ------------------------------------------------------------------------------- Thread Sample--- -------------------------------------------------------------------------------- | A 'ThreadSample' is a snapshot of a threads RTS callstack.--- This callstack is a copy of the original callstack, so can be traversed and--- decoded without affecting the running thread.------ The 'StackSnapshot' is a boxed value and needs to be garbage collected.--- Note, as long as 'StackSnapshot' is alive, you keep the full callstack--- alive, which might be quite expensive.-data ThreadSample = ThreadSample-  { threadSampleId :: !ThreadId-  , threadSampleCapability :: !CapabilityId-  , threadSampleStackSnapshot :: !StackSnapshot-  }-  deriving (Generic)--deserializeEventlogMessage :: LBS.ByteString -> Either String BinaryEventlogMessage-deserializeEventlogMessage msg = case runGetOrFail get msg of-  Left (_, _, errMsg) -> Left errMsg-  Right (_, _, callStackMessage) -> Right callStackMessage---- ------------------------------------------------------------------------------- Decoded RTS CallStack--- -------------------------------------------------------------------------------- | A decoded rts callstack that can be serialised to the EventLog.-data CallStackMessage = MkCallStackMessage-  { callThreadId :: !Word64-  , callCapabilityId :: !CapabilityId-  , callStack :: [StackItem]-  }-  deriving (Eq, Ord, Show, Generic)--data StackItem-  = IpeId !IpeId-  | UserAnnotation !String !(Maybe SourceLocation)-  deriving (Eq, Ord, Show, Generic)---- ------------------------------------------------------------------------------- Turning a 'CallStackMessage' into '[BinaryEventlogMessage]'--- -------------------------------------------------------------------------------- | Generic implementation to turn 'CallStackMessage' into '[BinaryEventlogMessage]'.------ Replaces already encountered text or source location information with unique ids.--- If new text or source location messages are encountered, they are inserted into--- the 'SymbolTableWriter'.------ All new string values and source location messages are before 'CallStackChunk' and--- 'CallStackFinal' messages.--- For the result list @r :: ['BinaryEventlogMessage']@, the following holds:------ * 'StringDef' messages are the first elements in @r@. There might not be any.--- * 'SourceLocationDef' are after 'StringDef' messages and before any 'CallStackChunk' or---    'CallStackFinal' messages. There might not be any such messages.--- * Then 'CallStackChunk' follow if there are any.--- * The last message is always a 'CallStackFinal' message and it occurs exactly once in @r@.-dehydrateCallStackMessage ::-  forall table.-  SymbolTableWriter table ->-  CallStackMessage ->-  ([BinaryEventlogMessage], SymbolTableWriter table)-dehydrateCallStackMessage msgTbl0 msg =-  let-    (stackItems, finalState) =-      runWithEncodingState-        (newEncodingState msgTbl0)-        (mapM go (callStack msg))--    stringDefs =-      map StringDef $ stringMessages finalState--    sourceLocDefs =-      map SourceLocationDef $ sourceLocMessages finalState--    -- TODO: this needs to be fixed-    stackMsgChunks =-      chunkCallStackMessage-        MkBinaryCallStackMessage-          { binaryCallThreadId = callThreadId msg-          , binaryCallCapabilityId = callCapabilityId msg-          , binaryCallStack = stackItems-          }-  in-    ( stringDefs ++ sourceLocDefs ++ stackMsgChunks-    , symbolTableWriter finalState-    )- where-  go :: StackItem -> State (EncodingState tbl) BinaryStackItem-  go = \case-    IpeId ipeId ->-      pure $ BinaryIpe ipeId-    UserAnnotation s mSrcLoc -> do-      srcLocId <- case mSrcLoc of-        Nothing -> pure Nothing-        Just srcLoc -> Just <$> lookupSourceLocationMessage srcLoc-      BinaryMessage <$> lookupTextMessage (Text.pack s) <*> pure srcLocId--data BinaryCallStackDecodeError-  = StringIdNotFound StringId-  | SourceLocationIdNotFound SourceLocationId-  deriving (Show)--instance Exception BinaryCallStackDecodeError where-  displayException = \case-    StringIdNotFound sid ->-      "Failed to decode a BinaryCallStackMessage. Failed to find a String with the key: " ++ show (getStringId sid)-    SourceLocationIdNotFound sid ->-      "Failed to decode a BinaryCallStackMessage. Failed to find a SourceLocation with the key: " ++ show (getSourceLocationId sid)---- | Generic implementation to turn 'BinaryCallStackMessage' into the much richer--- 'CallStackMessage'.-hydrateEventlogCallStackMessage :: SymbolTableReader -> BinaryCallStackMessage -> (CallStackMessage, [BinaryCallStackDecodeError])-hydrateEventlogCallStackMessage decodeTable msg =-  let-    decodeItem :: BinaryStackItem -> Either BinaryCallStackDecodeError StackItem-    decodeItem = \case-      BinaryIpe ipeId ->-        Right $ IpeId ipeId-      BinaryMessage stringId mSrcLocId -> do-        str <--          maybe-            (Left $ StringIdNotFound stringId)-            (Right . Text.unpack)-            (lookupStringId decodeTable stringId)-        srcLoc <- case mSrcLocId of-          Nothing -> pure Nothing-          Just srcLocId ->-            maybe-              (Left $ SourceLocationIdNotFound srcLocId)-              (Right . Just)-              (lookupSourceLocationId decodeTable srcLocId)-        pure $ UserAnnotation str srcLoc--    itemsOrErros = map decodeItem (binaryCallStack msg)-    (errors, items) = partitionEithers itemsOrErros-  in-    ( MkCallStackMessage-        { callCapabilityId = binaryCallCapabilityId msg-        , callThreadId = binaryCallThreadId msg-        , callStack = items-        }-    , errors-    )---- | Combine all 'BinaryCallStackMessage's into a single 'BinaryCallStackMessage'.--- We assume that all 'BinaryCallStackMessage' only differ in their 'binaryCallStack' values.------ 'catCallStackMessage' is the conceptually inverse of 'chunkCallStackMessage'.-catCallStackMessage :: NonEmpty BinaryCallStackMessage -> BinaryCallStackMessage-catCallStackMessage msgs =-  MkBinaryCallStackMessage-    { binaryCallThreadId = binaryCallThreadId $ NonEmpty.head msgs-    , binaryCallCapabilityId = binaryCallCapabilityId $ NonEmpty.head msgs-    , binaryCallStack = concatMap (reverse . binaryCallStack) . reverse $ NonEmpty.toList msgs-    }---- | Chunk the 'binaryCallStack' of the 'BinaryCallStackMessage' by the given 'Word16'.--- If there are no items in 'BinaryCallStackMessage', then a singleton list is returned containing--- the original element.------ Post-condition for the result @r@:------ * all elements in @init r @ are 'CallStackChunk's--- * the element returned by @last r@ is a 'CallStackFinal' BinaryEventlogMessage.------ The resulting 'CallStackChunk' are in reverse order and so are the chunks themselves.------ This means, for a stack @[1,2,3,4,5,6]@ and an assumed chunk size of 2,--- we produce @[[6,5],[4,3],[2,1]]@.-chunkCallStackMessage :: BinaryCallStackMessage -> [BinaryEventlogMessage]-chunkCallStackMessage = chunkCallStackMessage_ callStackSizeLimit---- | Same as 'chunkCallStackMessage', but allows to set the chunking size in bytes.-chunkCallStackMessage_ :: Word16 -> BinaryCallStackMessage -> [BinaryEventlogMessage]-chunkCallStackMessage_ chunkLimit16 msg0 =-  let-    chunkLimitInt = word16ToInt chunkLimit16-    items = binaryCallStack msg0-    chunked =-      let-        go (!size, curChunk, restChunk) item =-          let-            !bytes = word16ToInt $ byteSizeOf item-          in-            if (size + bytes) < chunkLimitInt-              then (size + bytes, item : curChunk, restChunk)-              else (bytes, [item], curChunk : restChunk)-        (_, lastChunk, initChunk) = List.foldl' go (0, [], []) items-      in-        lastChunk : initChunk-  in-    mkEventlogMessages chunked- where-  mkCallStack chunk =-    MkBinaryCallStackMessage-      { binaryCallThreadId = binaryCallThreadId msg0-      , binaryCallCapabilityId = binaryCallCapabilityId msg0-      , binaryCallStack = chunk-      }--  mkEventlogMessages :: [[BinaryStackItem]] -> [BinaryEventlogMessage]-  mkEventlogMessages [] =-    -- If there are no chunks, we simply return the original message-    [ CallStackFinal msg0-    ]-  mkEventlogMessages [chunk] =-    [ CallStackFinal $ mkCallStack chunk-    ]-  mkEventlogMessages (chunk : chunks) =-    CallStackChunk (mkCallStack chunk) : mkEventlogMessages chunks---- ------------------------------------------------------------------------------- Helper types and functions to implement the conversion to the binary--- representation.--- ------------------------------------------------------------------------------data EncodingState tbl = MkEncodingState-  { symbolTableWriter :: !(SymbolTableWriter tbl)-  , stringMessages :: ![BinaryStringMessage]-  , sourceLocMessages :: ![BinarySourceLocationMessage]-  }-  deriving (Generic)--runWithEncodingState :: EncodingState tbl -> State (EncodingState tbl) a -> (a, EncodingState tbl)-runWithEncodingState encodingState encoder =-  runState encoder encodingState--newEncodingState :: SymbolTableWriter tbl -> EncodingState tbl-newEncodingState msgTbl0 =-  MkEncodingState-    { symbolTableWriter = msgTbl0-    , stringMessages = []-    , sourceLocMessages = []-    }--setSymbolTableWriter :: tbl -> State.State (EncodingState tbl) ()-setSymbolTableWriter tbl = State.modify' (\st -> st{symbolTableWriter = (symbolTableWriter st){writerTable = tbl}})--addStringMessage :: BinaryStringMessage -> State.State (EncodingState tbl) ()-addStringMessage msg = State.modify' (\st -> st{stringMessages = msg : stringMessages st})--addSourceLocationMessage :: BinarySourceLocationMessage -> State.State (EncodingState tbl) ()-addSourceLocationMessage msg = State.modify' (\st -> st{sourceLocMessages = msg : sourceLocMessages st})--lookupOrInsertTextMessage :: forall tbl. Text -> State (EncodingState tbl) (StringId, Bool)-lookupOrInsertTextMessage s = do-  tbl <- State.gets symbolTableWriter-  let-    (sid, new, tbl1) = lookupOrInsertText tbl (writerTable tbl) s-  setSymbolTableWriter tbl1-  pure (sid, new)--lookupOrInsertSrcLocMessage :: forall tbl. SourceLocation -> State (EncodingState tbl) (SourceLocationId, Bool)-lookupOrInsertSrcLocMessage s = do-  tbl <- State.gets symbolTableWriter-  let-    (sid, new, tbl1) = lookupOrInsertSourceLocation tbl (writerTable tbl) s-  setSymbolTableWriter tbl1-  pure (sid, new)--lookupTextMessage :: forall tbl. Text -> State (EncodingState tbl) StringId-lookupTextMessage s = do-  (sid, new) <- lookupOrInsertTextMessage s-  when new $-    addStringMessage $-      MkBinaryStringMessage sid s-  pure sid--lookupSourceLocationMessage :: forall tbl. SourceLocation -> State (EncodingState tbl) SourceLocationId-lookupSourceLocationMessage s = do-  (sid, new) <- lookupOrInsertSrcLocMessage s-  when new $ do-    fileId <- lookupTextMessage $ fileName s-    addSourceLocationMessage $-      MkBinarySourceLocationMessage-        { binarySourceLocationMessageId = sid-        , binarySourceLocationRow = line s-        , binarySourceLocationColumn = column s-        , binarySourceLocationFilename = fileId-        }-  pure sid
− src/GHC/Stack/Profiler/Core/Util.hs
@@ -1,104 +0,0 @@-module GHC.Stack.Profiler.Core.Util (-  idToInt,-  putTextWord16,-  getTextWord16,-  showAsHex,-  putWord64,-  putWord32,-  putWord16,-  getWord64,-  getWord32,-  getWord16,-  word64ToWord32,-  word32ToWord64,-  word64ToWord16,-  word32ToInt,-  word64ToInt,-  intToWord64,-  intToWord32,-  intToWord16,-  word16ToInt,-  intToWord8,-  word8ToInt,-) where--import Control.Monad (replicateM)-import Data.Binary-import Data.Binary.Get-import Data.Binary.Put-import Data.Coerce (Coercible, coerce)-import Data.Text (Text)-import qualified Data.Text as Text-import qualified Numeric--idToInt :: (Coercible a Word64) => a -> Int-idToInt = word64ToInt . coerce--putTextWord16 :: Word16 -> Text -> Put-putTextWord16 bound msg =-  putWord16 len <> putStringUtf8 (Text.unpack msg)- where-  shortName = Text.take (word16ToInt bound) msg-  -- this is safe as 'bound' is a 'Word16' itself-  -- so the short string can be at most have a length of 'Word16'-  len = intToWord16 $ Text.length shortName--getTextWord16 :: Get Text-getTextWord16 = do-  len <- getWord16-  s <- replicateM (word16ToInt len) get-  pure $ Text.pack s--showAsHex :: (Integral a) => a -> String-showAsHex d = "0x" ++ Numeric.showHex d ""--putWord64 :: Word64 -> Put-putWord64 = putWord64be--putWord32 :: Word32 -> Put-putWord32 = putWord32be--putWord16 :: Word16 -> Put-putWord16 = putWord16be--getWord64 :: Get Word64-getWord64 = getWord64be--getWord32 :: Get Word32-getWord32 = getWord32be--getWord16 :: Get Word16-getWord16 = getWord16be--word64ToWord32 :: Word64 -> Word32-word64ToWord32 = fromIntegral--word32ToWord64 :: Word32 -> Word64-word32ToWord64 = fromIntegral--word64ToWord16 :: Word64 -> Word16-word64ToWord16 = fromIntegral--word32ToInt :: Word32 -> Int-word32ToInt = fromIntegral--word64ToInt :: Word64 -> Int-word64ToInt = fromIntegral--intToWord64 :: Int -> Word64-intToWord64 = fromIntegral--intToWord32 :: Int -> Word32-intToWord32 = fromIntegral--intToWord16 :: Int -> Word16-intToWord16 = fromIntegral--word16ToInt :: Word16 -> Int-word16ToInt = fromIntegral--intToWord8 :: Int -> Word8-intToWord8 = fromIntegral--word8ToInt :: Word8 -> Int-word8ToInt = fromIntegral
test/Main.hs view
@@ -1,115 +1,276 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiWayIf #-}+{-# OPTIONS_GHC -Wno-incomplete-patterns #-} {-# OPTIONS_GHC -Wno-orphans #-}  module Main where -import Data.Binary-import Data.Binary.Put-import qualified Data.ByteString.Lazy as LBS+import Data.Binary (Binary, Word16, Word32, decode, encode)+import qualified Data.ByteString.Lazy as BSL import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe-import GHC.Stack.Profiler.Core.Eventlog-import GHC.Stack.Profiler.Core.ThreadSample-import GHC.Stack.Profiler.Core.Util (word32ToWord64)+import Data.Maybe (mapMaybe)+import Data.Proxy (Proxy (..))+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Foreign as TF+import GHC.Stack.Profiler.Core+import GHC.Stack.Profiler.Core.Internal import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.Options (IsOption (..)) import Test.Tasty.QuickCheck+import Text.Printf (printf)  main :: IO ()-main = defaultMain tests+main =+  defaultMain $+    adjustOption increaseDefaultMaxSize $+      testGroup "Tests" $+        [ testGroup "Size" $+            [ test_encodeSize "MessageTag" (Proxy @MessageTag) messageTagSize+            , test_encodeSize "CallStackFrameTag" (Proxy @CallStackFrameTag) callStackFrameTagSize+            , prop_encodeSizeInv "IpeId" (Proxy @IpeId) (== ipeIdSize)+            , prop_encodeSizeInv "ThreadId" (Proxy @ThreadId) (== threadIdSize)+            , prop_encodeSizeInv "CapabilityId" (Proxy @CapabilityId) (== capabilityIdSize)+            , prop_encodeSizeInv "CallStackFrame" (Proxy @CallStackFrame) (<= callStackFrameMaxSize)+            , prop_chunkCallStackSizeInv+            , let+                gen =+                  -- Generate a callStack, divide it into chunks, then pick one of the messages.+                  elements . chunkCallStack =<< arbitrary+              in+                prop_encodeSizeInv' "CallStackChunk/CallStackFinal" gen show Nothing (<= messageMaxSize)+            , prop_encodeSizeInv "StringId" (Proxy @StringId) (== stringIdSize)+            , let+                gen =+                  StringDef <$> arbitrary+                showFor msg@(StringDef MkStringDef{stringDefId, stringDefBody}) =+                  printf+                    "stringDefId == %d && length stringDefBody == %d && %s"+                    (getStringId stringDefId)+                    (T.length stringDefBody)+                    (labelFor msg)+                labelFor (StringDef MkStringDef{stringDefBody}) =+                  if fromIntegral (T.length stringDefBody) > stringDefBodyMaxSize+                    then "length stringDefBody >  stringDefBodyMaxSize"+                    else "length stringDefBody <= stringDefBodyMaxSize"+              in+                prop_encodeSizeInv' "StringDef" gen showFor (Just labelFor) (<= messageMaxSize)+            , prop_encodeSizeInv "SourceLocationId" (Proxy @SourceLocationId) (== sourceLocationIdSize)+            , prop_encodeSizeInv "SourceLocationDef" (Proxy @SourceLocationDef) (== sourceLocationDefSize)+            , prop_encodeSizeInv "ShortText" (Proxy @ShortText) (<= maxBoundWord16 + 2)+            , prop_truncateTextToByteLimit+            ]+        , testGroup "Encode/Decode" $+            [ test_encodeDecode "MessageTag" (Proxy @MessageTag)+            , test_encodeDecode "CallStackFrameTag" (Proxy @CallStackFrameTag)+            , prop_encodeDecode "IpeId" (Proxy @IpeId)+            , prop_encodeDecode "ThreadId" (Proxy @ThreadId)+            , prop_encodeDecode "CapabilityId" (Proxy @CapabilityId)+            , prop_encodeDecode "CallStackFrame" (Proxy @CallStackFrame)+            , prop_chunkAndJoinCallStack+            , let+                to :: CallStackChunk -> [Message]+                to = chunkCallStack+                from :: [Message] -> CallStackChunk+                from = joinCallStackChunks . NonEmpty.fromList . mapMaybe getCallStackFrame+              in+                prop_encodeDecodeVia' "CallStackChunk/CallStackFinal" arbitrary show Nothing to from+            , prop_encodeDecode "StringId" (Proxy @StringId)+            , let+                -- The roundtrip property only holds if the byte length of stringDefBody+                -- is less than stringDefBodyMaxSize, otherwise it's truncated.+                gen = do+                  MkStringDef{stringDefId, stringDefBody} <- arbitrary+                  let+                    stringDefBody' = truncateTextToByteLimit stringDefBodyMaxSize stringDefBody+                  pure $ StringDef MkStringDef{stringDefId, stringDefBody = stringDefBody'}+              in+                prop_encodeDecode' "StringDef" gen show Nothing+            , prop_encodeDecode "SourceLocationId" (Proxy @SourceLocationId)+            , prop_encodeDecode "SourceLocationDef" (Proxy @SourceLocationDef)+            , prop_encodeDecode "ShortText" (Proxy @ShortText)+            ]+        ]+ where+  increaseDefaultMaxSize :: QuickCheckMaxSize -> QuickCheckMaxSize+  increaseDefaultMaxSize v@(QuickCheckMaxSize _) =+    if v /= defaultValue then v else QuickCheckMaxSize (2 * maxBoundWord16) -tests :: TestTree-tests =-  testGroup-    "tests"-    [ properties+--------------------------------------------------------------------------------+-- Tests+--------------------------------------------------------------------------------++--------------------------------------------------------------------------------+-- Tests - Size Invariants++-- | Test a size invariant on the result of `encode` using `Enum`.+test_encodeSize :: (Binary a, Bounded a, Enum a, Eq a, Show a) => TestName -> Proxy a -> Int -> TestTree+test_encodeSize testName (_pa :: Proxy a) size =+  testGroup testName $+    [ testCase (printf "length (encode %s) == %d" (show a) size) $ do+        fromIntegral (BSL.length (encode a)) @?= size+    | (a :: a) <- [minBound .. maxBound]     ] -properties :: TestTree-properties =-  testGroup-    "property"-    [ testProperty "chunkCallStackMessage . catCallStackMessage" $-        withNumTests 500 $-          withMaxSize (fromIntegral callStackSizeLimit * 3) $-            chunkingRoundTrip_prop callStackSizeLimit-    , testProperty "chunkCallStackMessage size < chunkCallStackMessage" $-        withNumTests 500 $ do-          withMaxSize (fromIntegral callStackSizeLimit * 3) $-            messageChunkSize_prop eventlogBufferSize callStackSizeLimit-    , testProperty "chunkCallStackMessage_ n . catCallStackMessage" $-        withNumTests 500 $-          withEventlogSizeGen $ \eventlogSize ->-            withMaxSize (fromIntegral eventlogSize * 20) $-              chunkingRoundTrip_prop (callStackSizeLimit_ eventlogSize)-    , testProperty "chunkCallStackMessage_ n < size" $-        withNumTests 500 $ do-          withEventlogSizeGen $ \eventlogSize ->-            withMaxSize (fromIntegral eventlogSize * 5) $-              messageChunkSize_prop eventlogSize (callStackSizeLimit_ eventlogSize)+-- | Test a size invariant on the result of `encode` using QuickCheck.+prop_encodeSizeInv :: (Arbitrary a, Binary a, Eq a, Show a) => TestName -> Proxy a -> (Int -> Bool) -> TestTree+prop_encodeSizeInv testName (_pa :: Proxy a) sizeInv =+  testProperty testName $ \(a :: a) ->+    sizeInv (fromIntegral (BSL.length (encode a)))++-- | Variant of `prop_encodeSizeInv` that accepts a custom generator, show function, and label function.+prop_encodeSizeInv' :: (Binary a, Eq a) => TestName -> Gen a -> (a -> String) -> Maybe (a -> String) -> (Int -> Bool) -> TestTree+prop_encodeSizeInv' testName gen showFor maybeLabelFor sizeInv =+  testProperty testName $+    forAllShow gen showFor $ \a ->+      maybe property (label . ($ a)) maybeLabelFor $+        sizeInv (fromIntegral (BSL.length (encode a)))++--------------------------------------------------------------------------------+-- Tests - Encode/Decode Roundtrips++-- | Test that an `encode`/`decode` roundtrip using a `Binary` instance is the identity using `Enum`.+test_encodeDecode :: (Binary a, Bounded a, Enum a, Eq a, Show a) => TestName -> Proxy a -> TestTree+test_encodeDecode testName (_pa :: Proxy a) =+  testGroup testName $+    [ testCase (printf "decode (encode %s) == %s" (show a) (show a)) $ do+        decode (encode a) @?= a+    | (a :: a) <- [minBound .. maxBound]     ]++-- | Test that an `encode`/`decode` roundtrip using a `Binary` instance is the identity using QuickCheck.+prop_encodeDecode :: (Arbitrary a, Binary a, Eq a, Show a) => TestName -> Proxy a -> TestTree+prop_encodeDecode testName (_pa :: Proxy a) =+  testProperty testName $ \(a :: a) ->+    decode (encode a) == a++-- | Variant of `prop_encodeDecode` that accepts a custom generator, show function, and label function.+prop_encodeDecode' :: (Binary a, Eq a) => TestName -> Gen a -> (a -> String) -> Maybe (a -> String) -> TestTree+prop_encodeDecode' testName gen showFor maybeLabelFor =+  prop_encodeDecodeVia' testName gen showFor maybeLabelFor id id++-- | Variant of `prop_encodeDecode` that accepts conversions to/from a type with a `Binary` instance.+prop_encodeDecodeVia :: (Arbitrary a, Eq a, Show a, Binary b) => TestName -> (a -> b) -> (b -> a) -> TestTree+prop_encodeDecodeVia testName to from =+  prop_encodeDecodeVia' testName arbitrary show Nothing to from++-- | Variant of `prop_encodeDecodeVia'` that accepts conversions to/from a type with a `Binary` instance.+prop_encodeDecodeVia' :: (Eq a, Binary b) => TestName -> Gen a -> (a -> String) -> Maybe (a -> String) -> (a -> b) -> (b -> a) -> TestTree+prop_encodeDecodeVia' testName gen showFor maybeLabelFor to from =+  testProperty testName $+    forAllShow gen showFor $ \a ->+      maybe property (label . ($ a)) maybeLabelFor $+        from (decode (encode (to a))) == a++--------------------------------------------------------------------------------+-- Tests - Auxilliary++-- | Test that `chunkCallStack_` works as advertised.+prop_chunkCallStackSizeInv :: TestTree+prop_chunkCallStackSizeInv =+  testProperty "length (encode message) <= messageMaxSize | message <- chunkCallStack callStack" $ \callStack ->+    forAll (choose (messageMinSize, 2 * messageMaxSize)) $ \messageMaxSize' ->+      conjoin+        [ BSL.length (encode message) <= fromIntegral messageMaxSize'+        | message <- chunkCallStack_ (callStackMaxLen' messageMaxSize') callStack+        ]++-- | Test that `chunkCallStack_` and `joinCallStackChunks` are inverses.+prop_chunkAndJoinCallStack :: TestTree+prop_chunkAndJoinCallStack =+  testProperty "joinCallStackChunks (chunkCallStack_ n callStack) == callStack" $+    \(Positive n) callStack ->+      case NonEmpty.nonEmpty (mapMaybe getCallStackFrame (chunkCallStack_ n callStack)) of+        Nothing -> True+        Just callStackChunks -> joinCallStackChunks callStackChunks == callStack++-- | Test that `truncateTextToByteLimit` works as advertised.+prop_truncateTextToByteLimit :: TestTree+prop_truncateTextToByteLimit =+  testProperty "lengthWord8 (truncateTextToByteLimit byteLimit text) <= byteLimit" $+    \(NonNegative byteLimit) (UnicodeString (T.pack -> text)) ->+      label (labelFor byteLimit text) $+        let+          text' = truncateTextToByteLimit byteLimit text+        in+          if TF.lengthWord8 text <= byteLimit+            then+              -- If the original text fit within the byteLimit, the text should be unchanged.+              text == text'+            else+              -- Otherwise:+              and+                [ -- 1. The new text length should be within 3 byte of the byteLimit.+                  byteLimit - 3 <= TF.lengthWord8 text' && TF.lengthWord8 text' <= byteLimit+                , -- 2. The new text should be a prefix of the old text.+                  text' `T.isPrefixOf` text+                ]  where-  chunkingRoundTrip_prop stackSizeLimit message =-    let-      msgs = mapMaybe go (chunkCallStackMessage_ stackSizeLimit message)-      go = \case-        CallStackChunk csm -> Just csm-        CallStackFinal csm -> Just csm-        _ -> Nothing-    in-      catCallStackMessage (NonEmpty.fromList msgs) === message+  labelFor :: Int -> Text -> String+  labelFor byteLimit text+    | TF.lengthWord8 text <= byteLimit = "lengthWord8 text <= byteLimit"+    | otherwise = "lengthWord8 text >  byteLimit" -  messageChunkSize_prop eventlogSize stackSizeLimit message =-    let-      msgs = mapMaybe go (chunkCallStackMessage_ stackSizeLimit message)-      go = \case-        CallStackChunk csm -> Just csm-        CallStackFinal csm -> Just csm-        _ -> Nothing-    in-      conjoin $-        map (eventlogMessageSmallerThanStackSizeLimit_prop (fromIntegral eventlogSize)) msgs+--------------------------------------------------------------------------------+-- Helpers -  eventlogMessageSmallerThanStackSizeLimit_prop :: Word -> BinaryCallStackMessage -> Property-  eventlogMessageSmallerThanStackSizeLimit_prop eventlogLimit chunk =-    let-      -- We need either 'CallStackFinal' or 'CallStackChunk' to add the leading '0xFFCA' or '0xFFCB'.-      -- This way, the eventlogLimit length is the correct thing to check.-      msg = runPut (put $ CallStackFinal chunk)-    in-      (LBS.length msg <= fromIntegral eventlogLimit) === True+-- | Get the `CallStackChunk` from a `Message`.+getCallStackFrame :: Message -> Maybe CallStackChunk+getCallStackFrame = \case+  CallStackFinal callStackChunk -> Just callStackChunk+  CallStackChunk callStackChunk -> Just callStackChunk+  _otherwise -> Nothing -withEventlogSizeGen :: (Word64 -> Property) -> Property-withEventlogSizeGen k =-  -- 29 bytes is minimum possible eventlog size.-  -- 12 bytes need to be subtracted for message overhead.-  -- The largest stack item is 17 bytes.-  forAll (choose (29, 1000)) k+maxBoundWord16 :: Int+maxBoundWord16 = fromIntegral (maxBound @Word16) -instance Arbitrary BinaryCallStackMessage where-  arbitrary =-    MkBinaryCallStackMessage-      <$> (word32ToWord64 <$> arbitrary)-      <*> arbitrary-      <*> arbitrary+--------------------------------------------------------------------------------+-- Generators +instance Arbitrary IpeId where+  arbitrary :: Gen IpeId+  arbitrary = MkIpeId <$> arbitrary++instance Arbitrary ThreadId where+  arbitrary :: Gen ThreadId+  arbitrary = MkThreadId . fromIntegral <$> arbitrary @Word32+ instance Arbitrary CapabilityId where-  arbitrary =-    MkCapabilityId <$> word32ToWord64 <$> arbitrary+  arbitrary :: Gen CapabilityId+  arbitrary = MkCapabilityId . fromIntegral <$> arbitrary @Word32 -instance Arbitrary BinaryStackItem where+instance Arbitrary CallStackFrame where+  arbitrary :: Gen CallStackFrame   arbitrary =     oneof-      [ BinaryIpe <$> arbitrary-      , BinaryMessage <$> arbitrary <*> arbitrary+      [ CallStackFrameIpe <$> arbitrary+      , CallStackFrameAnn <$> arbitrary <*> arbitrary       ] -instance Arbitrary IpeId where-  arbitrary =-    MkIpeId <$> arbitrary+instance Arbitrary CallStackChunk where+  arbitrary :: Gen CallStackChunk+  arbitrary = MkCallStackChunk <$> arbitrary <*> arbitrary <*> (NonEmpty.toList <$> arbitrary)  instance Arbitrary StringId where-  arbitrary =-    MkStringId <$> arbitrary+  arbitrary :: Gen StringId+  arbitrary = MkStringId <$> arbitrary +instance Arbitrary StringDef where+  arbitrary :: Gen StringDef+  arbitrary = MkStringDef <$> arbitrary <*> arbitraryUnicodeText+ instance Arbitrary SourceLocationId where-  arbitrary =-    MkSourceLocationId <$> arbitrary+  arbitrary :: Gen SourceLocationId+  arbitrary = MkSourceLocationId <$> arbitrary++instance Arbitrary SourceLocationDef where+  arbitrary :: Gen SourceLocationDef+  arbitrary = MkSourceLocationDef <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary ShortText where+  arbitrary :: Gen ShortText+  arbitrary = toShortText <$> arbitraryUnicodeText++arbitraryUnicodeText :: Gen Text+arbitraryUnicodeText = T.pack . getUnicodeString <$> arbitrary