packages feed

prodapi-0.1.0.0: src/Prod/Reports.hs

{-# LANGUAGE KindSignatures #-}

module Prod.Reports (
    ReportsApi,
    Report (..),
    countReports,
    Runtime,
    initRuntime,
)
where

import Control.Monad.IO.Class (liftIO)
import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
import GHC.TypeLits (Symbol)
import Prod.Tracer
import qualified Prometheus as Prometheus
import Servant (JSON, Post, ReqBody, Summary, (:>))
import Servant.Server (Handler)

{- | Some minimal report wrapper.
Has low expectations on the client.
-}
data Report a = Report {posixTime :: Int, backoff :: Int, events :: [a]}

instance (FromJSON a) => FromJSON (Report a) where
    parseJSON = withObject "Report" $ \o ->
        Report <$> o .: "t" <*> o .: "b" <*> o .: "es"
instance (ToJSON a) => ToJSON (Report a) where
    toJSON (Report t b es) = object ["t" .= t, "b" .= b, "es" .= es]

type ReportsApi a =
    Summary "receives and acknowledge some reports"
        :> "reports"
        :> ReqBody '[JSON] (Report a)
        :> Post '[JSON] Int

-- | A set of counters tracking .
data Counters
    = Counters
    { reports_count :: !Prometheus.Counter
    , reported_events :: !Prometheus.Counter
    , reported_sizes :: !Prometheus.Summary
    , reported_backoffs :: !Prometheus.Summary
    }

newCounters :: IO Counters
newCounters =
    Counters
        <$> counts "reports"
        <*> counts "report_events"
        <*> summary "report_sizes"
        <*> summary "report_backoffs"
  where
    counts k =
        Prometheus.register $
            Prometheus.counter (Prometheus.Info k "")
    summary k =
        Prometheus.register $
            Prometheus.summary (Prometheus.Info k "") Prometheus.defaultQuantiles

-- | A default runtime for the `dropReports` route.
data Runtime a = Runtime {counters :: !Counters, tracer :: !(Tracer IO (Report a))}

initRuntime :: Tracer IO (Report a) -> IO (Runtime a)
initRuntime tracer = Runtime <$> newCounters <*> pure tracer

-- | Count and drop reports.
countReports :: Runtime a -> Report a -> Handler Int
countReports (Runtime counters tracer) report = do
    let size = length (events report)
    let dsize = fromIntegral size
    liftIO $ do
        runTracer tracer $ report
        Prometheus.incCounter (reports_count counters)
        Prometheus.addCounter (reported_events counters) dsize
        Prometheus.observe (reported_sizes counters) dsize
        Prometheus.observe (reported_backoffs counters) (fromIntegral $ backoff report)
    pure size