packages feed

hs-opentelemetry-sdk-1.0.0.0: src/OpenTelemetry/MetricReader.hs

{-# LANGUAGE NumericUnderscores #-}

{- | Periodic metric export (spec-style periodic reader) built on 'collectResourceMetrics'.

Stop the reader before 'meterProviderShutdown' on the associated 'MeterProvider'.

Applications that scrape metrics on demand (\"pull\") can skip a periodic reader and instead
call 'exportMetricsOnce' (or 'collectResourceMetrics' plus a text\/OTLP encoder) from their HTTP handler.
-}
module OpenTelemetry.MetricReader (
  PeriodicMetricReaderOptions (..),
  defaultPeriodicMetricReaderOptions,
  periodicMetricReaderOptionsFromEnv,
  PeriodicMetricReaderHandle (..),
  forkPeriodicMetricReader,
  exportMetricsOnce,
) where

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (Async, async, cancel, waitCatch)
import Control.Monad (forever)
import qualified Data.Vector as V
import OpenTelemetry.Environment (lookupMetricExportIntervalMillis)
import OpenTelemetry.Exporter.Metric (MetricExporter (..))
import OpenTelemetry.Internal.Common.Types (ExportResult)
import OpenTelemetry.MeterProvider (SdkMeterEnv, collectResourceMetrics)


-- | Interval between automatic export cycles (microseconds).
newtype PeriodicMetricReaderOptions = PeriodicMetricReaderOptions
  { periodicIntervalMicros :: Int
  }


-- | Default: 60 seconds between exports (overridden by 'periodicMetricReaderOptionsFromEnv' when @OTEL_METRIC_EXPORT_INTERVAL@ is set).
defaultPeriodicMetricReaderOptions :: PeriodicMetricReaderOptions
defaultPeriodicMetricReaderOptions =
  PeriodicMetricReaderOptions
    { periodicIntervalMicros = 60_000_000
    }


-- | Use @OTEL_METRIC_EXPORT_INTERVAL@ (milliseconds) when set and positive; otherwise same as 'defaultPeriodicMetricReaderOptions'.
periodicMetricReaderOptionsFromEnv :: IO PeriodicMetricReaderOptions
periodicMetricReaderOptionsFromEnv = do
  mi <- lookupMetricExportIntervalMillis
  pure $
    PeriodicMetricReaderOptions
      { periodicIntervalMicros = case mi of
          Just ms | ms > 0 -> ms * 1000
          _ -> periodicIntervalMicros defaultPeriodicMetricReaderOptions
      }


-- | Handle for a background thread that exports on an interval.
data PeriodicMetricReaderHandle = PeriodicMetricReaderHandle
  { periodicMetricReaderAsync :: !(Async ())
  , stopPeriodicMetricReader :: !(IO ())
  }


-- | One collect + export (for tests and custom schedulers).
exportMetricsOnce :: SdkMeterEnv -> MetricExporter -> IO ExportResult
exportMetricsOnce env ex = do
  batches <- collectResourceMetrics env
  metricExporterExport ex (V.fromList batches)


-- | Spawn an async loop: export on each interval until stopped.
forkPeriodicMetricReader
  :: SdkMeterEnv
  -> MetricExporter
  -> PeriodicMetricReaderOptions
  -> IO PeriodicMetricReaderHandle
forkPeriodicMetricReader env ex opts = do
  a <-
    async $
      forever $ do
        _ <- exportMetricsOnce env ex
        threadDelay (periodicIntervalMicros opts)
  let stop = do
        cancel a
        _ <- waitCatch a
        _ <- exportMetricsOnce env ex
        _ <- metricExporterShutdown ex
        pure ()
  pure $
    PeriodicMetricReaderHandle
      { periodicMetricReaderAsync = a
      , stopPeriodicMetricReader = stop
      }