module Prometheus.Metric.Counter ( Counter , counter , incCounter , addCounter , unsafeAddCounter , addDurationToCounter , getCounter , countExceptions ) where import Prometheus.Info import Prometheus.Metric import Prometheus.Metric.Observer (timeAction) import Prometheus.MonadMonitor import Control.DeepSeq import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad (unless) import qualified Data.Atomics as Atomics import qualified Data.ByteString.UTF8 as BS import qualified Data.IORef as IORef newtype Counter = MkCounter (IORef.IORef Double) instance NFData Counter where rnf (MkCounter ioref) = seq ioref () -- | Creates a new counter metric with a given name and help string. counter :: Info -> Metric Counter counter info = Metric $ do ioref <- IORef.newIORef 0 return (MkCounter ioref, collectCounter info ioref) withCounter :: MonadMonitor m => Counter -> (Double -> Double) -> m () withCounter (MkCounter ioref) f = doIO $ Atomics.atomicModifyIORefCAS_ ioref f -- | Increments the value of a counter metric by 1. incCounter :: MonadMonitor m => Counter -> m () incCounter c = withCounter c (+ 1) -- | Add the given value to the counter, if it is zero or more. addCounter :: MonadMonitor m => Counter -> Double -> m Bool addCounter c x | x < 0 = return False | otherwise = do withCounter c add return True where add i = i `seq` x `seq` i + x -- | Add the given value to the counter. Panic if it is less than zero. unsafeAddCounter :: MonadMonitor m => Counter -> Double -> m () unsafeAddCounter c x = do added <- addCounter c x unless added $ error $ "Tried to add negative value to counter: " ++ show x -- | Add the duration of an IO action (in seconds) to a counter. -- -- If the IO action throws, no duration is added. addDurationToCounter :: (MonadIO m, MonadMonitor m) => Counter -> m a -> m a addDurationToCounter metric io = do (result, duration) <- timeAction io _ <- addCounter metric duration return result -- | Retrieves the current value of a counter metric. getCounter :: MonadIO m => Counter -> m Double getCounter (MkCounter ioref) = liftIO $ IORef.readIORef ioref collectCounter :: Info -> IORef.IORef Double -> IO [SampleGroup] collectCounter info c = do value <- IORef.readIORef c let sample = Sample (metricName info) [] (BS.fromString $ show value) return [SampleGroup info CounterType [sample]] -- | Count the amount of times an action throws any synchronous exception. -- -- >>> exceptions <- register $ counter (Info "exceptions_total" "Total amount of exceptions thrown") -- >>> countExceptions exceptions $ return () -- >>> getCounter exceptions -- 0.0 -- >>> countExceptions exceptions (error "Oh no!") `catch` (\SomeException{} -> return ()) -- >>> getCounter exceptions -- 1.0 -- -- It's important to note that this will count *all* synchronous exceptions. If -- you want more granular counting of exceptions, you will need to write custom -- code using 'incCounter'. countExceptions :: (MonadCatch m, MonadMonitor m) => Counter -> m a -> m a countExceptions m io = io `onException` incCounter m -- $setup -- >>> :module +Prometheus -- >>> :set -XOverloadedStrings