{-# 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 ss mm _ _) run = do
  let sname' = case ss of
                 Service sname _ -> sname
                 OneOf   sname _ -> sname
      mname' = case mm of
                 Just (Method mname _ _) -> mname
                 Nothing                 -> "<noname>"
  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)
