prodapi-0.1.0.0: src/Prod/Watchdog.hs
module Prod.Watchdog where
import Control.Exception.Base (IOException, catch)
import Control.Monad (when)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Time.Clock (UTCTime, getCurrentTime)
import Prod.Background (BackgroundVal, MicroSeconds, backgroundLoop)
import qualified Prod.Background
import Prod.Tracer (Tracer (..), contramap)
import Prometheus (Counter, Label1, Label2, Vector)
import qualified Prometheus as Prometheus
import System.Directory (doesFileExist, setModificationTime)
data Track r = BackgroundTrack (Prod.Background.Track (WatchdogResult r))
deriving (Show)
data WatchdogResult a
= Skipped
| Success a
| Failed
deriving (Show, Ord, Eq)
data Watchdog a = Watchdog
{ backgroundVal :: BackgroundVal (WatchdogResult a)
, tracer :: Tracer IO (Track a)
}
watchdog ::
(Prometheus.Label label) =>
Vector label Counter ->
Tracer IO (Track a) ->
(WatchdogResult a -> label) ->
MicroSeconds Int ->
IO (WatchdogResult a) ->
IO (Watchdog a)
watchdog counters tracer mkLabel delay action =
Watchdog <$> backgroundLoop (contramap BackgroundTrack tracer) Skipped go delay <*> pure tracer
where
go = do
res <- action
Prometheus.withLabel counters (mkLabel res) Prometheus.incCounter
pure res
{- | Basic watchdog with a vector metric.
The input vector label is set with success|failed|skipped depending on the WatchdogResult.
-}
basicWatchdog ::
Vector Label1 Counter ->
Tracer IO (Track a) ->
MicroSeconds Int ->
IO (WatchdogResult a) ->
IO (Watchdog a)
basicWatchdog counters tracer delay action =
watchdog counters tracer basicLabel delay action
basicLabel :: WatchdogResult a -> Label1
basicLabel res = case res of
Success _ -> "success"
Failed -> "failed"
Skipped -> "skipped"
data FileTouchTrack r = FileTouchTrack FilePath (Track r)
deriving (Show)
{- | Touches a file periodically, using setModificationTime.
If the file does not exists when the watchdog is initialized, then it is
created empty.
-}
fileTouchWatchdog ::
FilePath ->
Tracer IO (FileTouchTrack UTCTime) ->
MicroSeconds Int ->
IO (Watchdog UTCTime)
fileTouchWatchdog path tracer delay = do
let mkLabel res = (basicLabel res, Text.pack path)
shouldCreate <- not <$> doesFileExist path
when shouldCreate $ writeFile path ""
watchdog fileTouchWatchdogCounter (contramap (FileTouchTrack path) tracer) mkLabel delay io
where
handleIOException :: IOException -> IO (WatchdogResult UTCTime)
handleIOException _ = pure $ Failed
io = do
now <- getCurrentTime
let touchFile = setModificationTime path now *> pure (Success now)
touchFile `catch` handleIOException
{-# NOINLINE fileTouchWatchdogCounter #-}
fileTouchWatchdogCounter :: Vector Label2 Counter
fileTouchWatchdogCounter =
Prometheus.unsafeRegister $
Prometheus.vector ("status", "path") $
Prometheus.counter (Prometheus.Info "prodapi_watchdog_filetouch" "")