tracy-profiler-0.2.0.0: src/System/Tracy/Zone.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ImplicitParams #-}
module System.Tracy.Zone
( -- * Declare zones
withSrcLoc
, withSourceLocation
-- * Update zone context
, text
, name
, color
, value
-- * Internals
, withSrcLocImpl
, allocSrcloc
) where
import Control.Monad.IO.Class (MonadIO(..))
import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.Word
#ifdef TRACY_ENABLE
import Control.Exception (bracket)
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
import Data.ByteString.Char8 qualified as ByteString
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Text.Foreign qualified as Text
import Foreign.C.ConstPtr (ConstPtr(..))
import Foreign.C.String (peekCAString)
import Foreign.Storable (peek)
import GHC.Stack
import GHC.Stack.Types qualified as GHC
#ifndef ZONES_UNSAFE
import Control.Concurrent (isCurrentThreadBound, rtsSupportsBoundThreads)
-- !unsafe
#endif
#ifdef ZONES_PEDANTIC
import System.Exit (die)
-- pedantic
#endif
import System.Tracy.FFI qualified as FFI
-- enable
#endif
import System.Tracy.FFI.Types qualified as FFI
import System.Tracy.SourceLocation (SourceLocation(..))
{- | Allocate SrcLoc and run a Zone with it.
It will produce a @?zoneCtx@ implicit for the zone functions to work.
@
{-# LANGUAGE OverloadedLabels #-} -- for colors
{-# LANGUAGE OverloadedStrings #-} -- for names
import System.Tracy.Zone qualified as Zone
rendering = Zone.withSrcLoc "rendering" #yellow do
-- ...
@
NB: With the threaded runtime, zones can only be used on a bound thread.
Main thread is safe, but the rest should use one of forkOS/asyncBound/runInBoundThread.
The non-threaded runtime has no bound threads and runs everything on a single OS thread, so the check is skipped there.
Zones from different Haskell threads must not overlap in that case, or the capture will fail.
-}
{-# INLINE withSrcLoc #-}
withSrcLoc
#ifdef TRACY_ENABLE
:: ( HasCallStack
, MonadUnliftIO m
)
#else
:: ()
#endif
=> ByteString -- ^ Function name (if used as a top-level wrapper) or section name (in a middle of a @do@ block).
-> FFI.Color
-> ((?zoneCtx :: FFI.TracyCZoneCtx) => m a)
-> m a
#ifndef TRACY_ENABLE
withSrcLoc _function _col action = let ?zoneCtx = FFI.nullTracyCZoneCtx in action
#else
withSrcLoc function col action = withRunInIO \inIO -> do
-- XXX: breaking into non-public API from GHC.Stack.Types to short-circuit `srcLocFile` stringification
case callStack of
GHC.PushCallStack _myself GHC.SrcLoc{srcLocStartLine, srcLocFile} _next ->
withSrcLocImpl
(fromIntegral srcLocStartLine)
(ByteString.pack srcLocFile) -- XXX: srcLocFile is originally an Addr#. So pack should be optimized down to FinalPtr too.
function -- XXX: IsString instance has an unsafePackLiteral rule for string literals
col
inIO
action
_EmptyOrFreeze ->
error "withSrcLoc ought to have HasCallStack in context"
#endif
{- | Allocate SrcLoc and run a Zone with it.
This will copy the strings into a one-time temporary buffer and feed it into emitZoneBeginAlloc.
You'd better have those bytestrings come from static literals to avoid even more intermediate allocations.
-}
{-# INLINE withSrcLocImpl #-}
withSrcLocImpl
:: Word32
-> ByteString
-> ByteString
-> FFI.Color
-> (m a -> IO a)
-> ((?zoneCtx :: FFI.TracyCZoneCtx) => m a)
-> IO a
#ifndef TRACY_ENABLE
withSrcLocImpl _line _file _function _col inIO action =
inIO $ let ?zoneCtx = FFI.nullTracyCZoneCtx in action
#else
withSrcLocImpl line file function col inIO action =
runZone
(pure $ ByteString.unpack file <> ":" <> show line)
inIO
(allocSrcloc line file function Nothing col >>= \srcloc -> FFI.emitZoneBeginAlloc srcloc 1)
action
-- TRACY_ENABLE
#endif
{-# INLINE withSourceLocation #-}
#ifdef TRACY_ENABLE
withSourceLocation
:: MonadUnliftIO m
=> SourceLocation
-> ((?zoneCtx :: FFI.TracyCZoneCtx) => m a)
-> m a
withSourceLocation location@(SourceLocation locationData) action = withRunInIO \inIO ->
runZone
(describeSourceLocation location)
inIO
(FFI.emitZoneBegin locationData 1)
action
#else
withSourceLocation
:: SourceLocation
-> ((?zoneCtx :: FFI.TracyCZoneCtx) => m a)
-> m a
withSourceLocation _location action = let ?zoneCtx = FFI.nullTracyCZoneCtx in action
#endif
#ifdef TRACY_ENABLE
{-# INLINE runZone #-}
runZone
:: IO String
-> (m a -> IO a)
-> IO FFI.TracyCZoneCtx
-> ((?zoneCtx :: FFI.TracyCZoneCtx) => m a)
-> IO a
runZone location inIO beginZone action = do
onStableOsThread <- canStartZone
if onStableOsThread then
bracket beginZone FFI.emitZoneEnd \ctx -> inIO $ let ?zoneCtx = ctx in action
else
unboundZone location . inIO $ let ?zoneCtx = FFI.nullTracyCZoneCtx in action
{-# INLINE canStartZone #-}
canStartZone :: IO Bool
#ifdef ZONES_UNSAFE
canStartZone = pure True
#else
canStartZone = if rtsSupportsBoundThreads then isCurrentThreadBound else pure True
#endif
unboundZone :: IO String -> IO a -> IO a
#ifdef ZONES_PEDANTIC
unboundZone location _withoutZone = do
{-
XXX: Will not actually crash the whole program, only the thread.
But at least there would be a console notice when this happens.
-}
zoneLocation <- location
die $ zoneLocation <> " Starting a zone on unbound thread"
#else
unboundZone _location withoutZone = withoutZone
#endif
describeSourceLocation :: SourceLocation -> IO String
describeSourceLocation (SourceLocation (ConstPtr locationData)) = do
FFI.SourceLocationData{file, line} <- peek locationData
fileName <- peekCAString file
pure $ fileName <> ":" <> show line
#endif
{- | Prepare a single-use location identifier
Returns a source location identifier corresponding to an *allocated source location*.
As these functions do not require the provided string data to be available after they return, the calling code is free to deallocate them at any time afterward.
This way, the string lifetime requirements described in section 3.1 are relaxed.
The variable representing an allocated source location is of an opaque type.
After it is passed to one of the zone begin functions, its value *cannot be reused* (the variable is consumed).
You must allocate a new source location for each zone begin event, even if the location data would be the same as in the previous instance.
-}
allocSrcloc
:: Word32
-> ByteString
-> ByteString
-> Maybe ByteString
-> FFI.Color
-> IO FFI.SrcLoc
#ifndef TRACY_ENABLE
allocSrcloc _line _source _function _name _col = pure $ FFI.SrcLoc 0
#else
allocSrcloc line source function name_ col =
unsafeUseAsCStringLen source \(sourcePtr, sourceSz) ->
unsafeUseAsCStringLen function \(functionPtr, functionSz) ->
case name_ of
Nothing ->
FFI.allocSrcloc
line
(ConstPtr sourcePtr) (fromIntegral sourceSz)
(ConstPtr functionPtr) (fromIntegral functionSz)
col
Just name' ->
unsafeUseAsCStringLen name' \(namePtr, nameSz) ->
FFI.allocSrclocName
line
(ConstPtr sourcePtr) (fromIntegral sourceSz)
(ConstPtr functionPtr) (fromIntegral functionSz)
(ConstPtr namePtr) (fromIntegral nameSz)
col
#endif
{-# INLINE text #-}
#ifndef TRACY_ENABLE
text :: MonadIO m => Text -> m ()
text _txt = pure ()
#else
text :: (MonadIO m, ?zoneCtx :: FFI.TracyCZoneCtx) => Text -> m ()
text txt = liftIO $
Text.withCStringLen txt \(txtPtr, txtSz) ->
FFI.emitZoneText ?zoneCtx (ConstPtr txtPtr) (FFI.clampTextBytes txtSz)
#endif
{-# INLINE name #-}
#ifndef TRACY_ENABLE
name :: MonadIO m => Text -> m ()
name _txt = pure ()
#else
name :: (MonadIO m, ?zoneCtx :: FFI.TracyCZoneCtx) => Text -> m ()
name txt = liftIO $
Text.withCStringLen txt \(txtPtr, txtSz) ->
FFI.emitZoneName ?zoneCtx (ConstPtr txtPtr) (FFI.clampTextBytes txtSz)
#endif
{-# INLINE color #-}
#ifndef TRACY_ENABLE
color :: MonadIO m => FFI.Color -> m ()
color _col = pure ()
#else
color :: (MonadIO m, ?zoneCtx :: FFI.TracyCZoneCtx) => FFI.Color -> m ()
color col = liftIO $ FFI.emitZoneColor ?zoneCtx col
#endif
{-# INLINE value #-}
#ifndef TRACY_ENABLE
value :: MonadIO m => Word64 -> m ()
value _val = pure ()
#else
value :: (MonadIO m, ?zoneCtx :: FFI.TracyCZoneCtx) => Word64 -> m ()
value val = liftIO $ FFI.emitZoneValue ?zoneCtx val
#endif