{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} module Data.Metrics.Meter.Internal ( Meter, meterData, mark, clear, tick, meanRate, oneMinuteAverage, fiveMinuteAverage, fifteenMinuteAverage, tickIfNecessary, count, lastTick ) where import Control.Lens import Control.Lens.TH import Data.Time.Clock import qualified Data.Metrics.MovingAverage as M data Meter = Meter { meterCount :: {-# UNPACK #-} !Int , meterOneMinuteRate :: !M.MovingAverage , meterFiveMinuteRate :: !M.MovingAverage , meterFifteenMinuteRate :: !M.MovingAverage , meterStartTime :: !NominalDiffTime , meterLastTick :: !NominalDiffTime , meterTickInterval :: {-# UNPACK #-} !Double } deriving (Show) makeFields ''Meter meterData :: Double -> (Double -> Int -> M.MovingAverage) -> NominalDiffTime -> Meter meterData ti f t = Meter { meterCount = 0 , meterOneMinuteRate = f ti 1 , meterFiveMinuteRate = f ti 5 , meterFifteenMinuteRate = f ti 15 , meterStartTime = t , meterLastTick = t , meterTickInterval = ti } -- TODO: make moving average prism mark :: NominalDiffTime -> Int -> Meter -> Meter mark t c m = ticked & count +~ c & oneMinuteRate %~ updateMeter & fiveMinuteRate %~ updateMeter & fifteenMinuteRate %~ updateMeter where updateMeter = M.update $ fromIntegral c ticked = tickIfNecessary t m {-# INLINEABLE mark #-} clear :: NominalDiffTime -> Meter -> Meter clear t = (count .~ 0) . (startTime .~ t) . (lastTick .~ t) . (oneMinuteRate %~ M.clear) . (fiveMinuteRate %~ M.clear) . (fifteenMinuteRate %~ M.clear) {-# INLINEABLE clear #-} tick :: Meter -> Meter tick = (oneMinuteRate %~ M.tick) . (fiveMinuteRate %~ M.tick) . (fifteenMinuteRate %~ M.tick) {-# INLINEABLE tick #-} tickIfNecessary :: NominalDiffTime -> Meter -> Meter tickIfNecessary new d = if age >= meterTickInterval d then iterate tick (d { meterLastTick = latest }) !! truncate (age / meterTickInterval d) else d where age = realToFrac (new - meterLastTick d) swapped = meterLastTick d < new latest = Prelude.max (meterLastTick d) new {-# INLINEABLE tickIfNecessary #-} meanRate :: NominalDiffTime -> Meter -> Double meanRate t d = if c == 0 then 0 else fromIntegral c / elapsed where c = meterCount d start = meterStartTime d elapsed = realToFrac (t - start) {-# INLINEABLE meanRate #-} oneMinuteAverage :: Meter -> M.MovingAverage oneMinuteAverage = meterOneMinuteRate fiveMinuteAverage :: Meter -> M.MovingAverage fiveMinuteAverage = meterFiveMinuteRate fifteenMinuteAverage :: Meter -> M.MovingAverage fifteenMinuteAverage = meterFifteenMinuteRate