{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Instana.SDK.Internal.Metrics.Delta
Description : Computes deltas between two metrics samples.
-}
module Instana.SDK.Internal.Metrics.Deltas
  ( deltaKeyList
  , enrichWithDeltas
  ) where


import           Data.HashMap.Strict                 (HashMap)
import qualified Data.HashMap.Strict                 as HashMap
import           Data.Text                           (Text)
import qualified Data.Text                           as T

import           Instana.SDK.Internal.Metrics.Sample (InstanaMetricValue,
                                                      TimedSample)
import qualified Instana.SDK.Internal.Metrics.Sample as Sample


-- |All metric keys from ekg-core that are eligible for being turned into deltas
-- (difference in value/second since the last metric collection).
deltaKeyList :: [Text]
deltaKeyList :: [Text]
deltaKeyList =
  [ "rts.gc.bytes_allocated"
  , "rts.gc.num_gcs"
  , "rts.gc.num_bytes_usage_samples"
  , "rts.gc.cumulative_bytes_used"
  , "rts.gc.bytes_copied"
  , "rts.gc.init_cpu_ms"
  , "rts.gc.init_wall_ms"
  , "rts.gc.mutator_cpu_ms"
  , "rts.gc.mutator_wall_ms"
  , "rts.gc.gc_cpu_ms"
  , "rts.gc.gc_wall_ms"
  , "rts.gc.cpu_ms"
  , "rts.gc.wall_ms"
  ]


-- |Calculates deltas and adds them to the sample.
enrichWithDeltas :: TimedSample -> TimedSample -> TimedSample
enrichWithDeltas :: TimedSample -> TimedSample -> TimedSample
enrichWithDeltas previousSample :: TimedSample
previousSample currentSample :: TimedSample
currentSample =
  let
    currentTimestamp :: Int
currentTimestamp = TimedSample -> Int
Sample.timestamp TimedSample
currentSample
    previousTimestamp :: Int
previousTimestamp = TimedSample -> Int
Sample.timestamp TimedSample
previousSample
    deltaT :: Int
deltaT = Int
currentTimestamp Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
previousTimestamp

    previousMetrics :: InstanaSample
previousMetrics = TimedSample -> InstanaSample
Sample.sample TimedSample
previousSample
    currentMetrics :: InstanaSample
currentMetrics = TimedSample -> InstanaSample
Sample.sample TimedSample
currentSample

    metricsWithDeltas :: InstanaSample
metricsWithDeltas =
      (Text -> InstanaMetricValue -> InstanaSample -> InstanaSample)
-> InstanaSample -> InstanaSample -> InstanaSample
forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
HashMap.foldrWithKey
        (Int
-> InstanaSample
-> Text
-> InstanaMetricValue
-> InstanaSample
-> InstanaSample
addDeltaToSample Int
deltaT InstanaSample
previousMetrics)
        InstanaSample
currentMetrics
        InstanaSample
currentMetrics
  in
    InstanaSample -> Int -> TimedSample
Sample.mkTimedSample InstanaSample
metricsWithDeltas Int
currentTimestamp


addDeltaToSample ::
  Int
  -> HashMap Text InstanaMetricValue
  -> Text
  -> InstanaMetricValue
  -> HashMap Text InstanaMetricValue
  -> HashMap Text InstanaMetricValue
addDeltaToSample :: Int
-> InstanaSample
-> Text
-> InstanaMetricValue
-> InstanaSample
-> InstanaSample
addDeltaToSample deltaT :: Int
deltaT previousMetrics :: InstanaSample
previousMetrics metricKey :: Text
metricKey currentMetricValue :: InstanaMetricValue
currentMetricValue currentMetrics :: InstanaSample
currentMetrics  =
  if Bool -> Bool
not (Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Text
metricKey [Text]
deltaKeyList) then
    InstanaSample
currentMetrics
  else
    let
      previousMetricValue :: Maybe InstanaMetricValue
previousMetricValue = Text -> InstanaSample -> Maybe InstanaMetricValue
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HashMap.lookup Text
metricKey InstanaSample
previousMetrics
    in
    case (Maybe InstanaMetricValue
previousMetricValue, InstanaMetricValue
currentMetricValue) of
      -- We are only interested in integer metrics here. Reason: The ekg package
      -- only emits integer values and only the deltas are fractional values. We
      -- never want to compute a delta of two deltas, that wouldn't make sense.
      (Just (Sample.IntegralValue previousValue :: Int
previousValue),
        Sample.IntegralValue currentValue :: Int
currentValue) ->
        Text -> Int -> Int -> InstanaSample -> InstanaSample
addNormalizedDelta
          Text
metricKey
          Int
deltaT
          (Int
currentValue Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
previousValue)
          InstanaSample
currentMetrics
      -- The metric is present in the current sample but was not present in the
      -- previous sample, that must be the first sample taken ever. Assume zero
      -- for the previous value.
      (Nothing, Sample.IntegralValue currentValue :: Int
currentValue) ->
        Text -> Int -> Int -> InstanaSample -> InstanaSample
addNormalizedDelta
          Text
metricKey
          Int
deltaT
          Int
currentValue
          InstanaSample
currentMetrics
      _ ->
        InstanaSample
currentMetrics


addNormalizedDelta ::
  Text
  -> Int
  -> Int
  -> HashMap Text InstanaMetricValue
  -> HashMap Text InstanaMetricValue
addNormalizedDelta :: Text -> Int -> Int -> InstanaSample -> InstanaSample
addNormalizedDelta metricKey :: Text
metricKey deltaT :: Int
deltaT deltaV :: Int
deltaV metrics :: InstanaSample
metrics =
  let
    deltaKey :: Text
deltaKey = Text -> Text -> Text
T.append Text
metricKey "_delta"
    -- Normalize difference to a one second timespan, no matter how much time
    -- elapsed between taking the two samples. The provided timestamps (and thus
    -- deltaT) are in milliseconds.
    normalizedDeltaV :: InstanaMetricValue
normalizedDeltaV =
      Double -> InstanaMetricValue
Sample.FractionalValue (Double -> InstanaMetricValue) -> Double -> InstanaMetricValue
forall a b. (a -> b) -> a -> b
$
        (Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
deltaV Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
deltaT) Double -> Double -> Double
forall a. Num a => a -> a -> a
* 1000
  in
  Text -> InstanaMetricValue -> InstanaSample -> InstanaSample
forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
HashMap.insert
    Text
deltaKey
    InstanaMetricValue
normalizedDeltaV
    InstanaSample
metrics