packages feed

mu-prometheus-0.4.0.0: src/Mu/Instrumentation/Prometheus.hs

{-# language FlexibleContexts    #-}
{-# language OverloadedStrings   #-}
{-# language PolyKinds           #-}
{-# language RankNTypes          #-}
{-# language ScopedTypeVariables #-}
module Mu.Instrumentation.Prometheus (
  initPrometheus
, prometheus
, prometheusWai
) where

import           Control.Concurrent.MVar.Lifted
import           Control.Exception.Lifted
import           Control.Monad.Trans.Control
import           Data.Text                         (Text)
import           Mu.Rpc
import           Mu.Server
import           Network.Wai
import qualified Network.Wai.Middleware.Prometheus as Wai
import           Prometheus

-- Taken from https://github.com/higherkindness/mu-scala/blob/master/modules/metrics/prometheus/src/main/scala/higherkindness/mu/rpc/prometheus/PrometheusMetrics.scala

data MuMetrics
  = MuMetrics {
      activeCalls      :: Gauge
    , messagesSent     :: Vector Label2 Counter
    , messagesReceived :: Vector Label2 Counter
    , callsTotal       :: Vector Label2 Histogram
    }

initPrometheus :: Text -> IO MuMetrics
initPrometheus prefix =
  MuMetrics <$> register (gauge $ Info (prefix <> "_active_calls") "")
            <*> register (vector ("service", "method")
                         $ counter $ Info (prefix <> "_messages_sent") "")
            <*> register (vector ("service", "method")
                         $ counter $ Info (prefix <> "_messages_received") "")
            <*> register (vector ("service", "method")
                         $ histogram (Info (prefix <> "_calls_total") "")
                                     defaultBuckets)

prometheus :: (MonadBaseControl IO m, MonadMonitor m)
           => MuMetrics -> ServerT chn info p m topHs -> ServerT chn info p m topHs
prometheus m = wrapServer (prometheusMetrics m)

prometheusMetrics :: forall m a info. (MonadBaseControl IO m, MonadMonitor m)
                  => MuMetrics -> RpcInfo info -> m a -> m a
prometheusMetrics metrics NoRpcInfo run = do
  incGauge (activeCalls metrics)
  run `finally` decGauge (activeCalls metrics)
prometheusMetrics metrics (RpcInfo _pkg (Service sname _) (Method mname _ _) _ _) run = do
  incGauge (activeCalls metrics)
  withLabel (messagesReceived metrics) (sname, mname) incCounter
  ( do -- We are forced to use a MVar because 'withLabel' only allows IO ()
       r <- liftBaseWith $ \runInIO -> do
         result :: MVar (StM m a) <- newEmptyMVar
         withLabel (callsTotal metrics) (sname, mname) $ \h ->
           h `observeDuration` (runInIO run >>= putMVar result)
         takeMVar result
       x <- restoreM r
       withLabel (messagesSent metrics) (sname, mname) incCounter
       pure x )
  `finally` decGauge (activeCalls metrics)

prometheusWai :: [Text] -> Middleware
prometheusWai endpoint
  = Wai.prometheus (Wai.PrometheusSettings endpoint False False)