data-prometheus-0.1.0.0: src/Data/Prometheus/Monad.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.Prometheus.Monad
( MetricState(..)
, Metrics
, MetricsT
, ToMetrics(..)
, execMetricsT
, addMetric'
, addMetric
, subMetrics
, labeledMetrics
, metric
, sub
, desc
, label
, mkGauge
, mkCounter
, eitherExitCode
, eitherToGauge
, boolToGauge
, enumToGauge
, floatToGauge
, logError
) where
import Control.Monad.Identity (Identity)
import Control.Monad.Trans.State.Strict
import Data.Text (Text)
import Data.Map (Map)
import qualified Data.Map
import qualified Data.Text
import qualified GHC.Float
import Data.Prometheus.Types
data MetricState = MetricState
{ baseMetric :: MetricId
, metrics :: Map MetricId Metric
, errors :: [Text]
}
type MetricsT m = StateT MetricState m ()
type Metrics = MetricsT Identity
class ToMetrics a where
toMetrics
:: Monad m
=> a
-> MetricsT m
instance ToMetrics a => ToMetrics [a] where
toMetrics xs =
mapM_
(\(k, v) ->
labeledMetrics
"id"
(Data.Text.pack $ show k)
$ toMetrics v
)
(zip [(0 :: Int)..] xs)
-- | Evaluate metrics into `MetricState`
execMetricsT
:: Monad m
=> MetricId
-> MetricsT m
-> m MetricState
execMetricsT rootMetric =
flip
execStateT
(MetricState rootMetric mempty mempty)
-- | Add metric with value
addMetric'
:: Monad m
=> (MetricId -> MetricId) -- ^ Function to change the current @MetricId@
-> Metric -- ^ @Metric@ to add
-> MetricsT m
addMetric' f mData = do
mId <- f <$> gets baseMetric
modify $ \ms ->
ms { metrics = Data.Map.insert mId mData (metrics ms) }
-- | Add metric with value
addMetric
:: Monad m
=> Text -- ^ Suffix (sub metric to add)
-> Metric -- ^ @Metric@ to add
-> MetricsT m
addMetric subName = addMetric' (sub subName)
-- | Combinator to create sub-metrics
subMetrics
:: Monad m
=> Text
-> MetricsT m
-> MetricsT m
subMetrics subName act = do
old <- gets baseMetric
modify $ \ms ->
ms { baseMetric = sub subName $ baseMetric ms }
act
modify $ \ms ->
ms { baseMetric = old }
-- | Combinator to create labeled metrics
labeledMetrics
:: Monad m
=> Text -- ^ Label name
-> Text -- ^ Label value
-> MetricsT m
-> MetricsT m
labeledMetrics labelName labelValue act = do
old <- gets baseMetric
modify $ \ms ->
ms { baseMetric = label labelName labelValue $ baseMetric ms }
act
modify $ \ms ->
ms { baseMetric = old }
-- | Create metric with just `name`
metric
:: Text
-> MetricId
metric mName = MetricId mName mempty mempty
-- | Append `subName` to the name of a @MetricId@
--
-- > metric "a" & sub "b"
-- results in name "a_b"
sub
:: Text
-> MetricId
-> MetricId
sub subName m =
m { metricIdName = metricIdName m <> "_" <> subName }
-- | Set help text / description of a @MetricId@
desc
:: Text
-> MetricId
-> MetricId
desc h m =
m { metricIdHelp = h }
-- | Add label to MetricId
label
:: Text
-> Text
-> MetricId
-> MetricId
label k v m =
m { metricIdLabels = Data.Map.insert k v (metricIdLabels m) }
-- | Create @Gauge@ metric
mkGauge
:: Double
-> Metric
mkGauge = Gauge
-- | Create @Counter@ metric
mkCounter
:: Double
-> Metric
mkCounter = Counter
-- | Right is exitcode 0, Left non-zero
eitherExitCode :: Either a b -> Integer
eitherExitCode (Right _) = 0
eitherExitCode (Left _) = 1
-- | Convert Either to Gauge, 0 meaning Right
eitherToGauge :: Either a b -> Metric
eitherToGauge = mkGauge . fromIntegral . eitherExitCode
-- | Convert Bool to Gauge, 0 meaning False
boolToGauge :: Bool -> Metric
boolToGauge False = mkGauge 0
boolToGauge True = mkGauge 1
-- | Convert Enum to Gauge, 0 (typically) meaning Ok status
enumToGauge :: Enum a => a -> Metric
enumToGauge = mkGauge . fromIntegral . fromEnum
-- | Convert @Float@ to Gauge
floatToGauge
:: Float
-> Metric
floatToGauge = mkGauge . GHC.Float.float2Double
-- | Log error message
--
-- These are appended after all metrics were printed
--
-- Not a standard token but textfile collector ignores it as a comment
-- and we can use it to provide some insight to our scripts.
logError
:: Monad m
=> Text
-> StateT MetricState m ()
logError err =
modify $ \ms -> ms { errors = (errors ms) ++ [errComment] }
where
errComment =
Data.Text.unwords
[ "# ERROR"
, err
]