packages feed

tracy-profiler-0.2.0.0: src/System/Tracy.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}

module System.Tracy
  ( -- * Profiler
    withProfiler
  , enabled
  , connected
  , waitConnected
  , waitConnected_

    -- * Program information
  , setThreadName
  , appInfo

    -- * Zones
  , Zone.withSrcLoc

    -- * Frames
  , frameMark
  , frameMark_
  , frameMarkStart
  , frameMarkEnd
  , frameImage

    -- * Messages
  , message
  , messageC
  , messageL
  , messageLC
  , logString
  , logStringL
  , MessageSeverity(..)

    -- * Time
  , getTime

    -- * Sampling profiler
  , beginSamplingProfiling
  , endSamplingProfiling

    -- * Plots
  , plot
  , plotFloat
  , plotInt
  , plotIntegral
  , plotConfig
  , PlotFormat(..)

#ifdef TRACY_FIBERS
    -- * Fibers
  , fiberEnter
  , fiberLeave
#endif

    -- * Colors
  , Color
  ) where

import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO(..))
import Data.Int (Int64)
import Data.Text (Text)
import Data.Word (Word8, Word16)
import GHC.Exts (Addr#, Ptr(..))

#if defined(TRACY_ENABLE)
import Control.Concurrent (threadDelay)
import Data.Maybe (fromMaybe)
import Data.Text.Foreign qualified as Text
import Foreign (castPtr, fromBool, nullPtr)
import Foreign.C.ConstPtr (ConstPtr(..))
import System.Timeout (timeout)
import System.Tracy.FFI qualified as FFI
import System.Tracy.FFI.Types (clampTextBytes)
#endif

import System.Tracy.FFI.Types (Color, MessageSeverity(..), PlotFormat(..))
import System.Tracy.Zone qualified as Zone

#if defined(TRACY_ENABLE) && defined(TRACY_MANUAL_LIFETIME)
import Control.Exception (bracket_)
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
#endif

{- | Start/stop profiler when TRACY_MANUAL_LIFETIME is enabled.
Otherwise does nothing.
-}
#if defined(TRACY_ENABLE) && defined(TRACY_MANUAL_LIFETIME)
withProfiler :: MonadUnliftIO m => m a -> m a
withProfiler action =
  withRunInIO \run ->
    bracket_ FFI.startupProfiler FFI.shutdownProfiler (run action)
#else
withProfiler :: a -> a
withProfiler = id
#endif

enabled :: Bool
#if defined(TRACY_ENABLE)
enabled = True
#else
enabled = False
#endif

{- | Check whether the profiler is connected.
-}
connected :: IO Bool
#if defined(TRACY_ENABLE)
connected = (/= 0) <$> FFI.connected
#else
connected = pure False
#endif

{- | Poll connection status repeatedly until the profiler is connected or the time is up.

Immediately returns False when Tracy is not enabled.
-}
waitConnected
  :: MonadIO m
  => Int       -- ^ Poll interval
  -> Maybe Int -- ^ Maximum waiting time
  -> m Bool
#if defined(TRACY_ENABLE)
waitConnected interval mtimeout =
  liftIO $
    case mtimeout of
      Nothing -> go
      Just t -> fromMaybe False <$> timeout t go
  where
    go = do
      conn <- connected
      if conn then
        pure True
      else
        threadDelay interval >> go
#else
waitConnected _interval _timeout = pure False -- XXX: ignore indefinite waiting
#endif

{- | Wait indefinitely until the profiler connects.

Useful for tests, benches, and other short-lifetime scripts.
-}
waitConnected_ :: IO ()
waitConnected_ = void $ waitConnected 100000 Nothing

setThreadName :: Addr# -> IO ()
#if defined(TRACY_ENABLE)
setThreadName name = FFI.setThreadName (ConstPtr (Ptr name))
#else
setThreadName _name = pure ()
#endif

{-# INLINE frameMark_ #-}
frameMark_ :: MonadIO m => m ()
#if defined(TRACY_ENABLE)
frameMark_ = liftIO $ FFI.emitFrameMark (ConstPtr nullPtr)
#else
frameMark_ = pure ()
#endif

{-# INLINE frameMark #-}
frameMark :: MonadIO m => Addr# -> m ()
#if defined(TRACY_ENABLE)
frameMark name = liftIO $ FFI.emitFrameMark (ConstPtr $ Ptr name)
#else
frameMark _name = pure ()
#endif

{-# INLINE frameMarkStart #-}
frameMarkStart :: MonadIO m => Addr# -> m ()
#if defined(TRACY_ENABLE)
frameMarkStart name = liftIO $ FFI.emitFrameMarkStart (ConstPtr $ Ptr name)
#else
frameMarkStart _name = pure ()
#endif

{-# INLINE frameMarkEnd #-}
frameMarkEnd :: MonadIO m => Addr# -> m ()
#if defined(TRACY_ENABLE)
frameMarkEnd name = liftIO $ FFI.emitFrameMarkEnd (ConstPtr $ Ptr name)
#else
frameMarkEnd _name = pure ()
#endif

{-# INLINE frameImage #-}
frameImage :: MonadIO m => Ptr pixels -> Word16 -> Word16 -> Word8 -> Bool -> m ()
#if defined(TRACY_ENABLE)
frameImage image width height lag flipped = liftIO $
  FFI.emitFrameImage (ConstPtr $ castPtr image) width height lag (fromBool flipped)
#else
frameImage _image _width _height _lag _flipped = pure ()
#endif

{-# INLINE message #-}
message :: MonadIO m => Text -> m ()
message = messageC 0

{-# INLINE messageC #-}
messageC :: MonadIO m => Color -> Text -> m ()
messageC = logString MessageSeverityInfo

{-# INLINE messageL #-}
messageL :: MonadIO m => Addr# -> m ()
messageL = messageLC 0

{-# INLINE messageLC #-}
messageLC :: MonadIO m => Color -> Addr# -> m ()
messageLC = logStringL MessageSeverityInfo

{-# INLINE logString #-}
logString :: MonadIO m => MessageSeverity -> Color -> Text -> m ()
#if defined(TRACY_ENABLE)
logString severity color txt = liftIO $
  Text.withCStringLen txt \(txtPtr, txtSz) ->
    FFI.emitLogString (fromIntegral $ fromEnum severity) color 0 (clampTextBytes txtSz) (ConstPtr txtPtr)
#else
logString _severity _color _txt = pure ()
#endif

{-# INLINE logStringL #-}
logStringL :: MonadIO m => MessageSeverity -> Color -> Addr# -> m ()
#if defined(TRACY_ENABLE)
logStringL severity color txt = liftIO $
  FFI.emitLogStringL (fromIntegral $ fromEnum severity) color 0 (ConstPtr (Ptr txt))
#else
logStringL _severity _color _txt = pure ()
#endif

{-# INLINE getTime #-}
getTime :: MonadIO m => m Int64
#if defined(TRACY_ENABLE)
getTime = liftIO FFI.getTime
#else
getTime = pure 0
#endif

beginSamplingProfiling :: MonadIO m => m Bool
#if defined(TRACY_ENABLE)
beginSamplingProfiling = liftIO $ (/= 0) <$> FFI.beginSamplingProfiling
#else
beginSamplingProfiling = pure False
#endif

endSamplingProfiling :: MonadIO m => m ()
#if defined(TRACY_ENABLE)
endSamplingProfiling = liftIO FFI.endSamplingProfiling
#else
endSamplingProfiling = pure ()
#endif

{-# INLINE plot #-}
plot :: MonadIO m => Addr# -> Double -> m ()
#if defined(TRACY_ENABLE)
plot name val = liftIO $
  FFI.emitPlot (ConstPtr (Ptr name)) val
#else
plot _name _val = pure ()
#endif

{-# INLINE plotFloat #-}
plotFloat :: MonadIO m => Addr# -> Float -> m ()
#if defined(TRACY_ENABLE)
plotFloat name val = liftIO $
  FFI.emitPlotFloat (ConstPtr (Ptr name)) val
#else
plotFloat _name _val = pure ()
#endif

{-# INLINE plotInt #-}
plotInt :: MonadIO m => Addr# -> Int -> m ()
plotInt = plotIntegral

{-# INLINE plotIntegral #-}
#if defined(TRACY_ENABLE)
plotIntegral :: (Integral a, MonadIO m) => Addr# -> a -> m ()
plotIntegral name val = liftIO $
  FFI.emitPlotInt (ConstPtr (Ptr name)) (fromIntegral val)
#else
plotIntegral :: Monad m => Addr# -> a -> m ()
plotIntegral _name _val = pure ()
#endif

{-# INLINE plotConfig #-}
plotConfig
  :: MonadIO m
  => Addr#
  -> PlotFormat
  -> Bool -- ^ Step
  -> Bool -- ^ Fill
  -> Color
  -> m ()
#if defined(TRACY_ENABLE)
plotConfig name pf step fill col = liftIO $
  FFI.emitPlotConfig
    (ConstPtr (Ptr name))
    (fromIntegral $ fromEnum pf)
    (fromIntegral $ fromEnum step)
    (fromIntegral $ fromEnum fill)
    col
#else
plotConfig _name _typ _step _fill _col = pure ()
#endif

{- | Record additional information about the profiled application,
  which will be available in the trace description.

  This can include data such as the source repository revision, the
  application's environment (dev/prod), etc.
-}
{-# INLINE appInfo #-}
appInfo :: MonadIO m => Text -> m ()
#if defined(TRACY_ENABLE)
appInfo txt = liftIO $
  Text.withCStringLen txt \(txtPtr, txtSz) ->
    FFI.emitMessageAppinfo (ConstPtr txtPtr) (clampTextBytes txtSz)
#else
appInfo _txt = pure ()
#endif

#ifdef TRACY_FIBERS
{-# INLINE fiberEnter #-}
fiberEnter :: MonadIO m => Addr# -> m ()
#if defined(TRACY_ENABLE)
fiberEnter name = liftIO $
  FFI.fiberEnter (ConstPtr (Ptr name))
#else
fiberEnter _name = pure ()
#endif

{-# INLINE fiberLeave #-}
fiberLeave :: MonadIO m => m ()
#if defined(TRACY_ENABLE)
fiberLeave = liftIO FFI.fiberLeave
#else
fiberLeave = pure ()
#endif
#endif