instrument-0.6.1.0: src/Instrument/Utils.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
module Instrument.Utils
( formatDecimal,
formatInt,
showT,
showBS,
collect,
noDots,
encodeCompress,
decodeCompress,
indefinitely,
seconds,
milliseconds,
for,
)
where
-------------------------------------------------------------------------------
import Codec.Compression.GZip
import Control.Applicative ((<|>))
import Control.Concurrent (threadDelay)
import Control.Exception (SomeException)
import Control.Monad
import Control.Monad.Catch (Handler (..))
import Control.Retry
import qualified Data.ByteString.Char8 as B
import Data.ByteString.Lazy (fromStrict, toStrict)
import qualified Data.Map as M
import qualified Data.Map.Strict as MS
import qualified Data.SafeCopy as SC
import Data.Serialize
import Data.Text (Text)
import qualified Data.Text as T
import Numeric
import System.IO
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
collect ::
(Ord b) =>
[a] ->
(a -> b) ->
(a -> c) ->
M.Map b [c]
collect as mkKey mkVal = foldr step M.empty as
where
step x acc = MS.insertWith (++) (mkKey x) ([mkVal x]) acc
-------------------------------------------------------------------------------
noDots :: Text -> Text
noDots = T.intercalate "_" . T.splitOn "."
-------------------------------------------------------------------------------
showT :: Show a => a -> Text
showT = T.pack . show
showBS :: Show a => a -> B.ByteString
showBS = B.pack . show
-------------------------------------------------------------------------------
formatInt :: RealFrac a => a -> Text
formatInt i = showT ((floor i) :: Int)
-------------------------------------------------------------------------------
formatDecimal ::
RealFloat a =>
-- | Digits after the point
Int ->
-- | Add thousands sep?
Bool ->
-- | Number
a ->
Text
formatDecimal n th i =
let res = T.pack . showFFloat (Just n) i $ ""
in if th then addThousands res else res
-------------------------------------------------------------------------------
addThousands :: Text -> Text
addThousands t = T.concat [n', dec]
where
(n, dec) = T.span (/= '.') t
n' = T.reverse . T.intercalate "," . T.chunksOf 3 . T.reverse $ n
-------------------------------------------------------------------------------
-- | Serialize and compress with GZip in that order. This is the only
-- function we use for serializing to Redis.
encodeCompress :: SC.SafeCopy a => a -> B.ByteString
encodeCompress = toStrict . compress . runPutLazy . SC.safePut
-------------------------------------------------------------------------------
-- | Decompress from GZip and deserialize in that order. Tries to
-- decode SafeCopy first and falls back to Serialize if that fails to
-- account for old data. Note that encodeCompress only serializes to
-- SafeCopy so writes will be updated.
decodeCompress :: (SC.SafeCopy a, Serialize a) => B.ByteString -> Either String a
decodeCompress = decodeWithFallback . decompress . fromStrict
where
decodeWithFallback lbs = runGetLazy SC.safeGet lbs <|> decodeLazy lbs
-------------------------------------------------------------------------------
-- | Run an IO repeatedly with the given delay in microseconds. If
-- there are exceptions in the inner loop, they are logged to stderr,
-- prefixed with the given string context and retried at an exponential
-- backoff capped at 60 seconds between.
indefinitely :: String -> Int -> IO () -> IO ()
indefinitely ctx n = forever . delayed . logAndBackoff ctx
where
delayed = (>> threadDelay n)
-------------------------------------------------------------------------------
logAndBackoff :: String -> IO () -> IO ()
logAndBackoff ctx = recovering policy [h] . const
where
policy = capDelay (seconds 60) (exponentialBackoff (milliseconds 50))
h _ = Handler (\e -> logError e >> return True)
logError :: SomeException -> IO ()
logError e = hPutStrLn stderr msg
where
msg = "Caught exception in " ++ ctx ++ ": " ++ show e ++ ". Retrying..."
-------------------------------------------------------------------------------
-- | Convert seconds to microseconds
seconds :: Int -> Int
seconds = (* milliseconds 1000)
-------------------------------------------------------------------------------
-- | Convert milliseconds to microseconds
milliseconds :: Int -> Int
milliseconds = (* 1000)
-------------------------------------------------------------------------------
for :: (Functor f) => f a -> (a -> b) -> f b
for = flip fmap