packages feed

instrument-0.6.1.0: src/Instrument/Counter.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TypeApplications #-}

module Instrument.Counter
  ( Counter,
    newCounter,
    readCounter,
    resetCounter,
    add,
    increment,
  )
where

-------------------------------------------------------------------------------
import Data.IORef
import qualified Data.Atomics.Counter as A
-------------------------------------------------------------------------------

data Counter = Counter {_atomicCounter :: A.AtomicCounter, _lastResetValue :: IORef Int}

-------------------------------------------------------------------------------
newCounter :: IO Counter
newCounter = Counter <$> A.newCounter 0 <*> newIORef 0

-------------------------------------------------------------------------------

-- | Reads current counter value
readCounter :: Counter -> IO Integer
readCounter (Counter i lastReset) = calculateDelta <$> readIORef lastReset <*> A.readCounter i

-- | Our counters are represented as Int (with machine word size) and increment
-- | only, with no possibility to reset. Since we report deltas, we need facility
-- | to reset counter: we do that by storing last reported value in IORef.
-- | When application will run for long-enough counters will inevitably overflow.
-- | In such case our delta would be negative – this does not make sense for increment only.
-- | To overcome this issue we apply heuristic: when current value of counter
-- | is lower last value the counter has been reset, rollover happened and we need to
-- | take it into account. Otherwise, we can simply subtract values.
calculateDelta :: Int -> Int -> Integer
calculateDelta lastReset current | current < lastReset =
  fromIntegral (maxBound @Int - lastReset) + fromIntegral (current - minBound @Int) + 1
calculateDelta lastReset current = fromIntegral current - fromIntegral lastReset

-------------------------------------------------------------------------------

-- | Reset the counter while reading it
resetCounter :: Counter -> IO Integer
resetCounter (Counter i lastReset) = do
  ctrValue <- A.readCounter i
  oldLast <- atomicModifyIORef' lastReset $ \oldLast -> (ctrValue, oldLast)
  pure $ calculateDelta oldLast ctrValue

-------------------------------------------------------------------------------
increment :: Counter -> IO ()
increment = add 1

-------------------------------------------------------------------------------
add :: Int -> Counter -> IO ()
add x (Counter i _) = A.incrCounter_ x i