packages feed

instrument-0.6.1.0: src/Instrument/Types.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Instrument.Types
  ( createInstrumentPool,
    Samplers,
    Counters,
    Instrument (..),
    InstrumentConfig (..),
    SubmissionPacket (..),
    MetricName (..),
    DimensionName (..),
    DimensionValue (..),
    Dimensions,
    Payload (..),
    Aggregated (..),
    AggPayload (..),
    Stats (..),
    hostDimension,
    HostDimensionPolicy (..),
    Quantile (..),
  )
where

-------------------------------------------------------------------------------
import Control.Applicative as A
import qualified Data.ByteString.Char8 as B
import Data.Default
import Data.IORef
import qualified Data.Map as M
import Data.Monoid as Monoid
import qualified Data.SafeCopy as SC
import Data.Serialize as Ser
import Data.Serialize.Text ()
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import           Database.Redis        as R
import GHC.Generics
-------------------------------------------------------------------------------
import qualified Instrument.Counter as C
import qualified Instrument.Sampler as S
import Network.HostName

-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
createInstrumentPool :: ConnectInfo -> IO Connection
createInstrumentPool ci =
    connect
      ci
        { connectMaxIdleTime = 15,
          connectMaxConnections = 1
        }

-------------------------------------------------------------------------------

newtype DimensionName = DimensionName
  { dimensionName :: Text
  }
  deriving (Eq, Ord, Show, Generic, Serialize, IsString)

$(SC.deriveSafeCopy 0 'SC.base ''DimensionName)

-- | Convention for the dimension of the hostname. Used in the client
-- to inject hostname into the parameters map
hostDimension :: DimensionName
hostDimension = "host"

newtype DimensionValue = DimensionValue
  { dimensionValue :: Text
  }
  deriving (Eq, Ord, Show, Generic, Serialize, IsString)

$(SC.deriveSafeCopy 0 'SC.base ''DimensionValue)

newtype MetricName = MetricName
  { metricName :: String
  }
  deriving (Eq, Show, Generic, Ord, IsString, Serialize)

$(SC.deriveSafeCopy 0 'SC.base ''MetricName)

-------------------------------------------------------------------------------

-- Map of user-defined samplers.
type Samplers = M.Map (MetricName, Dimensions) S.Sampler

-- Map of user-defined counters.
type Counters = M.Map (MetricName, Dimensions) C.Counter

type Dimensions = M.Map DimensionName DimensionValue

data Payload_v0
  = Samples_v0 {unSamples_v0 :: [Double]}
  | Counter_v0 {unCounter_v0 :: Int}
  deriving (Eq, Show, Generic)

instance Serialize Payload_v0

data Payload
  = Samples {unSamples :: [Double]}
  | Counter {unCounter :: Integer}
  deriving (Eq, Show, Generic)

instance Serialize Payload

$(SC.deriveSafeCopy 0 'SC.base ''Payload_v0)
$(SC.deriveSafeCopy 1 'SC.extension ''Payload)

instance SC.Migrate Payload where
  type MigrateFrom Payload = Payload_v0
  migrate (Samples_v0 n) = Samples n
  migrate (Counter_v0 n) = Counter $ fromIntegral n

data Instrument = I
  { hostName :: HostName,
    samplers :: !(IORef Samplers),
    counters :: !(IORef Counters),
    redis :: Connection
  }

data InstrumentConfig = ICfg
  { redisQueueBound :: Maybe Integer
  }

instance Default InstrumentConfig where
  def = ICfg Nothing

-- | Submitted package of collected samples
data SubmissionPacket_v0 = SP_v0
  { -- | Timing of this submission
    spTimeStamp_v0 :: !Double,
    -- | Who sent it
    spHostName_v0 :: !HostName,
    -- | Metric name
    spName_v0 :: String,
    -- | Collected values
    spPayload_v0 :: Payload
  }
  deriving (Eq, Show, Generic)

instance Serialize SubmissionPacket_v0

data SubmissionPacket_v1 = SP_v1
  { -- | Timing of this submission
    spTimeStamp_v1 :: !Double,
    -- | Metric name
    spName_v1 :: !MetricName,
    -- | Collected values
    spPayload_v1 :: !Payload_v0,
    -- | Defines slices that this packet belongs to. This allows
    -- drill-down on the backends. For instance, you could do
    -- "server_name" "app1" or "queue_name" "my_queue"
    spDimensions_v1 :: !Dimensions
  }
  deriving (Eq, Show, Generic)

instance Serialize SubmissionPacket_v1

data SubmissionPacket = SP
  { -- | Timing of this submission
    spTimeStamp :: !Double,
    -- | Metric name
    spName :: !MetricName,
    -- | Collected values
    spPayload :: !Payload,
    -- | Defines slices that this packet belongs to. This allows
    -- drill-down on the backends. For instance, you could do
    -- "server_name" "app1" or "queue_name" "my_queue"
    spDimensions :: !Dimensions
  }
  deriving (Eq, Show, Generic)

instance Serialize SubmissionPacket where
  get = (to <$> gGet)  <|> (upgradeSP1 <$> Ser.get) <|> (upgradeSP0 <$> Ser.get)

upgradeSP0 :: SubmissionPacket_v0 -> SubmissionPacket
upgradeSP0 SP_v0 {..} =
  SP
    { spTimeStamp = spTimeStamp_v0,
      spName = MetricName spName_v0,
      spPayload = spPayload_v0,
      spDimensions = M.singleton hostDimension (DimensionValue (T.pack spHostName_v0))
    }

upgradeSP1 :: SubmissionPacket_v1 -> SubmissionPacket
upgradeSP1 SP_v1 {..} =
  SP
    { spTimeStamp = spTimeStamp_v1,
      spName = spName_v1,
      spPayload = case spPayload_v1 of
        Samples_v0 n -> Samples n
        Counter_v0 n -> Counter $ fromIntegral n,
      spDimensions = spDimensions_v1
    }

$(SC.deriveSafeCopy 0 'SC.base ''SubmissionPacket_v0)
$(SC.deriveSafeCopy 1 'SC.extension ''SubmissionPacket)

instance SC.Migrate SubmissionPacket where
  type MigrateFrom SubmissionPacket = SubmissionPacket_v0
  migrate = upgradeSP0

-------------------------------------------------------------------------------

-- | Should we automatically pull the host and add it as a
-- dimension. Used at the call site of the various metrics ('timeI',
-- 'sampleI', etc). Hosts are basically grandfathered in as a
-- dimension and the functionality of automatically injecting them is
-- useful, but it is not relevant to some metrics and actually makes
-- some metrics difficult to use depending on the backend, so we made
-- them opt-in.
data HostDimensionPolicy
  = AddHostDimension
  | DoNotAddHostDimension
  deriving (Show, Eq)

-------------------------------------------------------------------------------
data Stats = Stats
  { smean :: Double,
    ssum :: Double,
    scount :: Int,
    smax :: Double,
    smin :: Double,
    srange :: Double,
    sstdev :: Double,
    sskewness :: Double,
    skurtosis :: Double,
    squantiles :: M.Map Int Double
  }
  deriving (Eq, Show, Generic)

instance Default Stats where
  def = Stats 0 0 0 0 0 0 0 0 0 mempty

instance Serialize Stats

$(SC.deriveSafeCopy 0 'SC.base ''Stats)

-------------------------------------------------------------------------------
-- | Resulting payload for metrics aggregation
data AggPayload_v0
  = AggStats_v0 Stats
  | AggCount_v0 Int
  deriving (Eq, Show, Generic)

instance Serialize AggPayload_v0

data AggPayload
  = AggStats Stats
  | AggCount Integer
  deriving (Eq, Show, Generic)

instance Serialize AggPayload

instance Default AggPayload where
  def = AggStats def

$(SC.deriveSafeCopy 0 'SC.base ''AggPayload_v0)
$(SC.deriveSafeCopy 1 'SC.extension ''AggPayload)

instance SC.Migrate AggPayload where
  type MigrateFrom AggPayload = AggPayload_v0
  migrate (AggStats_v0 n) = AggStats n
  migrate (AggCount_v0 n) = AggCount $ fromIntegral n

-------------------------------------------------------------------------------
data Aggregated = Aggregated
  { -- | Timestamp for this aggregation
    aggTS :: Double,
    -- | Name of the metric
    aggName :: MetricName,
    -- | Calculated stats for the metric
    aggPayload :: AggPayload,
    aggDimensions :: Dimensions
  }
  deriving (Eq, Show, Generic)

instance Serialize Aggregated where
  get = (to <$> gGet) <|> (upgradeAggregated_v2 <$> Ser.get) <|> (upgradeAggregated_v2 . upgradeAggregated_v1 <$> Ser.get)

instance Default Aggregated where
  def = Aggregated 0 "" def mempty

data Aggregated_v2 = Aggregated_v2
  { -- | Timestamp for this aggregation
    aggTS_v2 :: Double,
    -- | Name of the metric
    aggName_v2 :: MetricName,
    -- | Calculated stats for the metric
    aggPayload_v2 :: AggPayload_v0,
    aggDimensions_v2 :: Dimensions
  }
  deriving (Eq, Show, Generic)

instance Serialize Aggregated_v2

upgradeAggregated_v2 :: Aggregated_v2 -> Aggregated
upgradeAggregated_v2 a =
  Aggregated
    { aggTS = aggTS_v2 a,
      aggName = aggName_v2 a,
      aggPayload = SC.migrate (aggPayload_v2 a),
      aggDimensions = aggDimensions_v2 a
    }

data Aggregated_v1 = Aggregated_v1
  { -- | Timestamp for this aggregation
    aggTS_v1 :: Double,
    -- | Name of the metric
    aggName_v1 :: MetricName,
    -- | The aggregation level/group for this stat
    aggGroup_v1 :: B.ByteString,
    -- | Calculated stats for the metric
    aggPayload_v1 :: AggPayload_v0
  }
  deriving (Eq, Show, Generic)

upgradeAggregated_v1 :: Aggregated_v1 -> Aggregated_v2
upgradeAggregated_v1 a =
  Aggregated_v2
    { aggTS_v2 = aggTS_v1 a,
      aggName_v2 = aggName_v1 a,
      aggPayload_v2 = aggPayload_v1 a,
      aggDimensions_v2 = Monoid.mempty
    }

data Aggregated_v0 = Aggregated_v0
  { -- | Timestamp for this aggregation
    aggTS_v0 :: Double,
    -- | Name of the metric
    aggName_v0 :: String,
    -- | The aggregation level/group for this stat
    aggGroup_v0 :: B.ByteString,
    -- | Calculated stats for the metric
    aggPayload_v0 :: AggPayload_v0
  }
  deriving (Eq, Show, Generic)

instance Serialize Aggregated_v0

upgradeAggregated_v0 :: Aggregated_v0 -> Aggregated_v1
upgradeAggregated_v0 a =
  Aggregated_v1
    { aggTS_v1 = aggTS_v0 a,
      aggName_v1 = MetricName (aggName_v0 a),
      aggGroup_v1 = aggGroup_v0 a,
      aggPayload_v1 = aggPayload_v0 a
    }

instance Serialize Aggregated_v1 where
  get = (to <$> gGet) <|> (upgradeAggregated_v0 <$> Ser.get)

$(SC.deriveSafeCopy 0 'SC.base ''Aggregated_v0)

instance SC.Migrate Aggregated_v1 where
  type MigrateFrom Aggregated_v1 = Aggregated_v0
  migrate = upgradeAggregated_v0

$(SC.deriveSafeCopy 1 'SC.extension ''Aggregated_v1)

instance SC.Migrate Aggregated_v2 where
  type MigrateFrom Aggregated_v2 = Aggregated_v1
  migrate = upgradeAggregated_v1

$(SC.deriveSafeCopy 2 'SC.extension ''Aggregated_v2)

instance SC.Migrate Aggregated where
  type MigrateFrom Aggregated = Aggregated_v2
  migrate = upgradeAggregated_v2

$(SC.deriveSafeCopy 3 'SC.extension ''Aggregated)

-------------------------------------------------------------------------------

-- | Integer quantile, valid values range from 1-99, inclusive.
newtype Quantile = Q {quantile :: Int} deriving (Show, Eq, Ord)

instance Bounded Quantile where
  minBound = Q 1
  maxBound = Q 99