module Network.StatsD
( StatsD
, mkStatsD, openStatsD, closeStatsD
, Stat(..), stat
, push, showStat
) where
import Control.Monad.Writer
import qualified Data.ByteString as BS
import Data.List
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
import Network.Socket
import qualified Network.Socket.ByteString as BS
import qualified Network.Socket.ByteString.Lazy as BL
data StatsD = StatsD
{ connection :: !Socket
, prefix :: !T.Text
} deriving (Eq, Show)
mkStatsD :: Socket -> [String] -> StatsD
mkStatsD s prefix = StatsD
{ connection = s
, prefix = T.pack . concat . concat $ [[part, "."] | part <- prefix]
}
openStatsD host port prefix = do
let hints = defaultHints
{ addrFamily = AF_INET
, addrSocketType = Datagram
}
s <- socket AF_INET Datagram defaultProtocol
addrInfos <- getAddrInfo (Just hints) (Just host) (Just port)
case addrInfos of
[] -> fail "Could not resolve host and/or port"
addrInfo : _ -> connect s (addrAddress addrInfo)
return (mkStatsD s prefix)
data Stat = Stat
{ bucket :: !T.Text
, val :: !T.Text
, unit :: !T.Text
, sample :: !(Maybe Double)
} deriving (Eq, Show)
stat :: (Num a, Show a) => [String] -> a -> String -> Maybe Double -> Stat
stat b v u = Stat (T.pack (intercalate "." b)) (T.pack (show v)) (T.pack u)
showStat = T.unpack . fmt T.empty
fmt prefix Stat{..} = T.concat $ execWriter $ do
let colon = T.singleton ':'
bar = T.singleton '|'
bar_at = T.pack "|@"
tell [prefix, bucket, colon, val, bar, unit]
case sample of
Nothing -> return ()
Just sample -> tell [bar_at, T.pack (show sample)]
segment = id
fmtMany prefix = map (T.encodeUtf8 . fmt prefix)
push statsd = mapM_ (BL.sendAll (connection statsd)) . segment . fmtMany (prefix statsd)
closeStatsD = sClose . connection