packages feed

otel-effectful-1.0.0: src/Effectful/OpenTelemetry/Metrics/UpDownCounter.hs

{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

module Effectful.OpenTelemetry.Metrics.UpDownCounter (UpDownCounter, new, add) where

import Control.Concurrent.STM (TVar)
import Control.Concurrent.STM qualified as STM
import Data.Bifunctor (bimap)
import Data.Scientific (Scientific)
import Data.Text (Text)
import Effectful
import Effectful.Dispatch.Static (unsafeEff_)
import Effectful.OpenTelemetry.Metrics.Effect (Metrics)
import Effectful.OpenTelemetry.Metrics.Effect qualified as Metrics
import Effectful.OpenTelemetry.Metrics.Instrument (Instrument)
import Effectful.OpenTelemetry.Metrics.Instrument qualified as Instrument
import Effectful.OpenTelemetry.Metrics.Measurement (NumberDataPoint (..))
import Effectful.OpenTelemetry.Metrics.Metadata (Metadata (..))
import Effectful.OpenTelemetry.Metrics.Sum (Monotonicity (..))
import Effectful.OpenTelemetry.Metrics.Sum qualified as Sum
import Effectful.OpenTelemetry.Timestamp (Timestamp)
import Effectful.OpenTelemetry.Timestamp qualified as Timestamp
import Prelude

-- | A synchronous 'Instrument' which supports increments and decrements.
--
-- Example uses for 'UpDownCounter':
--
-- - the number of active requests
-- - the number of items in a queue
--
-- See <https://opentelemetry.io/docs/specs/otel/metrics/api/#updowncounter the OpenTelemetry spec>.
data UpDownCounter = UpDownCounter
    { name :: Text
    , startTime :: Timestamp
    , value :: TVar (Timestamp, Scientific)
    , metadata :: Metadata
    }

-- | Create a new 'UpDownCounter' and register it to be sampled and exported.
new :: (Metrics :> es) => Text -> Metadata -> Eff es UpDownCounter
new name metadata =
    Metrics.register =<< unsafeEff_ do
        startTime <- Timestamp.now
        value <- STM.newTVarIO (startTime, 0)
        pure UpDownCounter{..}

-- | Add to an 'UpDownCounter'. The amount may be negative.
add :: (Metrics :> es) => UpDownCounter -> Scientific -> Eff es ()
add = (unsafeEff_ .) . addIO

addIO :: UpDownCounter -> Scientific -> IO ()
addIO UpDownCounter{..} inc = do
    time <- Timestamp.now
    STM.atomically . STM.modifyTVar' value $ bimap (const time) (+ inc)

instance Instrument UpDownCounter where
    name = name
    sample UpDownCounter{..} = do
        (time, value) <- STM.readTVarIO value
        pure . pure $
            ( time
            , Sum.measurement NonMonotonic metadata name NumberDataPoint{attributes = metadata.attributes, ..}
            )