199 lines
7.4 KiB
Haskell
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
|