packages feed

monad-metrics-extensible 0.1.0.1 → 0.1.1.0

raw patch · 6 files changed

+151/−21 lines, 6 filesdep +timePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: time

API changes (from Hackage documentation)

- System.Metrics.Store: instance Data.GADT.Compare.GCompare System.Metrics.Store.SomeMetric
- System.Metrics.Store: instance Data.GADT.Compare.GEq System.Metrics.Store.SomeMetric
+ System.Metrics.ExtraTrackers: Msecs :: TimerMagnitude
+ System.Metrics.ExtraTrackers: Nsecs :: TimerMagnitude
+ System.Metrics.ExtraTrackers: Timer :: Distribution -> Timer (magn :: TimerMagnitude)
+ System.Metrics.ExtraTrackers: Usecs :: TimerMagnitude
+ System.Metrics.ExtraTrackers: [getTimerDistribution] :: Timer (magn :: TimerMagnitude) -> Distribution
+ System.Metrics.ExtraTrackers: data TimerMagnitude
+ System.Metrics.ExtraTrackers: data Timestamp
+ System.Metrics.ExtraTrackers: instance (Data.Typeable.Internal.Typeable magn, System.Metrics.ExtraTrackers.MagnitudeOps magn) => System.Metrics.Monad.Class.TrackerLike (System.Metrics.ExtraTrackers.Timer magn)
+ System.Metrics.ExtraTrackers: instance System.Metrics.ExtraTrackers.MagnitudeOps 'System.Metrics.ExtraTrackers.Msecs
+ System.Metrics.ExtraTrackers: instance System.Metrics.ExtraTrackers.MagnitudeOps 'System.Metrics.ExtraTrackers.Nsecs
+ System.Metrics.ExtraTrackers: instance System.Metrics.ExtraTrackers.MagnitudeOps 'System.Metrics.ExtraTrackers.Usecs
+ System.Metrics.ExtraTrackers: instance System.Metrics.Monad.Class.TrackerLike System.Metrics.ExtraTrackers.Timestamp
+ System.Metrics.ExtraTrackers: newtype Timer (magn :: TimerMagnitude)
+ System.Metrics.ExtraTrackers: timed :: forall m metric magn name a. (MonadMetrics m, KnownSymbol name, Typeable metric, Typeable magn, MagnitudeOps magn, Ord (metric (Timer magn) name)) => metric (Timer magn) name -> m a -> m a
+ System.Metrics.Store: instance Data.GADT.Internal.GCompare System.Metrics.Store.SomeMetric
+ System.Metrics.Store: instance Data.GADT.Internal.GEq System.Metrics.Store.SomeMetric
- System.Metrics.Extensible: newMetricsStore :: Server -> IO (MetricsStore, IO ())
+ System.Metrics.Extensible: newMetricsStore :: MonadIO m => Server -> m (MetricsStore, m ())
- System.Metrics.Extensible: withMetricsStore :: Server -> (MetricsStore -> IO a) -> IO a
+ System.Metrics.Extensible: withMetricsStore :: (MonadIO m, MonadMask m) => Server -> (MetricsStore -> m a) -> m a
- System.Metrics.Store: newMetricsStore :: Server -> IO (MetricsStore, IO ())
+ System.Metrics.Store: newMetricsStore :: MonadIO m => Server -> m (MetricsStore, m ())
- System.Metrics.Store: withMetricsStore :: Server -> (MetricsStore -> IO a) -> IO a
+ System.Metrics.Store: withMetricsStore :: (MonadIO m, MonadMask m) => Server -> (MetricsStore -> m a) -> m a

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for monad-metrics-extensible +## v. 0.1.1.0++Add some utility types and functions: `Timestamp`, `Timer`, `timed`.+ ## v. 0.1.0.1  A very brief documentation in README
README.md view
@@ -1,5 +1,10 @@ # monad-metrics-extensible +[![Build Status][travis-badge]][travis]+[![Hackage][hackage-badge]][hackage]+[![Stackage LTS][stackage-lts-badge]][stackage-lts]+[![Stackage Nightly][stackage-nightly-badge]][stackage-nightly]+ ## tl;dr  This library simplifies using [ekg](http://hackage.haskell.org/package/ekg)@@ -16,7 +21,7 @@   (hence "extensible" one more time).   You want a combined distribution + counter? No prob! -`import System.Metrics.Extensible` is your entry point of choice!+`System.Metrics.Extensible` is your entry point of choice!  ## A quick example @@ -36,19 +41,19 @@   AnotherCounter  :: SomeMetrics Counter "other_counter"   SomeGauge       :: SomeMetrics Gauge   "some_gauge" ```-The string literals is what will be shown via ekg UI.+The string literals are what will be shown via ekg UI.  There is a couple of requirements:  * The type shall be of the kind `* -> Symbol -> *`. * The first type argument (`Counter` and `Gauge` in the example above)   shall be an instance of `TrackerLike`. All ekg counters are already-  an instance of this class.-* The type shall be comparable, hence we shall also do-```haskell-deriving instance Eq (SomeMetrics ty name)-deriving instance Ord (SomeMetrics ty name)-```+  instances of this class.+* The type shall be comparable, hence we also do+  ```haskell+  deriving instance Eq (SomeMetrics ty name)+  deriving instance Ord (SomeMetrics ty name)+  ```  Then we can write our small program! @@ -67,5 +72,16 @@   the metrics. * `track` is the function that's responsible for updating the metrics.   Its arguments depend on the specific metric that's being tracked:-  for instance, `Counter`s have no arguments, while `Gauge`s accept+  for instance, as can be seen in the example above,+  `Counter`s have no arguments, while `Gauge`s accept   the corresponding new value.+++[travis]:        <https://travis-ci.org/0xd34df00d/monad-metrics-extensible>+[travis-badge]:  <https://travis-ci.org/0xd34df00d/monad-metrics-extensible.svg?branch=master>+[hackage]:       <https://hackage.haskell.org/package/monad-metrics-extensible>+[hackage-badge]: <https://img.shields.io/hackage/v/monad-metrics-extensible.svg>+[stackage-lts-badge]: <http://stackage.org/package/monad-metrics-extensible/badge/lts>+[stackage-nightly-badge]: <http://stackage.org/package/monad-metrics-extensible/badge/nightly>+[stackage-lts]: <http://stackage.org/lts/package/monad-metrics-extensible>+[stackage-nightly]: <http://stackage.org/nightly/package/monad-metrics-extensible>
monad-metrics-extensible.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.0.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 79c192bfb089d6da408408ac335a54cc76caffb9f9520888b58754d4981712fe+-- hash: 69ea4201ba5699f3590e46f8f9ef0a986f900c2f2b489840763c7e2b26c42394  name:           monad-metrics-extensible-version:        0.1.0.1+version:        0.1.1.0 synopsis:       An extensible and type-safe wrapper around EKG metrics description:    Please see the README on GitHub at <https://github.com/0xd34df00d/monad-metrics-extensible#readme> category:       Web@@ -31,6 +31,7 @@   exposed-modules:       Data.Dyn       System.Metrics.Extensible+      System.Metrics.ExtraTrackers       System.Metrics.Monad       System.Metrics.Monad.Class       System.Metrics.Store@@ -50,6 +51,7 @@     , mtl     , stm     , text+    , time   default-language: Haskell2010  test-suite monad-metrics-extensible-test@@ -72,4 +74,5 @@     , mtl     , stm     , text+    , time   default-language: Haskell2010
+ src/System/Metrics/ExtraTrackers.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE TypeFamilies, DataKinds, ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-}++module System.Metrics.ExtraTrackers+( Timestamp++, TimerMagnitude(..)+, Timer(..)+, timed+) where++import qualified Data.Text as T+import Control.Monad.IO.Class+import Data.Proxy+import Data.Time.Clock.POSIX+import Data.Typeable+import GHC.TypeLits+import System.CPUTime+import System.Metrics+import System.Metrics.Distribution as TD+import System.Metrics.Label as TL++import System.Metrics.Extensible++newtype Timestamp = Timestamp Label++instance TrackerLike Timestamp where+  type TrackAction Timestamp m = m ()+  track metric = do+    Timestamp l <- getTracker metric+    liftIO $ do+      utc <- getCurrentTime+      TL.set l $ T.pack $ show utc+  createTracker name store = Timestamp <$> createLabel name store+++newtype Timer (magn :: TimerMagnitude) = Timer { getTimerDistribution :: Distribution }++data TimerMagnitude = Msecs | Usecs | Nsecs deriving (Typeable)++class MagnitudeOps (magn :: TimerMagnitude) where+  toString :: Proxy magn -> T.Text+  secFraction :: Proxy magn -> Double++instance MagnitudeOps 'Msecs where+  toString _ = "ms"+  secFraction _ = 1e-3++instance MagnitudeOps 'Usecs where+  toString _ = "us"+  secFraction _ = 1e-6++instance MagnitudeOps 'Nsecs where+  toString _ = "ns"+  secFraction _ = 1e-9++instance (Typeable magn, MagnitudeOps magn) => TrackerLike (Timer magn) where+  type TrackAction (Timer magn) m = Double -> m ()+  track metric timing = do+    Timer timer <- getTracker metric+    liftIO $ TD.add timer timing+  createTracker name store = Timer <$> createDistribution (name <> "_" <> toString (Proxy :: Proxy magn)) store++-- Returns the number of picoseconds+time :: MonadIO m => m a -> m (Integer, a)+time act = do+  start <- liftIO getCPUTime+  res <- act+  end <- liftIO getCPUTime+  pure (end - start, res)++timed :: forall m metric magn name a.+         (MonadMetrics m, KnownSymbol name, Typeable metric, Typeable magn, MagnitudeOps magn, Ord (metric (Timer magn) name))+      => metric (Timer magn) name+      -> m a+      -> m a+timed metric act = do+  (cpuTime, res) <- time act+  track metric $ fromIntegral cpuTime / (1e12 * secFraction (Proxy :: Proxy magn))+  pure res
src/System/Metrics/Store.hs view
@@ -14,6 +14,7 @@ import Control.Concurrent import Control.Concurrent.STM.TQueue import Control.Monad.Catch+import Control.Monad.IO.Class import Control.Monad.Identity import Control.Monad.STM import Data.GADT.Compare@@ -62,11 +63,11 @@  newtype MetricsStore = MetricsStore { mReqQueue :: TQueue MetricRequest } -newMetricsStore :: Server -> IO (MetricsStore, IO ())-newMetricsStore srv = do+newMetricsStore :: MonadIO m => Server -> m (MetricsStore, m ())+newMetricsStore srv = liftIO $ do   queue <- newTQueueIO   threadId <- forkIO $ act queue $ MetricsState srv mempty-  pure (MetricsStore queue, killThread threadId)+  pure (MetricsStore queue, liftIO $ killThread threadId)   where     act queue state = do       req <- atomically (readTQueue queue)@@ -89,7 +90,7 @@       putMVar mvar tracker       pure state' -withMetricsStore :: Server -> (MetricsStore -> IO a) -> IO a+withMetricsStore :: (MonadIO m, MonadMask m) => Server -> (MetricsStore -> m a) -> m a withMetricsStore srv f = bracket   (newMetricsStore srv)   snd
test/Spec.hs view
@@ -2,28 +2,41 @@ {-# LANGUAGE OverloadedStrings, StandaloneDeriving #-}  import Control.Monad.IO.Class+import Data.Functor+import System.CPUTime import System.Metrics.Counter as TC(read)+import System.Metrics.Distribution as TD(read, sum, count) import System.Metrics.Gauge as TG(read) import System.Remote.Monitoring import Test.Hspec  import System.Metrics.Extensible+import System.Metrics.ExtraTrackers  data TestMetrics ty name where-  Foo1 :: TestMetrics Counter "foo1"-  Foo2 :: TestMetrics Counter "foo2"-  Bar  :: TestMetrics Gauge   "bar"+  Foo1    :: TestMetrics Counter        "foo1"+  Foo2    :: TestMetrics Counter        "foo2"+  Bar     :: TestMetrics Gauge          "bar"+  TimeMS  :: TestMetrics (Timer 'Msecs) "timeMS"  deriving instance Eq (TestMetrics ty name) deriving instance Ord (TestMetrics ty name)  data OtherMetrics ty name where-  Baz  :: OtherMetrics Counter "foo1"-  Quux :: OtherMetrics Gauge   "bar"+  Baz  :: OtherMetrics Counter "other-foo1"+  Quux :: OtherMetrics Gauge   "other-bar"  deriving instance Eq (OtherMetrics ty name) deriving instance Ord (OtherMetrics ty name) +cpuThreadDelay :: Int -> IO ()+cpuThreadDelay usecs | usecs < 0 = pure ()+                     | otherwise = do+  start <- getCPUTime+  void $ readFile "/dev/null"+  end <- getCPUTime+  cpuThreadDelay $ usecs - fromIntegral ((end - start) `div` 1000000)+ main :: IO () main = do   ekgServer <- forkServer "localhost" 21000@@ -34,6 +47,7 @@     track Bar 10     track Bar 20     track Baz+    timed TimeMS $ liftIO $ cpuThreadDelay testMicroseconds     track Quux 42     liftIO $ hspec $ do       describe "Counters are stored as expected" $ do@@ -43,3 +57,15 @@       describe "Gauges are stored as expected" $ do         it "The final value of Bar is kept" $ (getMetricFromStore store Bar >>= TG.read) `shouldReturn` 20         it "Metrics from other type are also saved" $ (getMetricFromStore store Quux >>= TG.read) `shouldReturn` 42+      describe "Timers are stored and computed as expected" $ do+        it "TimeMS has only seen one value" $ do+          storedStats <- getMetricFromStore store TimeMS >>= TD.read . getTimerDistribution+          TD.count storedStats `shouldBe` 1+        it "The value of TimeMS exceeds the wait time" $ do+          storedStats <- getMetricFromStore store TimeMS >>= TD.read . getTimerDistribution+          TD.sum storedStats `shouldSatisfy` (>= (fromIntegral testMicroseconds / 1000))+        it "…but not too much" $ do+          storedStats <- getMetricFromStore store TimeMS >>= TD.read . getTimerDistribution+          TD.sum storedStats `shouldSatisfy` (< 1000 * (fromIntegral testMicroseconds / 1000))+  where+    testMicroseconds = 10000