module System.Metrics.RRDTool.Internals where
import Control.Monad
import Control.Monad.Writer
import Data.Int
import Data.List
import Data.Time
import Data.Ord
import Data.Word
import System.Metrics
import Text.Printf
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
type IntervalSeconds = Int
type SourceValue = Int64
data DataSourceType
= DsGauge
| DsDerive
deriving (Eq, Show, Bounded, Enum)
data DataSource = DataSource
{ dsName :: T.Text
, dsMetric :: T.Text
, dsType :: DataSourceType
, dsHeartBeat :: IntervalSeconds
, dsMin :: Maybe SourceValue
, dsMax :: Maybe SourceValue
} deriving (Show, Eq)
gcSources
:: IntervalSeconds
-> [DataSource]
gcSources heartBeat = gcCounters ++ gcGauges
where
gcCounters = map (\(metric, name) -> DataSource name metric DsDerive heartBeat (Just 0) Nothing)
[("rts.gc.bytes_allocated", "bytes_allocated")
,("rts.gc.num_gcs", "num_gcs")
,("rts.gc.num_bytes_usage_samples", "num_bytes_usage_sam")
,("rts.gc.cumulative_bytes_used", "cumulative_bytes_us")
,("rts.gc.bytes_copied", "bytes_copied")
,("rts.gc.mutator_cpu_ms", "mutator_cpu_ms")
,("rts.gc.mutator_wall_ms", "mutator_wall_ms")
,("rts.gc.gc_cpu_ms", "gc_cpu_ms")
,("rts.gc.gc_wall_ms", "gc_wall_ms")
,("rts.gc.cpu_ms", "cpu_ms")
,("rts.gc.wall_ms", "wall_ms")
]
gcGauges = map (\(metric, name) -> DataSource name metric DsGauge heartBeat (Just 0) Nothing)
[("rts.gc.max_bytes_used", "max_bytes_used")
,("rts.gc.current_bytes_used", "current_bytes_used")
,("rts.gc.current_bytes_slop", "current_bytes_slop")
,("rts.gc.max_bytes_slop", "max_bytes_slop")
,("rts.gc.peak_megabytes_allocated", "peak_megabytes_allo")
]
data ConsolidationFunction
= CFLast
| CFAverage
| CFMin
| CFMax
deriving (Show, Eq)
data RoundRobinArchive = RoundRobinArchive
{ rraCf :: ConsolidationFunction
, rraXff :: Double
, rraPdpCount :: Int
, rraRecordCount :: Int
} deriving (Show, Eq)
data RoundRobinDatabase = RoundRobinDatabase
{ rrdToolPath :: FilePath
, rrdFilePath :: FilePath
, rrdSources :: HM.HashMap T.Text DataSource
, rrdArchives :: [RoundRobinArchive]
, rrdStore :: Store
, rrdStep :: IntervalSeconds
}
defineDataSource :: DataSource -> String
defineDataSource ds = printf "DS:%s:%s:%d:%s:%s"
(T.unpack $ dsName ds)
(case dsType ds of
DsDerive -> "DERIVE"
DsGauge -> "GAUGE" :: String)
(dsHeartBeat ds)
(showMaybeValue $ dsMin ds)
(showMaybeValue $ dsMax ds)
showMaybeValue :: Maybe SourceValue -> String
showMaybeValue = maybe "U" (showWord64 . fromIntegral)
defineRoundRobinArchive :: RoundRobinArchive -> String
defineRoundRobinArchive rra = printf "RRA:%s:%f:%d:%d"
(case rraCf rra of
CFAverage -> "AVERAGE"
CFMin -> "MIN"
CFMax -> "MAX"
CFLast -> "LAST" :: String)
(rraXff rra)
(rraPdpCount rra)
(rraRecordCount rra)
createRRDArgs :: RoundRobinDatabase -> [String]
createRRDArgs rrd = execWriter $ do
tell ["create", rrdFilePath rrd, "--no-overwrite", "-s", show $ rrdStep rrd]
forM_ (sortBy (comparing dsName) $ HM.elems $ rrdSources rrd)
$ tell . return . defineDataSource
forM_ (rrdArchives rrd) $ tell . return . defineRoundRobinArchive
updateRRDArgs :: RoundRobinDatabase -> HM.HashMap T.Text Value -> UTCTime -> Maybe [String]
updateRRDArgs rrd sample now =
let updateSpec = HM.elems $ HM.intersectionWith (,) sample $ rrdSources rrd
in if null updateSpec
then Nothing
else Just $ execWriter $ do
tell ["update", rrdFilePath rrd]
tell ["-t", intercalate ":" $ map (T.unpack . dsName . snd) updateSpec]
tell [epochTimeFromUTCTime now ++ concatMap (formatValue . fst) updateSpec]
epoch :: UTCTime
epoch = UTCTime (fromGregorian 1970 1 1) 0
epochTime :: UTCTime -> Integer
epochTime = floor . flip diffUTCTime epoch
epochTimeFromUTCTime :: UTCTime -> String
epochTimeFromUTCTime = show . epochTime
formatValue :: Value -> String
formatValue = (':':) . showMaybeValue . getMaybeValue
where getMaybeValue (Counter n) = Just n
getMaybeValue (Gauge n) = Just n
getMaybeValue _ = Nothing
showWord64 :: Word64 -> String
showWord64 = show