packages feed

opentracing-0.1.0.0: OpenTracing/Sampling.hs

{-|
Module: OpenTracing.Sampling

Distributed traces are sampled, meaning they (and the spans that make them up) are
selected to either be reported or not.

This module defines a few different ways to determine if a given trace should be
sampled.
-}

{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData      #-}
{-# LANGUAGE ViewPatterns    #-}

module OpenTracing.Sampling
    ( Sampler(..)
    , constSampler
    , probSampler
    , rateLimitSampler
    )
where

import Control.Monad.IO.Class
import Data.IORef
import Data.Text              (Text)
import OpenTracing.Types      (TraceID (..))
import System.Clock

-- | A `Sampler` is an algorithm for determine if a trace should be reported.
newtype Sampler = Sampler
    { runSampler :: forall m. MonadIO m => TraceID -> Text -> m Bool
      -- ^ Run a sampler, providing it a trace id and the operation of the span.
    }

-- | A `Sampler` that always returns the given value. Useful for debug environments.
constSampler :: Bool -> Sampler
constSampler x = Sampler $ \_ _ -> pure x

-- | A `Sampler` that randomly chooses to report a given percentage of traces. The
-- source of randomness is the ID of the trace.
probSampler
  :: Double -- ^ A probability percentage, between 0.0 and 1.0
  -> Sampler
probSampler (max 0.0 . min 1.0 -> rate) = Sampler $ \trace _ ->
    pure $ boundary >= traceIdLo trace
  where
    boundary = round $ maxRand * rate
    maxRand  = 0x7fffffffffffffff

-- | A `Sampler` that will report the given number of traces per second.
rateLimitSampler
  :: Double -- ^ Traces per second
  -> IO Sampler
rateLimitSampler tps = do
    lim <- newRateLimiter tps (max 1.0 tps)
    return $ Sampler $ \_ _ -> liftIO $ haveCredit lim 1.0

data RateLimiter = RateLimiter
    { creds      :: Double
    , balance    :: IORef Double
    , maxBalance :: Double
    , lastTick   :: IORef TimeSpec
    , timeNow    :: IO TimeSpec
    }

newRateLimiter :: Double -> Double -> IO RateLimiter
newRateLimiter creds maxb = RateLimiter
    <$> pure creds
    <*> newIORef maxb
    <*> pure maxb
    <*> (tnow >>= newIORef)
    <*> pure tnow
 where
    tnow = getTime Monotonic

haveCredit :: RateLimiter -> Double -> IO Bool
haveCredit RateLimiter{..} cost = do
    now     <- timeNow
    (lst,t) <- atomicModifyIORef' lastTick $ \x ->
                    if now > x then (now,(x,now)) else (x,(x,x))

    atomicModifyIORef' balance $ \bal -> do
        let elapsed = diffTimeSpec lst t
        let bal'    = min maxBalance (bal + (realToFrac (sec elapsed) * creds))
        if bal' >= cost then
            (bal' - cost, True)
        else
            (bal', False)