network-carbon-1.0.0: src/Network/Carbon/Plaintext.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.Carbon.Plaintext
( -- * Interacting with Carbon
-- ** Connections
Connection(..)
, connect
, disconnect
-- ** Metrics
, sendMetrics
, sendMetric
, Metric(..)
-- * Protocol details
, encodeMetric
)
where
import Control.Monad (unless)
import Data.Monoid ((<>), mempty, mappend)
import Data.Typeable (Typeable)
import qualified Data.ByteString.Builder as Builder
import qualified Data.Time as Time
import qualified Data.Time.Clock.POSIX as Time
import qualified Data.Vector as V
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Network.Socket as Network
import qualified Network.Socket.ByteString.Lazy as Network
--------------------------------------------------------------------------------
-- | Low-level representation of a Carbon connection. It's suggested that you
-- construct this via 'connect'. It is henceforth assumed that
-- 'Network.getPeerName' will return something useful, which usually means
-- 'connectionSocket' should have been 'Network.connect'ed at least once.
data Connection = Connection
{ connectionSocket :: !Network.Socket
-- ^ The connection socket to Carbon.
}
deriving (Eq, Show, Typeable)
--------------------------------------------------------------------------------
-- | Connect to Carbon.
connect :: Network.SockAddr -> IO Connection
connect sockAddr = fmap Connection $ do
s <- Network.socket Network.AF_INET Network.Stream Network.defaultProtocol
Network.connect s sockAddr
return s
--------------------------------------------------------------------------------
-- | Disconnect from Carbon. Note that it's still valid to 'sendMetrics' to this
-- 'Connection', and it will result in a reconnection.
disconnect :: Connection -> IO ()
disconnect (Connection s) = Network.close s
--------------------------------------------------------------------------------
reconnect :: Connection -> IO ()
reconnect (Connection s) = do
peer <- Network.getPeerName s
Network.connect s peer
--------------------------------------------------------------------------------
-- | A single data point. A metric has a path that names it, a value, and the
-- time the metric was sampled.
data Metric = Metric
{ metricPath :: !Text.Text
, metricValue :: !Double
, metricTimeStamp :: !Time.UTCTime
}
deriving (Eq, Show, Typeable)
--------------------------------------------------------------------------------
-- | Send a collection of metrics to Carbon.
sendMetrics :: Connection -> V.Vector Metric -> IO ()
sendMetrics c ms = do
let socket = connectionSocket c
do isWritable <- Network.isWritable socket
unless isWritable (reconnect c)
Network.sendAll socket (Builder.toLazyByteString (V.foldl' mappend mempty (V.map encodeMetric ms)))
--------------------------------------------------------------------------------
-- | Send a single metric.
sendMetric :: Connection -> Text.Text -> Double -> Time.UTCTime -> IO ()
sendMetric c k v t = sendMetrics c (V.singleton (Metric k v t))
--------------------------------------------------------------------------------
-- | Encode a 'Metric' for transmission over the plain text protocol.
encodeMetric :: Metric -> Builder.Builder
encodeMetric (Metric k v t) =
Builder.byteString (Text.encodeUtf8 k) <> " " <>
Builder.stringUtf8 (show v) <> " " <>
Builder.stringUtf8 (show (round (Time.utcTimeToPOSIXSeconds t) :: Int)) <> "\n"