{-# LANGUAGE OverloadedStrings #-}
module System.Metrics.Prometheus.Ridley.Metrics.Memory (
    processMemory
  ) where

import           Katip
import           System.Exit
import           System.Metrics.Prometheus.Ridley.Types
import           System.Metrics.Prometheus.Ridley.Types.Internal
import           System.Posix.Process
import           System.Process
import           Text.Read
import qualified Data.Text as T
import qualified System.Metrics.Prometheus.Metric.Gauge as P

--------------------------------------------------------------------------------
-- | Return the amount of occupied memory for this
-- process. We use unix's `ps` command that,
-- although has the reputation of not being 100%
-- accurate, at least works on Darwin and Linux
-- without using any CPP processor.
-- Returns the memory in Kb.
getProcessMemory :: Logger -> IO (Maybe Integer)
getProcessMemory :: Logger -> IO (Maybe Integer)
getProcessMemory Logger
logger = do
  ProcessID
myPid <- IO ProcessID
getProcessID
  (ExitCode
exitCode, String
rawOutput, String
errors) <- String -> [String] -> String -> IO (ExitCode, String, String)
readProcessWithExitCode String
"ps" [String
"-o", String
"rss=", String
"-p", ProcessID -> String
forall a. Show a => a -> String
show ProcessID
myPid] []
  case ExitCode
exitCode of
    ExitCode
ExitSuccess    -> Maybe Integer -> IO (Maybe Integer)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Integer -> IO (Maybe Integer))
-> Maybe Integer -> IO (Maybe Integer)
forall a b. (a -> b) -> a -> b
$ String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
rawOutput
    ExitFailure Int
ec -> do
      Logger
logger Severity
ErrorS (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
"getProcessMemory exited with error code " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show Int
ec) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack String
errors
      Maybe Integer -> IO (Maybe Integer)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Integer
forall a. Maybe a
Nothing

--------------------------------------------------------------------------------
-- | As this is a gauge, it makes no sense flushing it.
updateProcessMemory :: Logger -> P.Gauge -> Bool -> IO ()
updateProcessMemory :: Logger -> Gauge -> Bool -> IO ()
updateProcessMemory Logger
logger Gauge
g Bool
_ = do
  Maybe Integer
mbMem <- Logger -> IO (Maybe Integer)
getProcessMemory Logger
logger
  case Maybe Integer
mbMem of
    Maybe Integer
Nothing -> () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    Just Integer
m  -> Double -> Gauge -> IO ()
P.set (Integer -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
m) Gauge
g

--------------------------------------------------------------------------------
processMemory :: Logger -> P.Gauge -> RidleyMetricHandler
processMemory :: Logger -> Gauge -> RidleyMetricHandler
processMemory Logger
logger Gauge
g = do
  Text
-> Gauge -> (Gauge -> Bool -> IO ()) -> Bool -> RidleyMetricHandler
forall c.
HasCallStack =>
Text -> c -> (c -> Bool -> IO ()) -> Bool -> RidleyMetricHandler
mkRidleyMetricHandler Text
"ridley-process-memory" Gauge
g (Logger -> Gauge -> Bool -> IO ()
updateProcessMemory Logger
logger) Bool
False