packages feed

prometheus-wai 0.1.0.0 → 0.2.0.0

raw patch · 3 files changed

+80/−27 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- System.Metrics.Prometheus.Wai.Middleware: [metricsEndpointLastSecond] :: MetricsEndpoint -> Maybe (IO RegistrySample)
- System.Metrics.Prometheus.Wai.Middleware: [metricsEndpointRegistry] :: MetricsEndpoint -> Registry
+ System.Metrics.Prometheus.Wai.Middleware: [metricsEndpointCacheDurationNanos] :: MetricsEndpoint -> Maybe Word64
+ System.Metrics.Prometheus.Wai.Middleware: [metricsEndpointSample] :: MetricsEndpoint -> IO RegistrySample
+ System.Metrics.Prometheus.Wai.Middleware: setSampleCache :: Word64 -> MetricsEndpoint -> MetricsEndpoint
+ System.Metrics.Prometheus.Wai.Middleware: withLastSecondSamples :: IO RegistrySample -> MetricsEndpoint -> MetricsEndpoint
- System.Metrics.Prometheus.Wai.Middleware: MetricsEndpoint :: ByteString -> Registry -> Maybe (IO RegistrySample) -> MetricsEndpoint
+ System.Metrics.Prometheus.Wai.Middleware: MetricsEndpoint :: ByteString -> IO RegistrySample -> Maybe Word64 -> MetricsEndpoint
- System.Metrics.Prometheus.Wai.Middleware: metricsEndpointMiddleware :: MetricsEndpoint -> Middleware
+ System.Metrics.Prometheus.Wai.Middleware: metricsEndpointMiddleware :: MonadIO m => MetricsEndpoint -> m Middleware

Files

CHANGELOG.md view
@@ -1,6 +1,16 @@ # Changelog -## [0.1.0.0] - 2025-07-14+## [0.2.0.0] - 2025-12-17++### Added++* Support for sample caching in the metrics endpoint middleware++### Changed++* Simplify the metrics endpoint API even further++## [0.1.0.0] - 2025-12-17  ### Changed 
prometheus-wai.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           prometheus-wai-version:        0.1.0.0+version:        0.2.0.0 description:    Prometheus metrics for WAI applications homepage:       https://github.com/NorfairKing/prometheus-wai#readme bug-reports:    https://github.com/NorfairKing/prometheus-wai/issues
src/System/Metrics/Prometheus/Wai/Middleware.hs view
@@ -9,15 +9,20 @@     combineRegistrySample,     MetricsEndpoint (..),     defaultMetricsEndpoint,+    withLastSecondSamples,+    setSampleCache,     metricsEndpointMiddleware,   ) where -import Control.Monad (forM)+import Control.Concurrent.MVar+import Control.Monad+import Control.Monad.IO.Class import Data.ByteString (ByteString) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import qualified Data.Text as T+import Data.Word import GHC.Clock (getMonotonicTimeNSec) import qualified Network.HTTP.Types as HTTP import Network.Wai as Wai (Middleware)@@ -110,35 +115,73 @@ data MetricsEndpoint = MetricsEndpoint   { -- | Path to serve metrics on, e.g. @"/metrics"@     metricsEndpointPath :: ByteString,-    -- | Registry to sample from-    metricsEndpointRegistry :: Registry,-    -- | Extra samples to be collected 'at the last second', to be combined-    -- with the main registry samples-    metricsEndpointLastSecond :: Maybe (IO Prometheus.RegistrySample)+    -- | Samples to collect.+    --+    -- Typically obtained via 'Registry.sample' but you can add others that you collect at the last second.+    metricsEndpointSample :: IO Prometheus.RegistrySample,+    -- | Cache duration in nanoseconds for the metrics samples.+    --+    -- You can set this to use multiple prometheus fetchers for redundancy+    -- without resampling every time.+    --+    -- Nothing means no caching+    metricsEndpointCacheDurationNanos :: Maybe Word64   } +-- | Create a default 'MetricsEndpoint' serving at @"/metrics"@ with samples+-- from the given registry. defaultMetricsEndpoint :: Registry -> MetricsEndpoint defaultMetricsEndpoint registry =   MetricsEndpoint     { metricsEndpointPath = "/metrics",-      metricsEndpointRegistry = registry,-      metricsEndpointLastSecond = Nothing+      metricsEndpointSample = Registry.sample registry,+      metricsEndpointCacheDurationNanos = Nothing     } -metricsEndpointMiddleware :: MetricsEndpoint -> Wai.Middleware-metricsEndpointMiddleware MetricsEndpoint {..} application request sendResponse =-  if Request.rawPathInfo request == metricsEndpointPath-    then do-      let sampleRegistry = Registry.sample metricsEndpointRegistry-      finalSamples <- case metricsEndpointLastSecond of-        Nothing -> sampleRegistry-        Just lastSecondSample ->-          combineRegistrySample-            <$> sampleRegistry-            <*> lastSecondSample-      sendResponse-        $ Wai.responseBuilder-          HTTP.ok200-          [(HTTP.hContentType, "text/plain; version=0.0.4; charset=utf-8")]-        $ Prometheus.encodeMetrics finalSamples-    else application request sendResponse+-- | Combine the given last second samples with the existing samples of the+-- endpoint.+withLastSecondSamples :: IO Prometheus.RegistrySample -> MetricsEndpoint -> MetricsEndpoint+withLastSecondSamples lastSecondSamples endpoint =+  endpoint+    { metricsEndpointSample =+        combineRegistrySample+          <$> metricsEndpointSample endpoint+          <*> lastSecondSamples+    }++setSampleCache :: Word64 -> MetricsEndpoint -> MetricsEndpoint+setSampleCache cacheDurationNanos endpoint = endpoint {metricsEndpointCacheDurationNanos = Just cacheDurationNanos}++metricsEndpointMiddleware :: (MonadIO m) => MetricsEndpoint -> m Wai.Middleware+metricsEndpointMiddleware MetricsEndpoint {..} =+  -- This function is almost always called in something like LoggingT IO, so we+  -- might as well lift already.+  liftIO $ do+    mCache <- forM metricsEndpointCacheDurationNanos $ \cacheDurationNanos -> do+      begin <- getMonotonicTimeNSec+      firstSample <- metricsEndpointSample+      var <- newMVar (begin, firstSample)+      pure (cacheDurationNanos, var)++    let sampleWithCache :: IO Prometheus.RegistrySample+        sampleWithCache = case mCache of+          Nothing -> metricsEndpointSample+          Just (cacheDurationNanos, var) -> do+            modifyMVar var $ \lastTup@(lastSampleTime, lastSample) -> do+              now <- getMonotonicTimeNSec+              if now - lastSampleTime < cacheDurationNanos+                then pure (lastTup, lastSample)+                else do+                  newSample <- metricsEndpointSample+                  let combinedSample = combineRegistrySample lastSample newSample+                  let newTup = (now, combinedSample)+                  pure (newTup, combinedSample)++    pure $ \application request sendResponse ->+      if Request.rawPathInfo request == metricsEndpointPath+        then do+          samples <- sampleWithCache+          sendResponse $+            Wai.responseBuilder HTTP.ok200 [(HTTP.hContentType, "text/plain; version=0.0.4; charset=utf-8")] $+              Prometheus.encodeMetrics samples+        else application request sendResponse