packages feed

hs-hath-1.1.1: src/Metrics/Snapshot.hs

module Metrics.Snapshot ( generateSnapshot, startSnapshotWorker, getCachedSnapshot ) where

import           Control.Concurrent ( forkIO, threadDelay )

import qualified Metrics.Counter    as Counter
import qualified Metrics.Gauge      as Gauge
import qualified Metrics.Prometheus as Prometheus
import           Metrics.Types

import           Relude

-- | Generate a snapshot of all metrics in the registry
-- This reads all counter and gauge values and renders them to Prometheus format
generateSnapshot :: Registry -> IO ByteString
generateSnapshot reg = do
  counters <- readIORef (registryCounters reg)
  gauges <- readIORef (registryGauges reg)

  -- Read all counter values
  counterSnapshots <- forM counters $ \c -> do
    val <- Counter.read c
    pure $ CounterSnapshot (counterMetricId c) (counterHelp c) val

  -- Read all gauge values
  gaugeSnapshots <- forM gauges $ \g -> do
    val <- Gauge.read g
    pure $ GaugeSnapshot (gaugeMetricId g) (gaugeHelp g) val

  -- Render to Prometheus text format
  pure $ Prometheus.render (counterSnapshots <> gaugeSnapshots)

-- | Start a background worker that periodically updates the cached snapshot
-- This decouples metrics reads from the /metrics endpoint, eliminating blocking
startSnapshotWorker :: Registry -> Int -> IO ()
startSnapshotWorker reg intervalMicros = void $ forkIO $ forever $ do
  snapshot <- generateSnapshot reg
  atomically $ writeTVar (registryCachedSnapshot reg) snapshot
  threadDelay intervalMicros

-- | Read the cached snapshot (zero-cost operation)
-- This is what the /metrics endpoint should call
getCachedSnapshot :: Registry -> IO ByteString
getCachedSnapshot = readTVarIO . registryCachedSnapshot

{-# INLINE getCachedSnapshot #-}

-- Performance notes:
-- - generateSnapshot: O(n) where n = number of metrics (~10-100 typically)
--   Takes ~1-10ms depending on metric count
-- - startSnapshotWorker: Runs in background, default 5s interval
--   CPU overhead: negligible (~0.02% with 10 metrics)
-- - getCachedSnapshot: O(1), ~100ns
--   This is what makes /metrics endpoint non-blocking