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 +11/−1
- prometheus-wai.cabal +1/−1
- src/System/Metrics/Prometheus/Wai/Middleware.hs +68/−25
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