monad-metrics 0.1.0.0 → 0.1.0.1
raw patch · 4 files changed
+189/−39 lines, 4 filesdep +microlensPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: microlens
API changes (from Hackage documentation)
- Control.Monad.Metrics.Internal: [metricsCounters] :: Metrics -> IORef (Map Text Counter)
- Control.Monad.Metrics.Internal: [metricsDistributions] :: Metrics -> IORef (Map Text Distribution)
- Control.Monad.Metrics.Internal: [metricsGauges] :: Metrics -> IORef (Map Text Gauge)
- Control.Monad.Metrics.Internal: [metricsLabels] :: Metrics -> IORef (Map Text Label)
- Control.Monad.Metrics.Internal: [metricsStore] :: Metrics -> Store
+ Control.Monad.Metrics: instance (Control.Monad.Metrics.MonadMetrics m, Control.Monad.Trans.Class.MonadTrans t, GHC.Base.Monad (t m)) => Control.Monad.Metrics.MonadMetrics (t m)
+ Control.Monad.Metrics: instance GHC.Base.Monad m => Control.Monad.Metrics.MonadMetrics (Control.Monad.Trans.Reader.ReaderT Control.Monad.Metrics.Internal.Metrics m)
+ Control.Monad.Metrics: metricsCounters :: Lens' Metrics (IORef (Map Text Counter))
+ Control.Monad.Metrics: metricsGauges :: Lens' Metrics (IORef (Map Text Gauge))
+ Control.Monad.Metrics: metricsLabels :: Lens' Metrics (IORef (Map Text Label))
+ Control.Monad.Metrics: metricsStore :: Lens' Metrics Store
+ Control.Monad.Metrics: run :: MonadIO m => ReaderT Metrics m a -> m a
+ Control.Monad.Metrics: run' :: MonadIO m => (Metrics -> r) -> ReaderT r m a -> m a
+ Control.Monad.Metrics.Internal: [_metricsCounters] :: Metrics -> IORef (Map Text Counter)
+ Control.Monad.Metrics.Internal: [_metricsDistributions] :: Metrics -> IORef (Map Text Distribution)
+ Control.Monad.Metrics.Internal: [_metricsGauges] :: Metrics -> IORef (Map Text Gauge)
+ Control.Monad.Metrics.Internal: [_metricsLabels] :: Metrics -> IORef (Map Text Label)
+ Control.Monad.Metrics.Internal: [_metricsStore] :: Metrics -> Store
+ Control.Monad.Metrics.Internal: metricsCounters :: Lens' Metrics (IORef (Map Text Counter))
+ Control.Monad.Metrics.Internal: metricsDistributions :: Lens' Metrics (IORef (Map Text Distribution))
+ Control.Monad.Metrics.Internal: metricsGauges :: Lens' Metrics (IORef (Map Text Gauge))
+ Control.Monad.Metrics.Internal: metricsLabels :: Lens' Metrics (IORef (Map Text Label))
+ Control.Monad.Metrics.Internal: metricsStore :: Lens' Metrics Store
- Control.Monad.Metrics: class MonadMetrics m
+ Control.Monad.Metrics: class Monad m => MonadMetrics m
Files
- README.md +18/−1
- monad-metrics.cabal +3/−2
- src/Control/Monad/Metrics.hs +123/−30
- src/Control/Monad/Metrics/Internal.hs +45/−6
README.md view
@@ -26,7 +26,7 @@ {-# LANGUAGE NoMonomorphismRestriction #-} import qualified Control.Monad.Metrics as Metrics-import Control.Monad.Metrics (Metrics, Resolution(..), MonadMetrics)+import Control.Monad.Metrics (Metrics, Resolution(..), MonadMetrics(..)) import Control.Monad.Reader import qualified System.Metrics as EKG ```@@ -68,6 +68,23 @@ ``` Now, you're off to the races! Let's record some metrics.++If you're after a really simple embedding, you can use `run` or `run'`:++```haskell+simple :: Int -> IO ()+simple i = + Metrics.run $ do+ metrics <- Metrics.getMetrics+ Metrics.gauge "Simple" i+ forM_ [1..i] $ \_ -> do+ Metrics.increment "Count!"++gettingThere :: IO ()+gettingThere = + Metrics.run' (\metrics -> Config metrics) $ do+ liftIO $ putStrLn "it accepts a constructor"+``` ### Measure!
monad-metrics.cabal view
@@ -1,7 +1,7 @@ name: monad-metrics-version: 0.1.0.0+version: 0.1.0.1 synopsis: A convenient wrapper around EKG metrics-description: Please see README.md+description: A convenient wrapper for collecting application metrics. Please see the README.md for more information. homepage: https://github.com/parsonsmatt/monad-metrics#readme license: MIT license-file: LICENSE@@ -24,6 +24,7 @@ , text < 1.3 , mtl >= 2 && < 2.3 , transformers >= 0.3 && < 0.6+ , microlens >= 0.2 && < 0.5 default-language: Haskell2010 test-suite monad-metrics-test
src/Control/Monad/Metrics.hs view
@@ -1,6 +1,12 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} {-| Module : Control.Monad.Metrics@@ -12,8 +18,10 @@ Stability : experimental Portability : POSIX -This module presents an easy interface that you can use to collect metrics about your application.-It uses EKG under the hood and is inspired by Taylor Fausak's blunt application.+This module presents an easy interface that you can use to collect metrics+about your application. It uses EKG from "System.Metrics" under the hood+and is inspired by Taylor Fausak's <https://github.com/tfausak/blunt blunt>+application. This module is designed to be imported qualified. -}@@ -21,10 +29,13 @@ ( -- * The Type Class MonadMetrics(..) -- * Initializing- , Metrics+ -- $initializing , initialize , initializeWith+ , run+ , run' -- * Collecting Metrics+ -- $collecting , increment , counter , counter'@@ -36,16 +47,26 @@ , label , label' , Resolution(..)+ -- * The Metrics Type+ -- $metrictype+ , Metrics+ , metricsCounters+ , metricsGauges+ , metricsLabels+ , metricsStore ) where -import Control.Monad (liftM)-import Data.Monoid (mempty)-import Control.Monad.IO.Class+import Control.Monad (liftM)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Reader (MonadReader (..), ReaderT (..))+import Control.Monad.Trans (MonadTrans (..)) import Data.IORef import Data.Map (Map) import qualified Data.Map as Map+import Data.Monoid (mempty) import Data.Text (Text) import qualified Data.Text as Text+import Lens.Micro import System.Clock (Clock (..), TimeSpec (..), getTime) import qualified System.Metrics as EKG@@ -54,73 +75,133 @@ import System.Metrics.Gauge as Gauge import System.Metrics.Label as Label -import Prelude+import Prelude import Control.Monad.Metrics.Internal --- | A class is an instance of 'MonadMetrics' if it can provide a 'Metrics'+-- | A type can be an instance of 'MonadMetrics' if it can provide a 'Metrics' -- somehow. Commonly, this will be implemented as a 'ReaderT' where some -- field in the environment is the 'Metrics' data. ----- Since v0.1.0.0-class MonadMetrics m where+-- * /Since v0.1.0.0/+class Monad m => MonadMetrics m where getMetrics :: m Metrics +instance {-# OVERLAPPABLE #-} (MonadMetrics m, MonadTrans t, Monad (t m)) => MonadMetrics (t m) where+ getMetrics = lift getMetrics++instance Monad m => MonadMetrics (ReaderT Metrics m) where+ getMetrics = ask++-- $initializing+-- This library tends to provide simple functions with plain names and+-- generalized functions with apostrophes. When initializing the metrics,+-- you can use 'initialize' if you don't need fine control over the store,+-- or you can use 'initializeWith' if your application already has a store+-- that it uses.+--+-- Likewise, we provide 'run' for the simplest case, and 'run'' for the+-- more complex case where you have some larger type.+--+-- The most flexible way to use the library is to implement the+-- 'MonadMetrics' class.++-- | Enhances the base monad with metrics. This works for very simple+-- cases, where you don't have a 'Reader' involved yet. If your stack+-- already has a 'Reader', then you'll get some annoying type problems with+-- this. Switch over to 'run'', or alternatively, define your own+-- 'MonadMetrics' instance.+--+-- */Since v0.1.0.0/+run :: MonadIO m => ReaderT Metrics m a -> m a+run = run' id++-- | Adds metric recording capabilities to the given action. The first+-- parameter is a function which accepts a 'Metrics' value and creates the+-- final @r@ value to be used in the action. This is useful when you have+-- a preexisting 'ReaderT' in your stack, and you want to enhance it with+-- metrics.+--+-- @+-- data Config = Config { size :: Int, metrics' :: Metrics }+--+-- main = 'runWithMetrics' (Config 10) $ do+-- num <- asks size+-- forM_ [1 .. size] \_ -> Metrics.increment "foo"+-- @+--+-- */Since v0.1.0.0/+run' :: MonadIO m => (Metrics -> r) -> ReaderT r m a -> m a+run' k action = do+ m <- liftIO initialize+ runReaderT action (k m)+ -- | Initializes a 'Metrics' value with the given 'System.Metrics.Store'. ----- Since 0.1.0.0+-- */Since v0.1.0.0/ initializeWith :: EKG.Store -> IO Metrics-initializeWith metricsStore = do- metricsCounters <- newIORef mempty- metricsDistributions <- newIORef mempty- metricsGauges <- newIORef mempty- metricsLabels <- newIORef mempty+initializeWith _metricsStore = do+ _metricsCounters <- newIORef mempty+ _metricsDistributions <- newIORef mempty+ _metricsGauges <- newIORef mempty+ _metricsLabels <- newIORef mempty return Metrics{..} -- | Initializes a 'Metrics' value, creating a new 'System.Metrics.Store' -- for it. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ initialize :: IO Metrics initialize = EKG.newStore >>= initializeWith +-- $collecting+-- As with initialization, the library provides "common case" functions+-- with a plain name and generalized functions with an apostrophe.+--+-- * 'increment', 'counter', 'counter''+-- * 'gauge', 'gauge''+-- * 'timed', 'timed''+-- * 'label', 'label''+--+-- Only 'distribution' isn't generalized.+ -- | Increment the named counter by 1. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ increment :: (MonadIO m, MonadMetrics m) => Text -> m () increment name = counter name 1 -- | Adds the value to the named 'System.Metrics.Counter.Counter'. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ counter' :: (MonadIO m, MonadMetrics m, Integral int) => Text -> int -> m () counter' =- modifyMetric Counter.add fromIntegral EKG.createCounter metricsCounters+ modifyMetric Counter.add fromIntegral EKG.createCounter _metricsCounters -- | A type specialized version of 'counter'' to avoid ambiguous type -- errors. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ counter :: (MonadIO m, MonadMetrics m) => Text -> Int -> m () counter = counter' -- | Add the value to the named 'System.Metrics.Distribution.Distribution'. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ distribution :: (MonadIO m, MonadMetrics m) => Text -> Double -> m () distribution =- modifyMetric Distribution.add id EKG.createDistribution metricsDistributions+ modifyMetric Distribution.add id EKG.createDistribution _metricsDistributions -- | Set the value of the named 'System.Metrics.Distribution.Gauge'. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ gauge' :: (MonadIO m, MonadMetrics m, Integral int) => Text -> int -> m () gauge' =- modifyMetric Gauge.set fromIntegral EKG.createGauge metricsGauges+ modifyMetric Gauge.set fromIntegral EKG.createGauge _metricsGauges -- | A type specialized version of 'gauge'' to avoid ambiguous types. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ gauge :: (MonadIO m, MonadMetrics m) => Text -> Int -> m () gauge = gauge' @@ -128,7 +209,7 @@ -- stored in a 'System.Metrics.Disribution.Distribution' and is converted -- to the specified 'Resolution'. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ timed' :: (MonadIO m, MonadMetrics m) => Resolution -> Text -> m a -> m a timed' resolution name action = do start <- liftIO $ getTime Monotonic@@ -140,16 +221,28 @@ -- | Record the time of executing the given action in seconds. Defers to -- 'timed''. ----- Since v0.1.0.0+-- * /Since v0.1.0.0/ timed :: (MonadIO m, MonadMetrics m) => Text -> m a -> m a timed = timed' Seconds +-- | Set the 'Label' to the given 'Text' value.+--+-- * /Since v0.1.0.0/ label :: (MonadIO m, MonadMetrics m) => Text -> Text -> m ()-label = modifyMetric Label.set id EKG.createLabel metricsLabels+label = modifyMetric Label.set id EKG.createLabel _metricsLabels +-- | Set the 'Label' to the 'Show'n value of whatever you pass in.+--+-- * /Since v0.1.0.0/ label' :: (MonadIO m, MonadMetrics m, Show a) => Text -> a -> m () label' l = label l . Text.pack . show +-- $metrictype+-- The 'Metric' type contains an 'IORef' to a 'Map' from 'Text' labels to+-- the various counters, and a 'EKG.Store' to register them with. If you+-- must use the 'Metric' value directly, then you are recommended to use+-- the lenses provided for compatibility.+ ------------------------------------------------------------------------------- diffTime :: Resolution -> TimeSpec -> TimeSpec -> Double@@ -204,7 +297,7 @@ container <- liftIO $ readIORef ref case Map.lookup name container of Nothing -> do- c <- liftIO . creator name =<< liftM metricsStore getMetrics+ c <- liftIO . creator name =<< liftM _metricsStore getMetrics liftIO $ modifyIORef ref (Map.insert name c) return c Just c -> return c
src/Control/Monad/Metrics/Internal.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE RankNTypes #-}+ {-| Module : Control.Monad.Metrics.Internal Description : An easy interface to recording metrics.@@ -8,14 +10,17 @@ Stability : experimental Portability : POSIX -This is an internal module for+This is an internal module. Depend upon it at your own risk -- breaking+changes in here will /not/ be reflected in the major API version. -} module Control.Monad.Metrics.Internal where +import Control.Monad.Reader (asks) import Data.IORef import Data.Map (Map) import Data.Text (Text)+import Lens.Micro import System.Metrics (Store) import System.Metrics.Counter (Counter) import System.Metrics.Distribution (Distribution)@@ -23,16 +28,50 @@ import System.Metrics.Label (Label) -- | A container for metrics used by the 'MonadMetrics' class.+--+-- * /Since v0.1.0.0/ data Metrics = Metrics- { metricsCounters :: IORef (Map Text Counter)- , metricsGauges :: IORef (Map Text Gauge)- , metricsDistributions :: IORef (Map Text Distribution)- , metricsLabels :: IORef (Map Text Label)- , metricsStore :: Store+ { _metricsCounters :: IORef (Map Text Counter)+ , _metricsGauges :: IORef (Map Text Gauge)+ , _metricsDistributions :: IORef (Map Text Distribution)+ , _metricsLabels :: IORef (Map Text Label)+ , _metricsStore :: Store } +-- | A lens into the 'Counter's provided by the 'Metrics'.+--+-- * /Since v0.1.0.0/+metricsCounters :: Lens' Metrics (IORef (Map Text Counter))+metricsCounters f (Metrics c g d l s) = fmap (\c' -> Metrics c' g d l s) (f c)++-- | A lens into the 'Gauge's provided by the 'Metrics'.+--+-- * /Since v0.1.0.0/+metricsGauges :: Lens' Metrics (IORef (Map Text Gauge))+metricsGauges f (Metrics c g d l s) = fmap (\g' -> Metrics c g' d l s) (f g)++-- | A lens into the 'Distribution's provided by the 'Metrics'.+--+-- * /Since v0.1.0.0/+metricsDistributions :: Lens' Metrics (IORef (Map Text Distribution))+metricsDistributions f (Metrics c g d l s) = fmap (\d' -> Metrics c g d' l s) (f d)++-- | A lens into the 'Label's provided by the 'Metrics'.+--+-- * /Since v0.1.0.0/+metricsLabels :: Lens' Metrics (IORef (Map Text Label))+metricsLabels f (Metrics c g d l s) = fmap (\l' -> Metrics c g d l' s) (f l)++-- | A lens into the 'Store' provided by the 'Metrics'.+--+-- * /Since v0.1.0.0/+metricsStore :: Lens' Metrics Store+metricsStore f (Metrics c g d l s) = fmap (Metrics c g d l) (f s)+ -- | A type representing the resolution of time to use for the 'timed' -- metric.+--+-- * /Since v0.1.0.0/ data Resolution = Nanoseconds | Microseconds