packages feed

instrument-0.6.0.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
#if MIN_VERSION_hedis(0,12,0)
import           Database.Redis        as R
#else
import           Database.Redis        as R hiding (HostName, time)
#endif
import           GHC.Generics
import           Network.HostName
-------------------------------------------------------------------------------
import qualified Instrument.Counter    as C
import qualified Instrument.Sampler    as S
-------------------------------------------------------------------------------


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


-- 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


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


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 {
      spTimeStamp_v0 :: !Double
    -- ^ Timing of this submission
    , spHostName_v0  :: !HostName
    -- ^ Who sent it
    , spName_v0      :: String
    -- ^ Metric name
    , spPayload_v0   :: Payload
    -- ^ Collected values
    } deriving (Eq,Show,Generic)


instance Serialize SubmissionPacket_v0


data SubmissionPacket = SP {
      spTimeStamp  :: !Double
    -- ^ Timing of this submission
    , spName       :: !MetricName
    -- ^ Metric name
    , spPayload    :: !Payload
    -- ^ Collected values
    , spDimensions :: !Dimensions
    -- ^ 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"
    } deriving (Eq,Show,Generic)


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


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


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))
  }


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


-------------------------------------------------------------------------------
-- | 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)


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


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


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

instance Serialize Payload


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


instance Serialize Aggregated_v0


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


instance SC.Migrate Aggregated_v1 where
  type MigrateFrom Aggregated_v1 = Aggregated_v0
  migrate = upgradeAggregated_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)


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


upgradeAggregated_v1 :: Aggregated_v1 -> Aggregated
upgradeAggregated_v1 a = Aggregated
  { aggTS = aggTS_v1 a
  , aggName = aggName_v1 a
  , aggPayload = aggPayload_v1 a
  , aggDimensions = Monoid.mempty
  }


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


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


-- | Resulting payload for metrics aggregation
data AggPayload
    = AggStats Stats
    | AggCount Int
    deriving (Eq,Show, Generic)

instance Serialize AggPayload




instance Default AggPayload where
    def = AggStats def

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


-------------------------------------------------------------------------------
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


-------------------------------------------------------------------------------
-- | 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


-------------------------------------------------------------------------------
$(SC.deriveSafeCopy 0 'SC.base ''Payload)
$(SC.deriveSafeCopy 0 'SC.base ''SubmissionPacket_v0)
$(SC.deriveSafeCopy 1 'SC.extension ''SubmissionPacket)
$(SC.deriveSafeCopy 0 'SC.base ''AggPayload)
$(SC.deriveSafeCopy 0 'SC.base ''Aggregated_v0)
$(SC.deriveSafeCopy 1 'SC.extension ''Aggregated_v1)
$(SC.deriveSafeCopy 2 'SC.extension ''Aggregated)
$(SC.deriveSafeCopy 0 'SC.base ''Stats)
$(SC.deriveSafeCopy 0 'SC.base ''DimensionName)
$(SC.deriveSafeCopy 0 'SC.base ''DimensionValue)
$(SC.deriveSafeCopy 0 'SC.base ''MetricName)