This repository has been archived on 2024-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
fradrive-old/src/Utils/Metrics.hs

199 lines
7.4 KiB
Haskell

module Utils.Metrics
( withHealthReportMetrics
, registerGHCMetrics
, observeHTTPRequestLatency
, registerReadyMetric
, withJobWorkerStateLbls
, observeYesodCacheSize
, observeFavouritesQuickActionsDuration
, LoginOutcome(..), observeLoginOutcome
) where
import Import.NoModel hiding (Vector, Info)
import Model
import Prometheus
import Prometheus.Metric.GHC
import qualified Data.List as List
import System.Clock
import Data.Time.Clock.POSIX
import Network.Wai (Middleware)
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as HTTP
import Yesod.Core.Types (HandlerData(..), GHState(..))
{-# ANN module ("HLint: ignore Use even" :: String) #-}
histogramBuckets :: Rational -- ^ min
-> Rational -- ^ max
-> [Double]
histogramBuckets bMin bMax = map fromRational . takeWhile (<= bMax) . go bMin $ List.cycle factors
where
go n [] = [n]
go n (f:fs) = n : go (f * n) fs
factors
| bMin' `mod` 2 == 0 = [2.5, 2, 2]
| bMin' `mod` 5 == 0 = [2, 2, 2.5]
| otherwise = [2, 2.5, 2]
where
bMin' :: Integer
bMin' = floor . List.head . dropWhile (< 1) $ List.iterate (* 10) bMin
{-# NOINLINE healthReportTime #-}
healthReportTime :: Vector Label2 Gauge
healthReportTime = unsafeRegister . vector ("check", "status") $ gauge info
where
info = Info "uni2work_health_check_time"
"POSIXTime of last health check performed by this Uni2work-instance"
{-# NOINLINE healthReportDuration #-}
healthReportDuration :: Vector Label2 Histogram
healthReportDuration = unsafeRegister . vector ("check", "status") $ histogram info buckets
where
info = Info "uni2work_health_check_duration_seconds"
"Duration of last health check performed by this Uni2work-instance"
buckets = histogramBuckets 5e-6 100e-3
{-# NOINLINE httpRequestLatency #-}
httpRequestLatency :: Vector Label3 Histogram
httpRequestLatency = unsafeRegister . vector ("handler", "method", "status") $ histogram info buckets
where info = Info "http_request_duration_seconds"
"HTTP request latency"
buckets = histogramBuckets 50e-6 500
data ReadySince = MkReadySince
readyMetric :: POSIXTime -> Metric ReadySince
readyMetric ts = Metric $ return (MkReadySince, collectReadySince)
where
collectReadySince = return [SampleGroup info GaugeType [Sample "ready_time" [] sample]]
info = Info "ready_time"
"POSIXTime this Uni2work-instance became ready"
sample = encodeUtf8 $ tshow (realToFrac ts :: Nano)
{-# NOINLINE jobWorkerStateDuration #-}
jobWorkerStateDuration :: Vector Label4 Histogram
jobWorkerStateDuration = unsafeRegister . vector ("worker", "state", "jobctl", "task") $ histogram info buckets
where info = Info "uni2work_job_worker_state_duration_seconds"
"Duration of time a Uni2work job executor spent in a certain state"
buckets = histogramBuckets 1e-6 500
{-# NOINLINE jobWorkerStateTransitions #-}
jobWorkerStateTransitions :: Vector Label4 Counter
jobWorkerStateTransitions = unsafeRegister . vector ("worker", "state", "jobctl", "task") $ counter info
where info = Info "uni2work_job_worker_state_transitions_total"
"Number of times a Uni2work job executor entered a certain state"
{-# NOINLINE yesodCacheSize #-}
yesodCacheSize :: Histogram
yesodCacheSize = unsafeRegister $ histogram info buckets
where info = Info "yesod_ghs_cache_items"
"Number of items in Yesod's ghsCache and ghsCacheBy"
buckets = 0 : histogramBuckets 1 1e6
{-# NOINLINE favouritesQuickActionsDuration #-}
favouritesQuickActionsDuration :: Histogram
favouritesQuickActionsDuration = unsafeRegister $ histogram info buckets
where info = Info "uni2work_favourites_quick_actions_seconds"
"Duration of time needed to calculate a set of favourite quick actions"
buckets = histogramBuckets 500e-6 50
{-# NOINLINE loginOutcomes #-}
loginOutcomes :: Vector Label2 Counter
loginOutcomes = unsafeRegister . vector ("plugin", "outcome") $ counter info
where info = Info "uni2work_login_attempts_total"
"Number of login attempts"
withHealthReportMetrics :: MonadIO m => m HealthReport -> m HealthReport
withHealthReportMetrics act = do
before <- liftIO $ getTime Monotonic
report <- act
after <- liftIO $ getTime Monotonic
let checkVal = toPathPiece $ classifyHealthReport report
statusVal = toPathPiece $ healthReportStatus report
timeSample, durationSample :: Double
timeSample = realToFrac (realToFrac after :: Nano)
durationSample = realToFrac (realToFrac $ after - before :: Nano)
liftIO $ withLabel healthReportTime (checkVal, statusVal) $ flip setGauge timeSample
liftIO $ withLabel healthReportDuration (checkVal, statusVal) $ flip observe durationSample
return report
registerGHCMetrics :: MonadIO m => m ()
registerGHCMetrics = void $ register ghcMetrics
observeHTTPRequestLatency :: forall site. ParseRoute site => (Route site -> String) -> Middleware
observeHTTPRequestLatency classifyHandler app req respond' = do
start <- getTime Monotonic
app req $ \res -> do
end <- getTime Monotonic
let method = decodeUtf8 $ Wai.requestMethod req
status = tshow . HTTP.statusCode $ Wai.responseStatus res
route :: Maybe (Route site)
route = parseRoute ( Wai.pathInfo req
, over (mapped . _2) (fromMaybe "") . HTTP.queryToQueryText $ Wai.queryString req
)
handler' = pack . classifyHandler <$> route
labels :: Label3
labels = (fromMaybe "n/a" handler', method, status)
withLabel httpRequestLatency labels . flip observe . realToFrac $ end - start
respond' res
registerReadyMetric :: MonadIO m => m ()
registerReadyMetric = liftIO $ void . register . readyMetric =<< getPOSIXTime
withJobWorkerStateLbls :: (MonadIO m, MonadMask m) => Label4 -> m a -> m a
withJobWorkerStateLbls newLbls act = do
liftIO $ withLabel jobWorkerStateTransitions newLbls incCounter
start <- liftIO $ getTime Monotonic
res <- handleAll (return . Left) $ Right <$> act
end <- liftIO $ getTime Monotonic
liftIO . withLabel jobWorkerStateDuration newLbls . flip observe . realToFrac $ end - start
either throwM return res
observeYesodCacheSize :: MonadHandler m => m ()
observeYesodCacheSize = do
HandlerData{handlerState} <- liftHandler ask
liftIO $ do
GHState{..} <- readIORef handlerState
let size = fromIntegral $ length ghsCache + length ghsCacheBy
observe yesodCacheSize size
observeFavouritesQuickActionsDuration :: (MonadIO m, MonadMask m) => m a -> m a
observeFavouritesQuickActionsDuration act = do
start <- liftIO $ getTime Monotonic
res <- handleAll (return . Left) $ Right <$> act
end <- liftIO $ getTime Monotonic
liftIO . observe favouritesQuickActionsDuration . realToFrac $ end - start
either throwM return res
data LoginOutcome
= LoginSuccessful
| LoginInvalidCredentials
| LoginError
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''LoginOutcome $ camelToPathPiece' 1
observeLoginOutcome :: MonadHandler m
=> Text -- ^ Plugin
-> LoginOutcome
-> m ()
observeLoginOutcome plugin outcome
= liftIO $ withLabel loginOutcomes (plugin, toPathPiece outcome) incCounter