{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} module Raft.Metrics ( RaftNodeMetrics(..) , defaultRaftNodeMetrics , getMetricsStore , getRaftNodeMetrics , setNodeStateLabel , setLastLogEntryIndexGauge , setLastLogEntryHashLabel , setCommitIndexGauge , incrInvalidCmdCounter , incrEventsHandledCounter ) where import Protolude import qualified Control.Monad.Metrics as Metrics import qualified Control.Monad.Metrics.Internal as Metrics import qualified Data.HashMap.Strict as HashMap import Data.Serialize (Serialize) import qualified System.Metrics as EKG import Raft.Log (LastLogEntry, EntryHash(..), hashLastLogEntry, genesisHash) import Raft.Types (Index, Mode(..)) ------------------------------------------------------------------------------ -- Raft Node Metrics ------------------------------------------------------------------------------ data RaftNodeMetrics = RaftNodeMetrics { invalidCmdCounter :: Int64 , eventsHandledCounter :: Int64 , nodeStateLabel :: [Char] , lastLogHashLabel :: [Char] -- ^ Base 16 encoded last log entry hash , lastLogIndexGauge :: Int64 , commitIndexGauge :: Int64 } deriving (Show, Generic, Serialize) defaultRaftNodeMetrics :: RaftNodeMetrics defaultRaftNodeMetrics = RaftNodeMetrics { invalidCmdCounter = 0 , eventsHandledCounter = 0 , nodeStateLabel = "Follower" , lastLogHashLabel = toS (unEntryHash genesisHash) , lastLogIndexGauge = 0 , commitIndexGauge = 0 } getMetricsStore :: (MonadIO m, Metrics.MonadMetrics m) => m EKG.Store getMetricsStore = Metrics._metricsStore <$> Metrics.getMetrics getRaftNodeMetrics :: (MonadIO m, Metrics.MonadMetrics m) => m RaftNodeMetrics getRaftNodeMetrics = do metricsStore <- getMetricsStore sample <- liftIO (EKG.sampleAll metricsStore) pure RaftNodeMetrics { invalidCmdCounter = lookupCounterValue InvalidCmdCounter sample , eventsHandledCounter = lookupCounterValue EventsHandledCounter sample , nodeStateLabel = toS (lookupLabelValue NodeStateLabel sample) , lastLogHashLabel = toS (lookupLastLogEntryHashLabel sample) , lastLogIndexGauge = lookupGaugeValue LastLogEntryIndexGauge sample , commitIndexGauge = lookupGaugeValue CommitIndexGauge sample } -------------------------------------------------------------------------------- -- Labels -- -- Labels are variable values and can be used to track e.g. the command line -- arguments or other free-form values. -------------------------------------------------------------------------------- data RaftNodeLabel = NodeStateLabel | LastLogEntryHashLabel deriving Show lookupLabelValue :: RaftNodeLabel -> EKG.Sample -> Text lookupLabelValue label sample = case HashMap.lookup (show label) sample of Just (EKG.Label text) -> text -- TODO Handle failure in a better way? Nothing -> "" Just _ -> "" setRaftNodeLabel :: (MonadIO m, Metrics.MonadMetrics m) => RaftNodeLabel -> Text -> m () setRaftNodeLabel label = Metrics.label (show label) setNodeStateLabel :: (MonadIO m, Metrics.MonadMetrics m) => Mode -> m () setNodeStateLabel = setRaftNodeLabel NodeStateLabel . show setLastLogEntryHashLabel :: (MonadIO m, Metrics.MonadMetrics m, Serialize v) => LastLogEntry v -> m () setLastLogEntryHashLabel = setRaftNodeLabel LastLogEntryHashLabel . toS . unEntryHash . hashLastLogEntry lookupLastLogEntryHashLabel :: EKG.Sample -> Text lookupLastLogEntryHashLabel sample = case HashMap.lookup (show LastLogEntryHashLabel) sample of Just (EKG.Label hashAsText) -> hashAsText _ -> toS . unEntryHash $ genesisHash -------------------------------------------------------------------------------- -- Gauges -- -- Gauges are variable values and can be used to track e.g. the current number -- of concurrent connections. -------------------------------------------------------------------------------- data RaftNodeGauge = LastLogEntryIndexGauge | CommitIndexGauge deriving Show lookupGaugeValue :: RaftNodeGauge -> EKG.Sample -> Int64 lookupGaugeValue gauge sample = case HashMap.lookup (show gauge) sample of Nothing -> 0 Just (EKG.Gauge n) -> n -- TODO Handle failure in a better way? Just _ -> 0 setRaftNodeGauge :: (MonadIO m, Metrics.MonadMetrics m) => RaftNodeGauge -> Int -> m () setRaftNodeGauge gauge n = Metrics.gauge (show gauge) n setLastLogEntryIndexGauge :: (MonadIO m, Metrics.MonadMetrics m) => Index -> m () setLastLogEntryIndexGauge = setRaftNodeGauge LastLogEntryIndexGauge . fromIntegral setCommitIndexGauge :: (MonadIO m, Metrics.MonadMetrics m) => Index -> m () setCommitIndexGauge = setRaftNodeGauge CommitIndexGauge . fromIntegral -------------------------------------------------------------------------------- -- Counters -- -- Counters are non-negative, monotonically increasing values and can be used -- to track e.g. the number of requests served since program start. -------------------------------------------------------------------------------- data RaftNodeCounter = InvalidCmdCounter | EventsHandledCounter deriving Show lookupCounterValue :: RaftNodeCounter -> EKG.Sample -> Int64 lookupCounterValue counter sample = case HashMap.lookup (show counter) sample of Nothing -> 0 Just (EKG.Counter n) -> n -- TODO Handle failure in a better way? Just _ -> 0 incrRaftNodeCounter :: (MonadIO m, Metrics.MonadMetrics m) => RaftNodeCounter -> m () incrRaftNodeCounter = Metrics.increment . show incrInvalidCmdCounter :: (MonadIO m, Metrics.MonadMetrics m) => m () incrInvalidCmdCounter = incrRaftNodeCounter InvalidCmdCounter incrEventsHandledCounter :: (MonadIO m, Metrics.MonadMetrics m) => m () incrEventsHandledCounter = incrRaftNodeCounter EventsHandledCounter