network-metrics-0.1.2: src/Network/Metrics/Ganglia.hs
-- |
-- Module : Network.Metrics.Ganglia
-- Copyright : (c) 2012 Brendan Hay <brendan@soundcloud.com>
-- License : This Source Code Form is subject to the terms of
-- the Mozilla Public License, v. 2.0.
-- A copy of the MPL can be found in the LICENSE file or
-- you can obtain it at http://mozilla.org/MPL/2.0/.
-- Maintainer : Brendan Hay <brendan@soundcloud.com>
-- Stability : experimental
-- Portability : non-portable (GHC extensions)
--
module Network.Metrics.Ganglia (
-- * Exported types
Slope(..)
, MetricType(..)
, Metric(..)
-- * Default constructors
, defaultMetric
-- * Socket Handle operations
, open
, emit
-- * Development helpers
, test
-- * Re-exported from internal
, I.Handle
, I.close
) where
import Control.Concurrent (threadDelay)
import Control.Monad (unless, liftM)
import Data.Binary.Put
import Data.Bits ((.&.))
import Data.Char (toLower)
import Data.Data (Data, Typeable)
import Data.Default (Default, def)
import Data.Int (Int32)
import Data.Word (Word32)
import Network.Socket
import System.Random (randomRIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BS
import qualified Network.Metrics.Internal as I
-- | Allows gmetad and the PHP webfrontend to efficiently separate
-- constant data metrics from volatile ones
data Slope = Zero | Positive | Negative | Both | Unspecified
deriving (Data, Typeable, Show, Eq, Enum)
-- | Metric types supported by Ganglia
data MetricType = String | Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 | Float | Double
deriving (Data, Typeable, Eq, Show)
-- | Concrete metric type used to emit metadata and value packets
data Metric = Metric
{ name :: BS.ByteString
, type' :: MetricType
, units :: BS.ByteString
, value :: BS.ByteString
, host :: BS.ByteString
, spoof :: BS.ByteString
, group :: BS.ByteString
, slope :: Slope
, tmax :: Word32
, dmax :: Word32
} deriving (Show)
instance Default Metric where
def = defaultMetric
--
-- API
--
-- | A default metric record
defaultMetric :: Metric
defaultMetric = Metric
{ name = "magical_metric"
, type' = UInt16
, units = ""
, value = "0"
, host = ""
, spoof = ""
, group = ""
, slope = Both
, tmax = 60
, dmax = 0
}
-- | Create a new unconnected socket handle for UDP communication
open :: String -> String -> IO I.Handle
open = I.open Datagram
-- | Emit a metric's metadata and value on the specified socket handle
emit :: Metric -> I.Handle -> IO I.Handle
emit metric handle@(I.Handle sock addr) = do
sIsConnected sock >>= \b -> unless b $ connect sock addr
_ <- push putMetaData handle
_ <- push putMetric handle
return handle
where
push fn = I.emit . runPut $ fn metric
--
-- Private
--
-- TODO: enforce max buffer size length checks.
-- Magic number is per libgmond.c
bufferSize :: Integer
bufferSize = 1500
--
-- Binary Encoding
--
-- | Metric metadata
--
-- The format for this can be found in either:
-- * gm_protocol.x in the Ganglia 3.1 sources
-- * https://github.com/lookfirst/jmxtrans
putMetaData :: Metric -> Put
putMetaData metric@Metric{..} = do
putHeader 128 metric -- 128 = metadata_msg
putType type'
putString name
putString units
putEnum slope
putUInt tmax
putUInt dmax
putGroup group
-- | Metric value
putMetric :: Metric -> Put
putMetric metric@Metric{..} = do
putHeader 133 metric -- 133 = string_msg
putString "%s"
putString value
-- | Common headers for the metadata and value
putHeader :: Int32 -> Metric -> Put
putHeader code Metric{..} = do
putInt code
putString host
putString name
putString spoof
-- | Encode either a end of message delimiter or
-- an extra group field (Ganglia 3.1 only)
putGroup :: BS.ByteString -> Put
putGroup group | BS.null group = putInt 0
| otherwise = do
putInt 1
putString "GROUP"
putString group
putInt :: Int32 -> Put
putInt = putWord32be . fromIntegral
putUInt :: Word32 -> Put
putUInt = putWord32be
putEnum :: Enum a => a -> Put
putEnum = putInt . fromIntegral . fromEnum
putString :: BS.ByteString -> Put
putString bstr = do
putInt $ fromIntegral len
putByteString bstr
case fromIntegral len .&. 3 of
0 -> return ()
m -> putByteString $ B.replicate (4 - m) 0
where
len = BS.length bstr
putType :: MetricType -> Put
putType = putString . BS.pack . map toLower . show
--
-- Development
--
-- | Exactly one second!
oneSecond :: Int
oneSecond = 1000000
-- | The min, max variance of test metrics
variance :: (Int, Int)
variance = (0, 1000)
-- | Sample a random element from a list
sample :: [a] -> IO a
sample xs = liftM (xs !!) (randomRIO (0, length xs - 1))
-- | Used to emit a default metric via GHCi
test :: String -> String -> IO a
test host port = open host port >>= loop
where
loop h = do
r <- randomRIO variance :: IO Int
n <- sample ["magic", "candy", "unicorns"]
f <- emit defaultMetric { name = n, value = BS.pack $ show r } h
threadDelay oneSecond
loop f