hcat: Add Metrics module

This commit is contained in:
Alexander Kobjolke 2023-10-03 12:02:13 +02:00
parent e53fe2237d
commit 6301994d1e
3 changed files with 107 additions and 0 deletions

View file

@ -20,6 +20,7 @@ library
HCat
HCat.FileInfo
HCat.Internal
HCat.Metrics
HCat.Screen
HCat.StatusLine
other-modules:
@ -33,6 +34,7 @@ library
ghc-options: -Wall -fdefer-typed-holes
build-depends:
base >=4.13 && <5
, containers
, directory
, process
, text
@ -52,6 +54,7 @@ executable hcat
ghc-options: -Wall -fdefer-typed-holes
build-depends:
base >=4.13 && <5
, containers
, directory
, hcat
, time
@ -73,6 +76,7 @@ test-suite doctest
doctest:doctest
build-depends:
base >=4.13 && <5
, containers
, directory
, process
, time
@ -97,6 +101,7 @@ test-suite spec
build-depends:
QuickCheck
, base >=4.13 && <5
, containers
, directory
, hcat
, hspec

View file

@ -13,6 +13,7 @@ dependencies:
- base >= 4.13 && < 5
- time
- directory
- containers
# - bytestring
ghc-options:

101
src/HCat/Metrics.hs Normal file
View file

@ -0,0 +1,101 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE RecordWildCards #-}
-- | Define some metrics
module HCat.Metrics where
import Control.Exception (SomeException, try)
import Data.Time.Clock (diffUTCTime, getCurrentTime, nominalDiffTimeToSeconds)
import Data.IORef (IORef, modifyIORef, newIORef, readIORef)
import Data.Map.Merge.Strict qualified as Merge
import Data.Map.Strict (Map, singleton)
import Data.Map.Strict qualified as Map
import Data.Monoid
data Metrics = Metrics
{ metricSuccessCount :: Sum Int
, metricFailureCount :: Sum Int
, metricCallDuration :: Timings
}
deriving (Show, Eq)
newtype Timings = Timings {getTimings :: Map String (Sum Int)}
deriving (Show) via Map String (Sum Int)
deriving (Eq, Ord)
prettyMetrics :: Metrics -> String
prettyMetrics Metrics{..} =
unlines
[ "success: " <> prettySuccess
, "failure: " <> prettyFailure
, "timings: " <> "\n" <> prettyTimings
]
where
prettySuccess = show . getSum $ metricSuccessCount
prettyFailure = show . getSum $ metricFailureCount
prettyTimings = unlines . fmap (indentation . prettyTiming) $ Map.toList (getTimings metricCallDuration)
prettyTiming (name, duration) = name <> ": " <> (show . getSum $ duration)
indentation :: String -> String
indentation = (<>) " "
merge :: (Semigroup v, Ord k) => Map k v -> Map k v -> Map k v
merge = Merge.merge Merge.preserveMissing Merge.preserveMissing (Merge.zipWithMatched (const (<>)))
instance Semigroup Timings where
(Timings lhs) <> (Timings rhs) = Timings $ lhs `merge` rhs
instance Monoid Timings where
mempty = Timings mempty
instance Semigroup Metrics where
(Metrics a b c) <> (Metrics a' b' c') = Metrics (a <> a') (b <> b') (c <> c')
instance Monoid Metrics where
mempty = Metrics mempty mempty mempty
success :: Metrics
success = Metrics (Sum 1) mempty mempty
failure :: Metrics
failure = Metrics mempty (Sum 1) mempty
timing :: String -> Int -> Metrics
timing name duration = Metrics mempty mempty (Timings $ singleton name (Sum duration))
newtype MetricsRef = MetricsRef (IORef Metrics)
newMetrics :: IO MetricsRef
newMetrics = MetricsRef <$> newIORef mempty
storeMetric :: Metrics -> MetricsRef -> IO ()
storeMetric m (MetricsRef r) = modifyIORef r (m <>)
countSuccess :: MetricsRef -> IO ()
countSuccess = storeMetric success
countFailure :: MetricsRef -> IO ()
countFailure = storeMetric failure
countTiming :: MetricsRef -> String -> Int -> IO ()
countTiming metrics name duration = storeMetric (timing name duration) metrics
accountFor :: MetricsRef -> String -> IO a -> IO (Either SomeException a)
accountFor metrics name action = do
startTime <- getCurrentTime
result <- try action
endTime <- getCurrentTime
let duration = floor . nominalDiffTimeToSeconds $ diffUTCTime endTime startTime
_ <- case result of
Left _ -> countFailure metrics
Right _ -> countSuccess metrics
countTiming metrics name duration
pure result
printMetrics :: MetricsRef -> IO ()
printMetrics (MetricsRef metrics) = do
m <- readIORef metrics
putStr $ prettyMetrics m