{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}

module Utils.Metrics
  ( withHealthReportMetrics
  , registerGHCMetrics
  , observeHTTPRequestLatency
  , registerReadyMetric
  , registerHealthCheckInterval
  , withJobWorkerState
  , observeYesodCacheSize
  , observeFavouritesQuickActionsDuration
  , LoginOutcome(..), observeLoginOutcome
  , registerJobHeldLocksCount
  , FileChunkStorage(..), observeSourcedChunk, observeSunkChunk
  , observeDeletedUnreferencedFiles, observeDeletedUnreferencedChunks, observeInjectedFiles, observeRechunkedFiles
  , registerJobWorkerQueueDepth
  , observeMissingFiles
  , ARCMetrics, ARCLabel(..)
  , arcMetrics
  , LRUMetrics, LRULabel(..)
  , lruMetrics
  , InjectInhibitMetrics, injectInhibitMetrics
  , PoolMetrics, PoolLabel(..)
  , poolMetrics
  , observeDatabaseConnectionOpened, observeDatabaseConnectionClosed
  , onUseDBConn, onReleaseDBConn, DBConnUseState, DBConnLabel
  , AuthTagEvalOutcome(..), observeAuthTagEvaluation
  , observeFavouritesSkippedDueToDBLoad
  ) 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(..))

import qualified Data.Set as Set

import Jobs.Types

import qualified Data.Aeson as Aeson
import qualified Data.HashMap.Strict as HashMap

import Data.IntervalMap.Strict (IntervalMap)
import qualified Data.IntervalMap.Strict as IntervalMap

import qualified Data.Foldable as F

import qualified Utils.Pool as Custom

import GHC.Stack

{-# 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

data HealthCheckInterval = MkHealthCheckInterval

healthCheckInterval :: (HealthCheck -> Maybe NominalDiffTime) -> Metric HealthCheckInterval
healthCheckInterval hcInts = Metric $ return (MkHealthCheckInterval, collectHealthCheckInterval)
  where
    collectHealthCheckInterval = return . pure . SampleGroup info GaugeType $ do
      (hc, Just int) <- itoList hcInts
      return . Sample "uni2work_health_check_interval_seconds" [("check", toPathPiece hc)] . encodeUtf8 $ tshow (realToFrac int :: Nano)
    info = Info "uni2work_health_check_interval_seconds"
                "Target interval at which health checks are executed by this Uni2work-instance"

{-# 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 5000

{-# 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"

data JobHeldLocksCount = MkJobHeldLocksCount

jobHeldLocksCount :: TVar (Set QueuedJobId) -> Metric JobHeldLocksCount
jobHeldLocksCount heldLocks = Metric $ return (MkJobHeldLocksCount, collectJobHeldLocksCount)
  where
    collectJobHeldLocksCount = do
      nLocks <- Set.size <$> readTVarIO heldLocks
      let sample = encodeUtf8 $ tshow nLocks
      return [SampleGroup info GaugeType [Sample "uni2work_jobs_held_locks_count" [] sample]]
    info = Info "uni2work_jobs_held_locks_count"
                "Number of job locks currently held by this Uni2work-instance"

{-# NOINLINE sourcedFileChunkSizes #-}
sourcedFileChunkSizes :: Vector Label1 Histogram
sourcedFileChunkSizes = unsafeRegister . vector "storage" $ histogram info buckets
  where info = Info "uni2work_sourced_file_chunks_bytes"
                    "Sizes of file chunks sourced"
        buckets = 0 : histogramBuckets 1 1000000000

{-# NOINLINE sunkFileChunkSizes #-}
sunkFileChunkSizes :: Vector Label1 Histogram
sunkFileChunkSizes = unsafeRegister . vector "storage" $ histogram info buckets
  where info = Info "uni2work_sunk_file_chunks_bytes"
                    "Sizes of file chunks sunk"
        buckets = 0 : histogramBuckets 1 1000000000

{-# NOINLINE deletedUnreferencedFiles #-}
deletedUnreferencedFiles :: Counter
deletedUnreferencedFiles = unsafeRegister $ counter info
  where info = Info "uni2work_deleted_unreferenced_files_count"
                    "Number of unreferenced files deleted"

{-# NOINLINE deletedUnreferencedChunks #-}
deletedUnreferencedChunks :: Counter
deletedUnreferencedChunks = unsafeRegister $ counter info
  where info = Info "uni2work_deleted_unreferenced_chunks_count"
                    "Number of unreferenced chunks deleted"

{-# NOINLINE deletedUnreferencedChunksBytes #-}
deletedUnreferencedChunksBytes :: Counter
deletedUnreferencedChunksBytes = unsafeRegister $ counter info
  where info = Info "uni2work_deleted_unreferenced_chunks_bytes"
                    "Size of unreferenced chunks deleted"

{-# NOINLINE injectedFiles #-}
injectedFiles :: Counter
injectedFiles = unsafeRegister $ counter info
  where info = Info "uni2work_injected_files_count"
                    "Number of files injected from upload cache into database"

{-# NOINLINE injectedFilesBytes #-}
injectedFilesBytes :: Counter
injectedFilesBytes = unsafeRegister $ counter info
  where info = Info "uni2work_injected_files_bytes"
                    "Size of files injected from upload cache into database"
  
{-# NOINLINE rechunkedFiles #-}
rechunkedFiles :: Counter
rechunkedFiles = unsafeRegister $ counter info
  where info = Info "uni2work_rechunked_files_count"
                    "Number of files rechunked within database"

{-# NOINLINE rechunkedFilesBytes #-}
rechunkedFilesBytes :: Counter
rechunkedFilesBytes = unsafeRegister $ counter info
  where info = Info "uni2work_rechunked_files_bytes"
                    "Size of files rechunked within database"

{-# NOINLINE databaseConnectionsOpened #-}
databaseConnectionsOpened :: Counter
databaseConnectionsOpened = unsafeRegister $ counter info
  where info = Info "uni2work_database_connections_opened"
                    "Number of new connections to database opened"

{-# NOINLINE databaseConnectionsClosed #-}
databaseConnectionsClosed :: Counter
databaseConnectionsClosed = unsafeRegister $ counter info
  where info = Info "uni2work_database_connections_closed"
                    "Number of connections to database closed"

data JobWorkerQueueDepth = MkJobWorkerQueueDepth

jobWorkerQueueDepth :: TMVar JobState -> Metric JobWorkerQueueDepth
jobWorkerQueueDepth jSt = Metric $ return (MkJobWorkerQueueDepth, collectJobWorkerQueueDepth)
  where
    collectJobWorkerQueueDepth = maybeT (return []) $ do
      wQueues <- hoist atomically $ do
        JobState{..} <- MaybeT $ tryReadTMVar jSt
        flip ifoldMapM jobWorkers $ \wAsync wQueue
          -> lift $ pure . (jobWorkerName wAsync, ) . jqDepth <$> readTVar wQueue
      return [ SampleGroup info GaugeType
                 [ Sample "uni2work_queued_jobs_count" [("worker", showWorkerId wName)] . encodeUtf8 $ tshow wDepth
                 | (wName, wDepth) <- wQueues
                 ]
             ]
    info = Info "uni2work_queued_jobs_count"
                "Number of JobQueue entries in this Uni2work-instance"

{-# NOINLINE missingFiles #-}
missingFiles :: Vector Label1 Gauge
missingFiles = unsafeRegister . vector "ref" $ gauge info
  where info = Info "uni2work_missing_files_count"
                    "Number of files referenced from within database that are missing"

{-# NOINLINE favouritesSkippedDueToDBLoad #-}
favouritesSkippedDueToDBLoad :: Counter
favouritesSkippedDueToDBLoad = unsafeRegister $ counter info
  where info = Info "uni2work_favourites_skipped_due_to_db_load_count"
                    "Number of times this Uni2work-instance skipped generating FavouriteQuickActions due to database pressure"
  
relabel :: Text -> Text
        -> SampleGroup -> SampleGroup
relabel l s (SampleGroup i t ss) = SampleGroup i t . flip map ss $ \(Sample k lbls v) -> Sample k ((l, s) : filter (views _1 $ (/=) l) lbls) v

data ARCMetrics = ARCMetrics

data ARCLabel = ARCFileSource | ARCMemcachedLocal
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
  deriving anyclass (Universe, Finite)

nullaryPathPiece ''ARCLabel $ camelToPathPiece' 1

arcMetrics :: Integral w
           => ARCLabel
           -> ARCHandle k w v
           -> Metric ARCMetrics
arcMetrics lbl ah = Metric $ return (ARCMetrics, collectARCMetrics)
  where
    labelArc = relabel "arc"

    collectARCMetrics = map (labelArc $ toPathPiece lbl) <$> do
      (arc, _) <- readARCHandle ah
      return
        [ SampleGroup sizeInfo GaugeType
          [ Sample "arc_size" [("lru", "ghost-recent")] . encodeUtf8 . tshow $ arcGhostRecentSize arc
          , Sample "arc_size" [("lru", "recent")] . encodeUtf8 . tshow $ arcRecentSize arc
          , Sample "arc_size" [("lru", "frequent")] . encodeUtf8 . tshow $ arcFrequentSize arc
          , Sample "arc_size" [("lru", "ghost-frequent")] . encodeUtf8 . tshow $ arcGhostFrequentSize arc
          ]
        , SampleGroup weightInfo GaugeType
          [ Sample "arc_weight" [("lru", "recent")] . encodeUtf8 . tshow . toInteger $ getARCRecentWeight arc
          , Sample "arc_weight" [("lru", "frequent")] . encodeUtf8 . tshow . toInteger $ getARCFrequentWeight arc
          ]
        ]
    sizeInfo = Info "arc_size"
                    "Number of entries in the ARC LRUs"
    weightInfo = Info "arc_weight"
                      "Sum of weights of entries in the ARC LRUs"

data LRUMetrics = LRUMetrics

data LRULabel = LRUFileSourcePrewarm
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
  deriving anyclass (Universe, Finite)

nullaryPathPiece ''LRULabel $ camelToPathPiece' 1

lruMetrics :: Integral w
           => LRULabel
           -> LRUHandle k t w v
           -> Metric LRUMetrics
lruMetrics lbl lh = Metric $ return (LRUMetrics, collectLRUMetrics)
  where
    labelLru = relabel "lru"

    collectLRUMetrics = map (labelLru $ toPathPiece lbl) <$> do
      (lru, _) <- readLRUHandle lh
      return
        [ SampleGroup sizeInfo GaugeType
            [ Sample "lru_size" [] . encodeUtf8 . tshow $ lruStoreSize lru
            ]
        , SampleGroup weightInfo GaugeType
            [ Sample "lru_weight" [] . encodeUtf8 . tshow . toInteger $ getLRUWeight lru
            ]
        ]
    sizeInfo = Info "lru_size"
                    "Number of entries in the LRU"
    weightInfo = Info "lru_weight"
                      "Sum of weights of entries in the LRU"

data InjectInhibitMetrics = InjectInhibitMetrics

injectInhibitMetrics :: TVar (IntervalMap UTCTime (Set FileContentReference))
                     -> Metric InjectInhibitMetrics
injectInhibitMetrics tvar = Metric $ return (InjectInhibitMetrics, collectInjectInhibitMetrics)
  where
    collectInjectInhibitMetrics = do
      inhibits <- readTVarIO tvar
      return
        [ SampleGroup intervalsInfo GaugeType
          [ Sample "uni2work_inject_inhibited_intervals_count" [] . encodeUtf8 . tshow $ IntervalMap.size inhibits
          ]
        , SampleGroup hashesInfo GaugeType
          [ Sample "uni2work_inject_inhibited_hashes_count" [] . encodeUtf8 . tshow . Set.size $ F.fold inhibits
          ]
        ]
    intervalsInfo = Info "uni2work_inject_inhibited_intervals_count" 
                         "Number of distinct time intervals in which we don't transfer some files from upload cache to db"
    hashesInfo = Info "uni2work_inject_inhibited_hashes_count" 
                      "Number of files which we don't transfer from upload cache to db during some interval"

data PoolMetrics = PoolMetrics

data PoolLabel = PoolDatabaseConnections
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
  deriving anyclass (Universe, Finite)

nullaryPathPiece ''PoolLabel $ camelToPathPiece' 1

poolMetrics :: PoolLabel
            -> Custom.Pool' m c' c a
            -> Metric PoolMetrics
poolMetrics lbl pool = Metric $ return (PoolMetrics, collectPoolMetrics)
  where
    labelPool = relabel "pool" $ toPathPiece lbl

    collectPoolMetrics = map labelPool <$> do
      (available, inUse, usesCount) <- atomically $ (,,)
        <$> Custom.getPoolAvailableCount pool
        <*> Custom.getPoolInUseCount pool
        <*> Custom.getPoolUsesCount pool
      return
        [ SampleGroup availableInfo GaugeType
          [ Sample "uni2work_pool_available_count" [] . encodeUtf8 $ tshow available
          ]
        , SampleGroup inUseInfo GaugeType
          [ Sample "uni2work_pool_in_use_count" [] . encodeUtf8 $ tshow inUse
          ]
        , SampleGroup usesInfo CounterType
          [ Sample "uni2work_pool_uses_count" [] . encodeUtf8 $ tshow usesCount
          ]
        ]
  
    availableInfo = Info "uni2work_pool_available_count" 
                         "Number of open resources available for taking"
    inUseInfo = Info "uni2work_pool_in_use_count" 
                     "Number of resources currently in use"
    usesInfo = Info "uni2work_pool_uses_count" 
                    "Number of takes executed against the pool"

{-# NOINLINE databaseConnDuration #-}
databaseConnDuration :: Vector Label1 Histogram
databaseConnDuration = unsafeRegister . vector "label" $ histogram info buckets
  where
    info = Info "uni2work_database_conn_duration_seconds"
                "Duration of use of a database connection from the pool"
    buckets = histogramBuckets 50e-6 5000
  
data DBConnUseState = DBConnUseState
  { dbConnUseStart :: !TimeSpec
  , dbConnUseLabel :: !CallStack
  } deriving (Show, Typeable)

onUseDBConn :: (MonadIO m, MonadLogger m) => CallStack -> a -> m DBConnUseState
onUseDBConn dbConnUseLabel _ = do
  $logDebugS "DB" $ case getCallStack dbConnUseLabel of
    [] -> "no stack"
    xs -> intercalate "; " $ map (\(f, loc) -> pack f <> " @(" <> pack (prettySrcLoc loc) <> ")") xs
  dbConnUseStart <- liftIO $ getTime Monotonic
  return DBConnUseState{..}

onReleaseDBConn :: MonadIO m => DBConnUseState -> a -> m ()
onReleaseDBConn DBConnUseState{..} _ = liftIO $ do
  diff <- realToFrac . subtract dbConnUseStart <$> getTime Monotonic
  let lbl = case reverse $ getCallStack dbConnUseLabel of
        [] -> "unlabeled"
        (_, SrcLoc{..}) : _ -> pack srcLocModule
  withLabel databaseConnDuration lbl $ flip observe diff

data AuthTagEvalOutcome = OutcomeAuthorized | OutcomeUnauthorized | OutcomeAuthenticationRequired | OutcomeException
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
  deriving (Universe, Finite)
nullaryPathPiece ''AuthTagEvalOutcome $ camelToPathPiece' 1

{-# NOINLINE authTagEvaluationDuration #-}
authTagEvaluationDuration :: Vector Label3 Histogram
authTagEvaluationDuration = unsafeRegister . vector ("tag", "outcome", "handler") $ histogram info buckets
  where
    info = Info "uni2work_auth_tag_evaluation_duration_seconds"
                "Duration of auth tag evaluations"
    buckets = histogramBuckets 5e-6 1
    

withHealthReportMetrics :: MonadIO m => m HealthReport -> m HealthReport
withHealthReportMetrics act = do
  before <- liftIO getPOSIXTime
  report <- act
  after <- liftIO getPOSIXTime

  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

registerHealthCheckInterval :: MonadIO m => (HealthCheck -> Maybe NominalDiffTime) -> m ()
registerHealthCheckInterval = liftIO . void . register . healthCheckInterval

classifyJobWorkerState :: JobWorkerId -> JobWorkerState -> Prometheus.Label4
classifyJobWorkerState wId jws = (showWorkerId wId, tag, maybe "n/a" pack mJobCtl, maybe "n/a" pack mJob)
  where
    Aeson.Object obj = Aeson.toJSON jws
    Aeson.String tag = obj HashMap.! "state"
    mJobCtl = asum
      [ classifyJobCtl <$> jws ^? _jobWorkerJobCtl
      , "perform" <$ jws ^? _jobWorkerJob
      ]
    mJob = classifyJob <$> jws ^? _jobWorkerJob

withJobWorkerState :: (MonadIO m, MonadMask m) => JobWorkerId -> JobWorkerState -> m a -> m a
withJobWorkerState wId newSt = withJobWorkerStateLbls $ classifyJobWorkerState wId newSt

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

  throwLeft 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

  throwLeft res

data LoginOutcome
  = LoginSuccessful
  | LoginInvalidCredentials
  | LoginADInvalidCredentials
  | 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

registerJobHeldLocksCount :: MonadIO m => TVar (Set QueuedJobId) -> m ()
registerJobHeldLocksCount = liftIO . void . register . jobHeldLocksCount

data FileChunkStorage = StorageMinio | StorageDB | StorageARC | StoragePrewarm
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
  deriving anyclass (Universe, Finite)
nullaryPathPiece ''FileChunkStorage $ camelToPathPiece' 1

observeSunkChunk, observeSourcedChunk :: (Integral n, MonadIO m) => FileChunkStorage -> n -> m ()
observeSunkChunk store = liftIO . observeChunkSize sunkFileChunkSizes store . fromIntegral
observeSourcedChunk store = liftIO . observeChunkSize sourcedFileChunkSizes store . fromIntegral

observeChunkSize :: Vector Label1 Histogram -> FileChunkStorage -> Integer -> IO ()
observeChunkSize metric (toPathPiece -> storageLabel) = withLabel metric storageLabel . flip observe . fromInteger

observeDeletedUnreferencedFiles :: MonadIO m => Natural -> m ()
observeDeletedUnreferencedFiles = liftIO . void . addCounter deletedUnreferencedFiles . fromIntegral

observeDeletedUnreferencedChunks :: MonadIO m => Natural -> Word64 -> m ()
observeDeletedUnreferencedChunks num size = liftIO $ do
  void . addCounter deletedUnreferencedChunks $ fromIntegral num
  void . addCounter deletedUnreferencedChunksBytes $ fromIntegral size

observeInjectedFiles :: MonadIO m => Natural -> Word64 -> m ()
observeInjectedFiles num size = liftIO $ do
  void . addCounter injectedFiles $ fromIntegral num
  void . addCounter injectedFilesBytes $ fromIntegral size

observeRechunkedFiles :: MonadIO m => Natural -> Word64 -> m ()
observeRechunkedFiles num size = liftIO $ do
  void . addCounter rechunkedFiles $ fromIntegral num
  void . addCounter rechunkedFilesBytes $ fromIntegral size

registerJobWorkerQueueDepth :: MonadIO m => TMVar JobState -> m ()
registerJobWorkerQueueDepth = liftIO . void . register . jobWorkerQueueDepth

observeMissingFiles :: MonadIO m => Text -> Int -> m ()
observeMissingFiles refIdent = liftIO . withLabel missingFiles refIdent . flip setGauge . fromIntegral

observeDatabaseConnectionOpened, observeDatabaseConnectionClosed :: MonadIO m => m ()
observeDatabaseConnectionOpened = liftIO $ incCounter databaseConnectionsOpened
observeDatabaseConnectionClosed = liftIO $ incCounter databaseConnectionsClosed

observeAuthTagEvaluation :: MonadUnliftIO m => AuthTag -> String -> m (a, AuthTagEvalOutcome) -> m a
observeAuthTagEvaluation aTag handler act = do
  start <- liftIO $ getTime Monotonic
  res <- tryAny act
  end <- liftIO $ getTime Monotonic

  let outcome = case res of
        Right (_, outcome') -> outcome'
        Left _ -> OutcomeException
  
  liftIO . withLabel authTagEvaluationDuration (toPathPiece aTag, toPathPiece outcome, pack handler) . flip observe . realToFrac $ end - start

  either throwIO (views _1 return) res

observeFavouritesSkippedDueToDBLoad :: MonadIO m => m ()
observeFavouritesSkippedDueToDBLoad = liftIO $ incCounter favouritesSkippedDueToDBLoad
