{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Instana.SDK.Internal.Metrics.Sample
Description : Instana's internal representation of metrics values and samples.
-}
module Instana.SDK.Internal.Metrics.Sample
  ( InstanaSample
  , InstanaMetricValue(..)
  , SampleJson(..)
  , TimedSample(..)
  , ValueJson(..)
  , ekgSampleToInstanaSample
  , ekgValueToInstanaValue
  , empty
  , encodeSample
  , encodeValue
  , isMarkedForReset
  , markForReset
  , mkTimedSample
  , timedSampleFromEkgSample
  ) where


import qualified Data.Aeson          as Aeson
import qualified Data.Aeson.Types    as A
import           Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import           Data.Text           (Text)
import qualified Data.Text           as T
import           GHC.Generics
import qualified System.Metrics      as Metrics


-- |A collection of metric values.
type InstanaSample = HashMap Text InstanaMetricValue


-- |A single metric value.
data InstanaMetricValue =
  -- |A string metric value.
    StringValue     Text
  -- |An integral metric value.
  | IntegralValue   Int
  -- |A fractional metric value.
  | FractionalValue Double
  deriving (InstanaMetricValue -> InstanaMetricValue -> Bool
(InstanaMetricValue -> InstanaMetricValue -> Bool)
-> (InstanaMetricValue -> InstanaMetricValue -> Bool)
-> Eq InstanaMetricValue
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: InstanaMetricValue -> InstanaMetricValue -> Bool
$c/= :: InstanaMetricValue -> InstanaMetricValue -> Bool
== :: InstanaMetricValue -> InstanaMetricValue -> Bool
$c== :: InstanaMetricValue -> InstanaMetricValue -> Bool
Eq, (forall x. InstanaMetricValue -> Rep InstanaMetricValue x)
-> (forall x. Rep InstanaMetricValue x -> InstanaMetricValue)
-> Generic InstanaMetricValue
forall x. Rep InstanaMetricValue x -> InstanaMetricValue
forall x. InstanaMetricValue -> Rep InstanaMetricValue x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep InstanaMetricValue x -> InstanaMetricValue
$cfrom :: forall x. InstanaMetricValue -> Rep InstanaMetricValue x
Generic, Int -> InstanaMetricValue -> ShowS
[InstanaMetricValue] -> ShowS
InstanaMetricValue -> String
(Int -> InstanaMetricValue -> ShowS)
-> (InstanaMetricValue -> String)
-> ([InstanaMetricValue] -> ShowS)
-> Show InstanaMetricValue
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [InstanaMetricValue] -> ShowS
$cshowList :: [InstanaMetricValue] -> ShowS
show :: InstanaMetricValue -> String
$cshow :: InstanaMetricValue -> String
showsPrec :: Int -> InstanaMetricValue -> ShowS
$cshowsPrec :: Int -> InstanaMetricValue -> ShowS
Show)


-- |Converts an ekg-core sample into an Instana sample.
ekgSampleToInstanaSample :: Metrics.Sample -> InstanaSample
ekgSampleToInstanaSample :: Sample -> InstanaSample
ekgSampleToInstanaSample =
  (Value -> InstanaMetricValue) -> Sample -> InstanaSample
forall v1 v2 k. (v1 -> v2) -> HashMap k v1 -> HashMap k v2
HashMap.map Value -> InstanaMetricValue
ekgValueToInstanaValue


-- |Converts an ekg-core metric value into an Instana metric value.
ekgValueToInstanaValue :: Metrics.Value -> InstanaMetricValue
ekgValueToInstanaValue :: Value -> InstanaMetricValue
ekgValueToInstanaValue ekgValue :: Value
ekgValue =
  case Value
ekgValue of
    Metrics.Label text :: Text
text     -> Text -> InstanaMetricValue
StringValue Text
text
    Metrics.Counter int64 :: Int64
int64  -> Int -> InstanaMetricValue
IntegralValue (Int64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
int64)
    Metrics.Gauge int64 :: Int64
int64    -> Int -> InstanaMetricValue
IntegralValue (Int64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
int64)
    Metrics.Distribution _ -> Text -> InstanaMetricValue
StringValue "distribution"


-- |A metrics sample with timestamp
data TimedSample =
  TimedSample
    {
      -- |The metrics sample
      TimedSample -> InstanaSample
sample    :: InstanaSample
      -- |The timestamp
    , TimedSample -> Int
timestamp :: Int
    , TimedSample -> Bool
resetNext :: Bool
    } deriving (TimedSample -> TimedSample -> Bool
(TimedSample -> TimedSample -> Bool)
-> (TimedSample -> TimedSample -> Bool) -> Eq TimedSample
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TimedSample -> TimedSample -> Bool
$c/= :: TimedSample -> TimedSample -> Bool
== :: TimedSample -> TimedSample -> Bool
$c== :: TimedSample -> TimedSample -> Bool
Eq, (forall x. TimedSample -> Rep TimedSample x)
-> (forall x. Rep TimedSample x -> TimedSample)
-> Generic TimedSample
forall x. Rep TimedSample x -> TimedSample
forall x. TimedSample -> Rep TimedSample x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep TimedSample x -> TimedSample
$cfrom :: forall x. TimedSample -> Rep TimedSample x
Generic, Int -> TimedSample -> ShowS
[TimedSample] -> ShowS
TimedSample -> String
(Int -> TimedSample -> ShowS)
-> (TimedSample -> String)
-> ([TimedSample] -> ShowS)
-> Show TimedSample
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TimedSample] -> ShowS
$cshowList :: [TimedSample] -> ShowS
show :: TimedSample -> String
$cshow :: TimedSample -> String
showsPrec :: Int -> TimedSample -> ShowS
$cshowsPrec :: Int -> TimedSample -> ShowS
Show)


-- |Creates an empty sample with a timestamp.
empty :: Int -> TimedSample
empty :: Int -> TimedSample
empty t :: Int
t =
  TimedSample :: InstanaSample -> Int -> Bool -> TimedSample
TimedSample {
    sample :: InstanaSample
sample    = InstanaSample
forall k v. HashMap k v
HashMap.empty
  , timestamp :: Int
timestamp = Int
t
  , resetNext :: Bool
resetNext = Bool
False
  }


-- |Creates a sample with a timestamp.
mkTimedSample :: InstanaSample -> Int -> TimedSample
mkTimedSample :: InstanaSample -> Int -> TimedSample
mkTimedSample sampledMetrics :: InstanaSample
sampledMetrics t :: Int
t =
  TimedSample :: InstanaSample -> Int -> Bool -> TimedSample
TimedSample {
    sample :: InstanaSample
sample    = InstanaSample
sampledMetrics
  , timestamp :: Int
timestamp = Int
t
  , resetNext :: Bool
resetNext = Bool
False
  }


-- |Creates a sample with a timestamp from an ekg-core sample.
timedSampleFromEkgSample :: Metrics.Sample -> Int -> TimedSample
timedSampleFromEkgSample :: Sample -> Int -> TimedSample
timedSampleFromEkgSample sampledMetrics :: Sample
sampledMetrics =
  InstanaSample -> Int -> TimedSample
mkTimedSample (Sample -> InstanaSample
ekgSampleToInstanaSample Sample
sampledMetrics)


-- |Marks the sample for a reset on the next metric collection tick.
markForReset :: TimedSample -> TimedSample
markForReset :: TimedSample -> TimedSample
markForReset timedSample :: TimedSample
timedSample =
  TimedSample
timedSample { resetNext :: Bool
resetNext = Bool
True }


-- |Checks if the sample is marked for reset.
isMarkedForReset :: TimedSample -> Bool
isMarkedForReset :: TimedSample -> Bool
isMarkedForReset = TimedSample -> Bool
resetNext


-- |Encodes a sample to JSON.
encodeSample :: InstanaSample -> A.Value
encodeSample :: InstanaSample -> Value
encodeSample metrics :: InstanaSample
metrics =
    InstanaSample -> Value -> Value
buildOne InstanaSample
metrics (Value -> Value) -> Value -> Value
forall a b. (a -> b) -> a -> b
$ Value
A.emptyObject
  where
    buildOne :: HashMap T.Text InstanaMetricValue -> A.Value -> A.Value
    buildOne :: InstanaSample -> Value -> Value
buildOne m :: InstanaSample
m o :: Value
o = (Value -> Text -> InstanaMetricValue -> Value)
-> Value -> InstanaSample -> Value
forall a k v. (a -> k -> v -> a) -> a -> HashMap k v -> a
HashMap.foldlWithKey' Value -> Text -> InstanaMetricValue -> Value
build Value
o InstanaSample
m

    build :: A.Value -> T.Text -> InstanaMetricValue -> A.Value
    build :: Value -> Text -> InstanaMetricValue -> Value
build m :: Value
m name :: Text
name val :: InstanaMetricValue
val = Value -> [Text] -> InstanaMetricValue -> Value
go Value
m (Text -> Text -> [Text]
T.splitOn "." Text
name) InstanaMetricValue
val

    go :: A.Value -> [T.Text] -> InstanaMetricValue -> A.Value
    go :: Value -> [Text] -> InstanaMetricValue -> Value
go (A.Object m :: Object
m) [str :: Text
str] val :: InstanaMetricValue
val      = Object -> Value
A.Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ Text -> Value -> Object -> Object
forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
HashMap.insert Text
str Value
metric Object
m
      where metric :: Value
metric = InstanaMetricValue -> Value
encodeValue InstanaMetricValue
val
    go (A.Object m :: Object
m) (str :: Text
str:rest :: [Text]
rest) val :: InstanaMetricValue
val = case Text -> Object -> Maybe Value
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HashMap.lookup Text
str Object
m of
        Nothing -> Object -> Value
A.Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ Text -> Value -> Object -> Object
forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
HashMap.insert Text
str (Value -> [Text] -> InstanaMetricValue -> Value
go Value
A.emptyObject [Text]
rest InstanaMetricValue
val) Object
m
        Just m' :: Value
m' -> Object -> Value
A.Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ Text -> Value -> Object -> Object
forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
HashMap.insert Text
str (Value -> [Text] -> InstanaMetricValue -> Value
go Value
m' [Text]
rest InstanaMetricValue
val) Object
m
    go v :: Value
v _ _                        = String -> Value -> Value
forall a. String -> Value -> a
typeMismatch "Object" Value
v

typeMismatch :: String   -- ^ The expected type
             -> A.Value  -- ^ The actual value encountered
             -> a
typeMismatch :: String -> Value -> a
typeMismatch expected :: String
expected actual :: Value
actual =
    String -> a
forall a. HasCallStack => String -> a
error (String -> a) -> String -> a
forall a b. (a -> b) -> a -> b
$ "when expecting a " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
expected String -> ShowS
forall a. [a] -> [a] -> [a]
++ ", encountered " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
name String -> ShowS
forall a. [a] -> [a] -> [a]
++
    " instead"
  where
    name :: String
name = case Value
actual of
        A.Object _ -> "Object"
        A.Array _  -> "Array"
        A.String _ -> "String"
        A.Number _ -> "Number"
        A.Bool _   -> "Boolean"
        A.Null     -> "Null"


-- |Encodes a single metric value to JSON
encodeValue :: InstanaMetricValue -> A.Value
encodeValue :: InstanaMetricValue -> Value
encodeValue (IntegralValue   n :: Int
n) = Int -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Int
n
encodeValue (FractionalValue f :: Double
f) = Double -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Double
f
encodeValue (StringValue     s :: Text
s) = Text -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Text
s


-- |A type wrapper to convert a sample to JSON.
newtype SampleJson = SampleJson InstanaSample
    deriving Int -> SampleJson -> ShowS
[SampleJson] -> ShowS
SampleJson -> String
(Int -> SampleJson -> ShowS)
-> (SampleJson -> String)
-> ([SampleJson] -> ShowS)
-> Show SampleJson
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SampleJson] -> ShowS
$cshowList :: [SampleJson] -> ShowS
show :: SampleJson -> String
$cshow :: SampleJson -> String
showsPrec :: Int -> SampleJson -> ShowS
$cshowsPrec :: Int -> SampleJson -> ShowS
Show

instance A.ToJSON SampleJson where
    toJSON :: SampleJson -> Value
toJSON (SampleJson s :: InstanaSample
s) = InstanaSample -> Value
encodeSample InstanaSample
s


-- |A type wrapper to convert a metric value to JSON.
newtype ValueJson = ValueJson InstanaMetricValue
    deriving Int -> ValueJson -> ShowS
[ValueJson] -> ShowS
ValueJson -> String
(Int -> ValueJson -> ShowS)
-> (ValueJson -> String)
-> ([ValueJson] -> ShowS)
-> Show ValueJson
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ValueJson] -> ShowS
$cshowList :: [ValueJson] -> ShowS
show :: ValueJson -> String
$cshow :: ValueJson -> String
showsPrec :: Int -> ValueJson -> ShowS
$cshowsPrec :: Int -> ValueJson -> ShowS
Show

instance A.ToJSON ValueJson where
    toJSON :: ValueJson -> Value
toJSON (ValueJson v :: InstanaMetricValue
v) = InstanaMetricValue -> Value
encodeValue InstanaMetricValue
v