{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE OverloadedStrings #-}
module OpenTelemetry.Plugin.Shared
(
makeWrapperPluginPasses
, initializeTopLevelContext
, getTopLevelContext
, setRootModuleNames
, isRootModule
, flush
, getSampler
) where
import Control.Concurrent.MVar (MVar)
import Control.Monad.IO.Class (liftIO)
import Data.ByteString (ByteString)
import Data.Set (Set)
import Data.Text (Text)
import OpenTelemetry.Context (Context)
import OpenTelemetry.Trace.Sampler (Sampler(..), SamplingResult(..))
import Prelude hiding (span)
import System.Random.MWC (GenIO)
import OpenTelemetry.Trace
( Attribute(..)
, PrimitiveAttribute(..)
, InstrumentationLibrary(..)
, Span
, SpanArguments(..)
, SpanContext(..)
, Tracer
, TracerProvider
, TracerProviderOptions(..)
)
import qualified Control.Concurrent.MVar as MVar
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text.Encoding
import qualified Data.Version as Version
import qualified OpenTelemetry.Context as Context
import qualified OpenTelemetry.Propagator.W3CBaggage as W3CBaggage
import qualified OpenTelemetry.Propagator.W3CTraceContext as W3CTraceContext
import qualified OpenTelemetry.Trace as Trace
import qualified OpenTelemetry.Trace.Core as Trace.Core
import qualified OpenTelemetry.Trace.Sampler as Sampler
import qualified OpenTelemetry.Trace.TraceState as TraceState
import qualified Paths_opentelemetry_plugin as Paths
import qualified System.Environment as Environment
import qualified System.IO.Unsafe as Unsafe
import qualified System.Random.MWC as MWC
import qualified Text.Read as Read
getSampler :: IO (Maybe Sampler)
getSampler :: IO (Maybe Sampler)
getSampler = do
Maybe String
maybeSampler <- String -> IO (Maybe String)
Environment.lookupEnv String
"OTEL_TRACES_SAMPLER"
Maybe String
maybeRatio <- String -> IO (Maybe String)
Environment.lookupEnv String
"OTEL_TRACES_SAMPLER_ARG"
forall (f :: * -> *) a. Applicative f => a -> f a
pure do
String
"spanratio" <- Maybe String
maybeSampler
String
ratioString <- Maybe String
maybeRatio
Double
ratio <- forall a. Read a => String -> Maybe a
Read.readMaybe String
ratioString
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Double -> Sampler
spanRatioBased Double
ratio)
generator :: GenIO
generator :: GenIO
generator = forall a. IO a -> a
Unsafe.unsafePerformIO IO GenIO
MWC.createSystemRandom
{-# NOINLINE generator #-}
spanRatioBased :: Double -> Sampler
spanRatioBased :: Double -> Sampler
spanRatioBased Double
fraction = Sampler
{ getDescription :: Text
getDescription =
Text
"SpanRatioBased{" forall a. Semigroup a => a -> a -> a
<> String -> Text
Text.pack (forall a. Show a => a -> String
show Double
fraction) forall a. Semigroup a => a -> a -> a
<> Text
"}"
, shouldSample :: Context
-> TraceId
-> Text
-> SpanArguments
-> IO (SamplingResult, HashMap Text Attribute, TraceState)
shouldSample = \Context
context TraceId
traceId_ Text
name SpanArguments
spanArguments -> do
case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HashMap.lookup Text
"sample" (SpanArguments -> HashMap Text Attribute
attributes SpanArguments
spanArguments) of
Just (AttributeValue (BoolAttribute Bool
True)) -> do
Double
random <- forall a (m :: * -> *).
(Variate a, PrimMonad m) =>
(a, a) -> Gen (PrimState m) -> m a
MWC.uniformR (Double
0, Double
1) GenIO
generator
let samplingResult :: SamplingResult
samplingResult =
if Double
random forall a. Ord a => a -> a -> Bool
< Double
fraction then SamplingResult
RecordAndSample else SamplingResult
Drop
TraceState
traceState_ <- case Context -> Maybe Span
Context.lookupSpan Context
context of
Maybe Span
Nothing ->
forall (f :: * -> *) a. Applicative f => a -> f a
pure TraceState
TraceState.empty
Just Span
span ->
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap SpanContext -> TraceState
traceState (forall (m :: * -> *). MonadIO m => Span -> m SpanContext
Trace.Core.getSpanContext Span
span)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SamplingResult
samplingResult, forall k v. HashMap k v
HashMap.empty, TraceState
traceState_)
Maybe Attribute
_ ->
Sampler
-> Context
-> TraceId
-> Text
-> SpanArguments
-> IO (SamplingResult, HashMap Text Attribute, TraceState)
shouldSample Sampler
Sampler.alwaysOn Context
context TraceId
traceId_ Text
name SpanArguments
spanArguments
}
tracerProvider :: TracerProvider
tracerProvider :: TracerProvider
tracerProvider = forall a. IO a -> a
Unsafe.unsafePerformIO do
([Processor]
processors, TracerProviderOptions
options) <- IO ([Processor], TracerProviderOptions)
Trace.getTracerProviderInitializationOptions
Maybe Sampler
maybeSampler <- IO (Maybe Sampler)
getSampler
let newOptions :: TracerProviderOptions
newOptions =
case Maybe Sampler
maybeSampler of
Maybe Sampler
Nothing -> TracerProviderOptions
options
Just Sampler
sampler -> TracerProviderOptions
options{ tracerProviderOptionsSampler :: Sampler
tracerProviderOptionsSampler = Sampler
sampler }
TracerProvider
tracerProvider_ <- forall (m :: * -> *).
MonadIO m =>
[Processor] -> TracerProviderOptions -> m TracerProvider
Trace.createTracerProvider [Processor]
processors TracerProviderOptions
newOptions
forall (m :: * -> *). MonadIO m => TracerProvider -> m ()
Trace.setGlobalTracerProvider TracerProvider
tracerProvider_
forall (f :: * -> *) a. Applicative f => a -> f a
pure TracerProvider
tracerProvider_
{-# NOINLINE tracerProvider #-}
tracer :: Tracer
tracer :: Tracer
tracer =
TracerProvider -> InstrumentationLibrary -> TracerOptions -> Tracer
Trace.makeTracer TracerProvider
tracerProvider InstrumentationLibrary
instrumentationLibrary TracerOptions
Trace.tracerOptions
where
instrumentationLibrary :: InstrumentationLibrary
instrumentationLibrary =
InstrumentationLibrary
{ libraryName :: Text
libraryName = Text
"opentelemetry-plugin"
, libraryVersion :: Text
libraryVersion = String -> Text
Text.pack (Version -> String
Version.showVersion Version
Paths.version)
}
makeWrapperPluginPasses
:: Bool
-> IO Context
-> Text
-> IO (IO Context, IO (), IO ())
makeWrapperPluginPasses :: Bool -> IO Context -> Text -> IO (IO Context, IO (), IO ())
makeWrapperPluginPasses Bool
sample IO Context
getParentContext Text
label = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
MVar Span
spanMVar <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a. IO (MVar a)
MVar.newEmptyMVar
MVar Context
currentContextMVar <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a. IO (MVar a)
MVar.newEmptyMVar
let beginPass :: IO ()
beginPass = do
Context
parentContext <- IO Context
getParentContext
let spanArguments :: SpanArguments
spanArguments =
if Bool
sample
then
SpanArguments
Trace.defaultSpanArguments
{ attributes :: HashMap Text Attribute
attributes =
forall k v. Hashable k => k -> v -> HashMap k v
HashMap.singleton Text
"sample" (PrimitiveAttribute -> Attribute
AttributeValue (Bool -> PrimitiveAttribute
BoolAttribute Bool
True))
}
else
SpanArguments
Trace.defaultSpanArguments
Span
passSpan <- forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Tracer -> Context -> Text -> SpanArguments -> m Span
Trace.createSpan Tracer
tracer Context
parentContext Text
label SpanArguments
spanArguments
Bool
_ <- forall a. MVar a -> a -> IO Bool
MVar.tryPutMVar MVar Span
spanMVar Span
passSpan
let currentContext :: Context
currentContext = Span -> Context -> Context
Context.insertSpan Span
passSpan Context
parentContext
Bool
_ <- forall a. MVar a -> a -> IO Bool
MVar.tryPutMVar MVar Context
currentContextMVar Context
currentContext
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
let endPass :: IO ()
endPass = do
Span
passSpan <- forall a. MVar a -> IO a
MVar.readMVar MVar Span
spanMVar
forall (m :: * -> *). MonadIO m => Span -> Maybe Timestamp -> m ()
Trace.endSpan Span
passSpan forall a. Maybe a
Nothing
forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. MVar a -> IO a
MVar.readMVar MVar Context
currentContextMVar, IO ()
beginPass, IO ()
endPass)
topLevelContextMVar :: MVar Context
topLevelContextMVar :: MVar Context
topLevelContextMVar = forall a. IO a -> a
Unsafe.unsafePerformIO forall a. IO (MVar a)
MVar.newEmptyMVar
{-# NOINLINE topLevelContextMVar #-}
getTopLevelSpan :: IO Span
getTopLevelSpan :: IO Span
getTopLevelSpan = do
Maybe ByteString
traceParent <- String -> IO (Maybe ByteString)
lookupEnv String
"TRACEPARENT"
Maybe ByteString
traceState_ <- String -> IO (Maybe ByteString)
lookupEnv String
"TRACESTATE"
case Maybe ByteString -> Maybe ByteString -> Maybe SpanContext
W3CTraceContext.decodeSpanContext Maybe ByteString
traceParent Maybe ByteString
traceState_ of
Just SpanContext
spanContext ->
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SpanContext -> Span
Trace.Core.wrapSpanContext SpanContext
spanContext)
Maybe SpanContext
Nothing -> do
Timestamp
timestamp <- forall (m :: * -> *). MonadIO m => m Timestamp
Trace.Core.getTimestamp
let arguments :: SpanArguments
arguments =
SpanArguments
Trace.defaultSpanArguments
{ startTime :: Maybe Timestamp
startTime = forall a. a -> Maybe a
Just Timestamp
timestamp }
Span
span <- forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Tracer -> Context -> Text -> SpanArguments -> m Span
Trace.createSpan Tracer
tracer Context
Context.empty Text
"opentelemetry GHC plugin" SpanArguments
arguments
forall (m :: * -> *). MonadIO m => Span -> Maybe Timestamp -> m ()
Trace.endSpan Span
span (forall a. a -> Maybe a
Just Timestamp
timestamp)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Span
span
getTopLevelBaggage :: IO Context
getTopLevelBaggage :: IO Context
getTopLevelBaggage = do
Maybe ByteString
maybeBytes <- String -> IO (Maybe ByteString)
lookupEnv String
"BAGGAGE"
case Maybe ByteString
maybeBytes forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ByteString -> Maybe Baggage
W3CBaggage.decodeBaggage of
Maybe Baggage
Nothing -> forall (f :: * -> *) a. Applicative f => a -> f a
pure Context
Context.empty
Just Baggage
baggage -> forall (f :: * -> *) a. Applicative f => a -> f a
pure (Baggage -> Context -> Context
Context.insertBaggage Baggage
baggage Context
Context.empty)
lookupEnv :: String -> IO (Maybe ByteString)
lookupEnv :: String -> IO (Maybe ByteString)
lookupEnv = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> ByteString
encode)) String -> IO (Maybe String)
Environment.lookupEnv
where
encode :: String -> ByteString
encode = Text -> ByteString
Text.Encoding.encodeUtf8 forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
Text.pack
initializeTopLevelContext :: IO ()
initializeTopLevelContext :: IO ()
initializeTopLevelContext = do
Span
span <- IO Span
getTopLevelSpan
Context
context <- IO Context
getTopLevelBaggage
let contextWithSpan :: Context
contextWithSpan = Span -> Context -> Context
Context.insertSpan Span
span Context
context
Bool
_ <- forall a. MVar a -> a -> IO Bool
MVar.tryPutMVar MVar Context
topLevelContextMVar Context
contextWithSpan
forall (m :: * -> *) a. Monad m => a -> m a
return ()
getTopLevelContext :: IO Context
getTopLevelContext :: IO Context
getTopLevelContext = forall a. MVar a -> IO a
MVar.readMVar MVar Context
topLevelContextMVar
rootModuleNamesMVar :: MVar (Set Text)
rootModuleNamesMVar :: MVar (Set Text)
rootModuleNamesMVar = forall a. IO a -> a
Unsafe.unsafePerformIO forall a. IO (MVar a)
MVar.newEmptyMVar
{-# NOINLINE rootModuleNamesMVar #-}
setRootModuleNames :: [String] -> IO ()
setRootModuleNames :: [String] -> IO ()
setRootModuleNames [String]
rootModuleNames = do
let set :: Set Text
set = forall a. Ord a => [a] -> Set a
Set.fromList (forall a b. (a -> b) -> [a] -> [b]
map String -> Text
Text.pack [String]
rootModuleNames)
Bool
_ <- forall a. MVar a -> a -> IO Bool
MVar.tryPutMVar MVar (Set Text)
rootModuleNamesMVar Set Text
set
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
isRootModule :: String -> IO Bool
isRootModule :: String -> IO Bool
isRootModule String
moduleName = do
Set Text
rootModuleNames <- forall a. MVar a -> IO a
MVar.readMVar MVar (Set Text)
rootModuleNamesMVar
forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. Ord a => a -> Set a -> Bool
Set.member (String -> Text
Text.pack String
moduleName) Set Text
rootModuleNames)
flush :: IO ()
flush :: IO ()
flush = do
FlushResult
_ <- forall (m :: * -> *).
MonadIO m =>
TracerProvider -> Maybe Int -> m FlushResult
Trace.Core.forceFlushTracerProvider TracerProvider
tracerProvider forall a. Maybe a
Nothing
FlushResult
_ <- forall (m :: * -> *).
MonadIO m =>
TracerProvider -> Maybe Int -> m FlushResult
Trace.Core.forceFlushTracerProvider TracerProvider
tracerProvider forall a. Maybe a
Nothing
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()