{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE UndecidableInstances  #-}
-- | A meter measures the rate at which a set of events occur:
--
-- Meters measure the rate of the events in a few different ways. The mean rate is the average rate of events. It’s generally useful for trivia, but as it represents the total rate for your application’s entire lifetime (e.g., the total number of requests handled, divided by the number of seconds the process has been running), it doesn’t offer a sense of recency. Luckily, meters also record three different exponentially-weighted moving average rates: the 1-, 5-, and 15-minute moving averages.
--
-- (Just like the Unix load averages visible in uptime or top.)
module Data.Metrics.Meter (
  Meter,
  meter,
  mark,
  mark',
  mkMeter,
  fromMeter,
  module Data.Metrics.Types
) where
import Control.Lens
import Control.Monad.Base
import Control.Monad.Primitive
import Data.Primitive.MutVar
import Data.Time.Clock
import Data.Time.Clock.POSIX
import qualified Data.HashMap.Strict as H
import Data.Metrics.Internal
import qualified Data.Metrics.Meter.Internal as P
import qualified Data.Metrics.MovingAverage as A
import qualified Data.Metrics.MovingAverage.ExponentiallyWeighted as EWMA
import Data.Metrics.Types

-- | A measure of the /rate/ at which a set of events occurs.
data Meter m = Meter
  { fromMeter       :: !(MV m P.Meter)
  , meterGetSeconds :: !(m NominalDiffTime)
  }

instance {- (MonadBase b m, PrimMonad b) => -} Rate IO IO (Meter IO) where
  oneMinuteRate m = liftBase $ do
    t <- meterGetSeconds m
    updateAndApplyToRef (fromMeter m) (P.tickIfNecessary t) (A.rate . P.oneMinuteAverage)
  {-# INLINEABLE oneMinuteRate #-}

  fiveMinuteRate m = liftBase $ do
    t <- meterGetSeconds m
    updateAndApplyToRef (fromMeter m) (P.tickIfNecessary t) (A.rate . P.fiveMinuteAverage)
  {-# INLINEABLE fiveMinuteRate #-}

  fifteenMinuteRate m = liftBase $ do
    t <- meterGetSeconds m
    updateAndApplyToRef (fromMeter m) (P.tickIfNecessary t) (A.rate . P.fifteenMinuteAverage)
  {-# INLINEABLE fifteenMinuteRate #-}

  meanRate m = liftBase $ do
    t <- meterGetSeconds m
    m' <- readMutVar (fromMeter m)
    applyWithRef (fromMeter m) $ P.meanRate t
  {-# INLINEABLE meanRate #-}

instance (MonadBase b m, PrimMonad m) => Count b m (Meter m) where
  count = fmap (view P.count) . readMutVar . fromMeter
  {-# INLINEABLE count #-}

-- | Register multiple occurrences of an event.
mark' :: PrimMonad m => Meter m -> Int -> m ()
mark' m x = do
  t <- meterGetSeconds m
  updateRef (fromMeter m) (P.mark t x)
{-# INLINEABLE mark' #-}

-- | Register a single occurrence of an event.
mark :: PrimMonad m => Meter m -> m ()
mark = flip mark' 1
{-# INLINEABLE mark #-}

-- | Create a new meter using an exponentially weighted moving average
meter :: IO (Meter IO)
meter = mkMeter getPOSIXTime

-- | Create a meter using a custom function for retrieving the current time.
--
-- This is mostly exposed for testing purposes: prefer using "meter" if possible.
mkMeter :: PrimMonad m => m NominalDiffTime -> m (Meter m)
mkMeter m = do
  t <- m
  v <- newMutVar $ ewmaMeter t
  return $! Meter v m

-- | Make a pure meter using an exponentially weighted moving average
ewmaMeter :: NominalDiffTime -- ^ The starting time of the meter.
  -> P.Meter
ewmaMeter = P.meterData 5 EWMA.movingAverage