{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Instana.SDK.Internal.Metrics.Collector
Description : Initializes the collection of metrics and samples them at
              regular intervals.
-}
module Instana.SDK.Internal.Metrics.Collector
  ( registerMetrics
  , sampleAll
  ) where


import qualified Data.List                                        as List
import           Data.Text                                        (Text)
import qualified Data.Text                                        as T
import           Data.Time.Clock.POSIX                            (getPOSIXTime)
import qualified Data.Version                                     as Version
import           Paths_instana_haskell_trace_sdk                  (version)
import qualified System.Metrics                                   as Metrics
import qualified System.SysInfo                                   as SysInfo

import           Instana.SDK.Internal.AgentConnection.ProcessInfo (ProcessInfo)
import qualified Instana.SDK.Internal.AgentConnection.ProcessInfo as ProcessInfo
import           Instana.SDK.Internal.Util                        ((|>))


{-| Creates the ekg metric store and registers all relevant metrics for regular
collection.
-}
registerMetrics :: String -> ProcessInfo -> Int -> IO Metrics.Store
registerMetrics translatedPid processInfo sdkStartTime = do
  -- registerMetrics is executed once more after each connection loss/reconnect.
  -- It should not be an actual problem, as the previous metrics store should
  -- have been garbage collected.

  instanaMetricsStore <- Metrics.newStore

  -- register Instana specific metrics (mostly snapshot data)
  registerCustomMetrics
    instanaMetricsStore
    translatedPid
    processInfo
    sdkStartTime

  -- register all predefined GC metrics provided by ekg
  Metrics.registerGcMetrics instanaMetricsStore
  return instanaMetricsStore


{-| Collects the current value for all registered metrics.
-}
sampleAll :: Metrics.Store -> IO Metrics.Sample
sampleAll = Metrics.sampleAll


{-| Registers custom metrics (not included in the ekg default metrics).
-}
registerCustomMetrics ::
  Metrics.Store
  -> String
  -> ProcessInfo
  -> Int
  -> IO ()
registerCustomMetrics
    instanaMetricsStore
    translatedPid
    processInfo
    sdkStartTime = do
  startTime <- calcStartTime sdkStartTime
  registerConstantLabelMetric
    instanaMetricsStore
    "pid"
    translatedPid
  registerConstantLabelMetric
    instanaMetricsStore
    "programName"
    (ProcessInfo.programName processInfo)
  registerConstantLabelMetric
    instanaMetricsStore
    "executablePath"
    (ProcessInfo.executablePath processInfo)
  registerConstantLabelMetric
    instanaMetricsStore
    "arguments"
    (ProcessInfo.arguments processInfo |> List.intercalate " ")
  registerConstantLabelMetric
    instanaMetricsStore
    "sensorVersion"
    (Version.showVersion version)
  registerConstantCounterMetric
    instanaMetricsStore
    "startTime"
    startTime


calcStartTime :: Int -> IO Int
calcStartTime sdkStartTime = do
  sysInfoOrError <- SysInfo.sysInfo
  now <- round . (* 1000) <$> getPOSIXTime
  case sysInfoOrError of
    Right sysInfo -> do
      return $ now - (fromIntegral $ SysInfo.uptime sysInfo)
    Left _ ->
      -- System.SysInfo is not available on non-Linux systems, we use the time
      -- when the SDK has been initialized as a fallback.
      return sdkStartTime


registerConstantLabelMetric :: Metrics.Store -> Text -> String -> IO ()
registerConstantLabelMetric instanaMetricsStore label value = do
  Metrics.registerLabel label (return $ T.pack value) instanaMetricsStore


registerConstantCounterMetric :: Metrics.Store -> Text -> Int -> IO ()
registerConstantCounterMetric instanaMetricsStore label value = do
  Metrics.registerCounter
    label
    (return $ fromIntegral value)
    instanaMetricsStore