From e86c5d9767b3fdf7a13632e2287beaad75c863ae Mon Sep 17 00:00:00 2001 From: Lucas DiCioccio Date: Sat, 3 Feb 2018 20:35:29 +0100 Subject: [PATCH 1/5] Add Dimensional wrapper for metrics. Breaking change: the Sample type now holds dimensions as key-value pairs. --- System/Metrics.hs | 90 ++++++++++++++++++++++++++++++----- System/Metrics/Dimensional.hs | 88 ++++++++++++++++++++++++++++++++++ ekg-core.cabal | 1 + 3 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 System/Metrics/Dimensional.hs diff --git a/System/Metrics.hs b/System/Metrics.hs index bca8339..8a1ad2d 100644 --- a/System/Metrics.hs +++ b/System/Metrics.hs @@ -49,6 +49,7 @@ module System.Metrics , registerLabel , registerDistribution , registerGroup + , registerDimensional -- ** Convenience functions -- $convenience @@ -56,6 +57,10 @@ module System.Metrics , createGauge , createLabel , createDistribution + , createDimensionalCounter + , createDimensionalGauge + , createDimensionalLabel + , createDimensionalDistribution -- ** Predefined metrics -- $predefined @@ -70,7 +75,9 @@ module System.Metrics import Control.Applicative ((<$>)) import Control.Monad (forM) +import Data.Either (partitionEithers) import Data.Int (Int64) +import Data.Monoid ((<>)) import qualified Data.IntMap.Strict as IM import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef) import qualified Data.HashMap.Strict as M @@ -86,6 +93,8 @@ import System.Metrics.Gauge (Gauge) import qualified System.Metrics.Gauge as Gauge import System.Metrics.Label (Label) import qualified System.Metrics.Label as Label +import System.Metrics.Dimensional (Dimensional) +import qualified System.Metrics.Dimensional as Dimensional -- $naming -- Compound metric names should be separated using underscores. @@ -137,6 +146,7 @@ data MetricSampler = CounterS !(IO Int64) | GaugeS !(IO Int64) | LabelS !(IO T.Text) | DistributionS !(IO Distribution.Stats) + | DimensionalS !Dimensional.Dimensions !(IO (M.HashMap Dimensional.Point Value)) -- | Create a new, empty metric store. newStore :: IO Store @@ -192,6 +202,16 @@ registerDistribution registerDistribution name sample store = register name (DistributionS sample) store +-- | Registers a dimensional metric. +registerDimensional + :: Dimensional a + -> (a -> IO Value) -- ^ Function to sample the dimensional metric to a Value. + -> Store -- ^ Metric store + -> IO () +registerDimensional d f store = + let x = readIORef (Dimensional._points d) >>= traverse f in + register (Dimensional._name d) (DimensionalS (Dimensional._dimensions d) x) store + register :: T.Text -> MetricSampler -> Store @@ -323,6 +343,48 @@ createDistribution name store = do registerDistribution name (Distribution.read event) store return event +-- | Create and register a zero-initialized dimensional counter. +createDimensionalCounter :: T.Text -- ^ Counter name + -> Store -- ^ Metric store + -> Dimensional.Dimensions -- ^ Dimension names. + -> IO (Dimensional Counter) +createDimensionalCounter name store dims = do + counter <- Dimensional.newDimensional Counter.new name "" dims + registerDimensional counter (fmap Counter . Counter.read) store + return counter + +-- | Create and register a zero-initialized dimensional gauge. +createDimensionalGauge :: T.Text -- ^ Gauge name + -> Store -- ^ Metric store + -> Dimensional.Dimensions -- ^ Dimension names. + -> IO (Dimensional Gauge) +createDimensionalGauge name store dims = do + gauge <- Dimensional.newDimensional Gauge.new name "" dims + registerDimensional gauge (fmap Gauge . Gauge.read) store + return gauge + +-- | Create and register a zero-initialized dimensional label. +createDimensionalLabel :: T.Text -- ^ Label name + -> Store -- ^ Metric store + -> Dimensional.Dimensions -- ^ Dimension names. + -> IO (Dimensional Label) +createDimensionalLabel name store dims = do + label <- Dimensional.newDimensional Label.new name "" dims + registerDimensional label (fmap Label . Label.read) store + return label + +-- | Create and register a zero-initialized dimensional distribution. +createDimensionalDistribution :: T.Text -- ^ Distribution name + -> Store -- ^ Metric store + -> Dimensional.Dimensions -- ^ Dimension names. + -> IO (Dimensional Distribution) +createDimensionalDistribution name store dims = do + distribution <- Dimensional.newDimensional Distribution.new name "" dims + registerDimensional distribution (fmap Distribution . Distribution.read) store + return distribution + + + ------------------------------------------------------------------------ -- * Predefined metrics @@ -590,7 +652,7 @@ gcParTotBytesCopied = Stats.parAvgBytesCopied -- metrics atomically. -- | A sample of some metrics. -type Sample = M.HashMap T.Text Value +type Sample = M.HashMap (T.Text, [(T.Text, T.Text)]) Value -- | Sample all metrics. Sampling is /not/ atomic in the sense that -- some metrics might have been mutated before they're sampled but @@ -601,9 +663,10 @@ sampleAll store = do let metrics = stateMetrics state groups = stateGroups state cbSample <- sampleGroups $ IM.elems groups - sample <- readAllRefs metrics - let allSamples = sample ++ cbSample - return $! M.fromList allSamples + (dimSamples, sample) <- partitionEithers <$> readAllRefs metrics + let noDimSamples = [((k,[]),v) | (k,v) <- sample ++ cbSample] + let flatDimSamples = [ ((k,zip ds dvs),v) | (k, ds, pointVals) <- dimSamples, (dvs,v) <- pointVals] + return $! M.fromList (noDimSamples ++ flatDimSamples) -- | Sample all metric groups. sampleGroups :: [GroupSampler] -> IO [(T.Text, Value)] @@ -621,17 +684,22 @@ data Value = Counter {-# UNPACK #-} !Int64 | Distribution !Distribution.Stats deriving (Eq, Show) -sampleOne :: MetricSampler -> IO Value -sampleOne (CounterS m) = Counter <$> m -sampleOne (GaugeS m) = Gauge <$> m -sampleOne (LabelS m) = Label <$> m -sampleOne (DistributionS m) = Distribution <$> m +type Value2 = Either (Dimensional.Dimensions, [(Dimensional.Point, Value)]) Value + +sampleOne :: MetricSampler -> IO Value2 +sampleOne (CounterS m) = Right . Counter <$> m +sampleOne (GaugeS m) = Right . Gauge <$> m +sampleOne (LabelS m) = Right . Label <$> m +sampleOne (DistributionS m) = Right . Distribution <$> m +sampleOne (DimensionalS dims m) = Left . (\pairs -> (dims, pairs)) . M.toList <$> m -- | Get a snapshot of all values. Note that we're not guaranteed to -- see a consistent snapshot of the whole map. readAllRefs :: M.HashMap T.Text (Either MetricSampler GroupId) - -> IO [(T.Text, Value)] + -> IO [Either (T.Text, Dimensional.Dimensions, [(Dimensional.Point, Value)]) (T.Text, Value)] readAllRefs m = do forM ([(name, ref) | (name, Left ref) <- M.toList m]) $ \ (name, ref) -> do val <- sampleOne ref - return (name, val) + return $ case val of + Left (dims, pairs) -> Left (name, dims, pairs) + Right v -> Right (name, v) diff --git a/System/Metrics/Dimensional.hs b/System/Metrics/Dimensional.hs new file mode 100644 index 0000000..78fb80d --- /dev/null +++ b/System/Metrics/Dimensional.hs @@ -0,0 +1,88 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +module System.Metrics.Dimensional where + +import qualified System.Metrics.Counter as Counter +import qualified System.Metrics.Distribution as Distribution +import qualified System.Metrics.Gauge as Gauge +import qualified System.Metrics.Label as Label + +import Prelude hiding (lookup) +import GHC.Exception (Exception) +import Control.Exception (throwIO) +import Data.Text (Text) +import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) +import Data.HashMap.Strict (HashMap, empty) +import qualified Data.HashMap.Strict as HashMap + +type Name = Text +type Explanation = Text +type Dimensions = [Text] +type Point = [Text] + +data Dimensional a = Dimensional { + _create :: !(IO a) + , _name :: !Name + , _explanation :: !Explanation + , _dimensions :: !Dimensions + , _points :: !(IORef (HashMap Point a)) + } + +newDimensional :: IO a -> Name -> Explanation -> Dimensions -> IO (Dimensional a) +newDimensional new name explanation dimensions = + Dimensional new name explanation dimensions <$> newIORef empty + +data DimensionError + = DimensionError !Name !Dimensions !Point + deriving (Show, Ord, Eq) +instance Exception DimensionError + +data LookupFailure + = UnmatchedDimensions DimensionError + | NotFound + deriving (Show, Ord, Eq) + +lookup :: Dimensional a -> Point -> IO (Either LookupFailure a) +lookup d pt + | not $ matchDimensions (_dimensions d) pt = pure $ Left (UnmatchedDimensions err) + | otherwise = do + toLookupResult . HashMap.lookup pt <$> readIORef (_points d) + where + err :: DimensionError + err = DimensionError (_name d) (_dimensions d) pt + + toLookupResult Nothing = Left NotFound + toLookupResult (Just x) = Right x + +matchDimensions :: Dimensions -> Point -> Bool +matchDimensions ds ps = length ds == length ps + +create :: Dimensional a -> Point -> IO a +create d pt + | not $ matchDimensions (_dimensions d) pt = throwIO err + | otherwise = do + v <- _create d + atomicModifyIORef' (_points d) (\store -> (HashMap.insert pt v store, ())) + return v + where + err :: DimensionError + err = DimensionError (_name d) (_dimensions d) pt + +lookupOrCreate :: Dimensional a -> Point -> IO a +lookupOrCreate d pt = lookup d pt >>= \case + Left NotFound -> create d pt + Left (UnmatchedDimensions err) -> throwIO err + Right x -> return x + +type Counter = Dimensional Counter.Counter +type Gauge = Dimensional Gauge.Gauge +type Label = Dimensional Label.Label +type Distribution = Dimensional Distribution.Distribution + +example :: IO () +example = do + c <- newDimensional Counter.new "foo" "a foo" ["url", "status"] + let url = "/hello" + let status = "200" + x <- lookupOrCreate c [url, status] + Counter.inc x diff --git a/ekg-core.cabal b/ekg-core.cabal index 518563d..90b5ec0 100644 --- a/ekg-core.cabal +++ b/ekg-core.cabal @@ -22,6 +22,7 @@ library exposed-modules: System.Metrics System.Metrics.Counter + System.Metrics.Dimensional System.Metrics.Distribution System.Metrics.Distribution.Internal System.Metrics.Gauge From 09e8a446c9f60712a9ecf4137f7d23ae5674b72f Mon Sep 17 00:00:00 2001 From: Lucas DiCioccio Date: Sat, 3 Feb 2018 20:59:58 +0100 Subject: [PATCH 2/5] Version bump. --- ekg-core.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ekg-core.cabal b/ekg-core.cabal index 90b5ec0..d2626f5 100644 --- a/ekg-core.cabal +++ b/ekg-core.cabal @@ -1,5 +1,5 @@ name: ekg-core -version: 0.1.1.4 +version: 0.2.0.0 synopsis: Tracking of system metrics description: This library lets you defined and track system metrics. From 1a77ab47af48d93769af96f494b92bf6a98bbc28 Mon Sep 17 00:00:00 2001 From: Matthieu Morel Date: Sun, 18 Nov 2018 16:37:11 +0100 Subject: [PATCH 3/5] add documentation and cosmetic changes --- System/Metrics.hs | 4 +- System/Metrics/Dimensional.hs | 80 +++++++++++++++++++++-------------- examples/Dimensional.hs | 15 +++++++ 3 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 examples/Dimensional.hs diff --git a/System/Metrics.hs b/System/Metrics.hs index 8a1ad2d..d745645 100644 --- a/System/Metrics.hs +++ b/System/Metrics.hs @@ -209,8 +209,8 @@ registerDimensional -> Store -- ^ Metric store -> IO () registerDimensional d f store = - let x = readIORef (Dimensional._points d) >>= traverse f in - register (Dimensional._name d) (DimensionalS (Dimensional._dimensions d) x) store + let x = readIORef (Dimensional.dimensionalPoints d) >>= traverse f in + register (Dimensional.dimensionalName d) (DimensionalS (Dimensional.dimensionalDimensions d) x) store register :: T.Text -> MetricSampler diff --git a/System/Metrics/Dimensional.hs b/System/Metrics/Dimensional.hs index 78fb80d..d6ee962 100644 --- a/System/Metrics/Dimensional.hs +++ b/System/Metrics/Dimensional.hs @@ -1,5 +1,10 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} + +-- | This module defines a type for mutable dimensional string-valued label. +-- Dimensional label are variable values and can be used to track e.g. the +-- current number of call to an api endpoint by its http return code. +-- All operations on Dimensional label are thread-safe. module System.Metrics.Dimensional where import qualified System.Metrics.Counter as Counter @@ -7,34 +12,44 @@ import qualified System.Metrics.Distribution as Distribution import qualified System.Metrics.Gauge as Gauge import qualified System.Metrics.Label as Label -import Prelude hiding (lookup) -import GHC.Exception (Exception) +import Control.Applicative ((<$>), pure) import Control.Exception (throwIO) -import Data.Text (Text) -import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) import Data.HashMap.Strict (HashMap, empty) import qualified Data.HashMap.Strict as HashMap +import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) +import Data.Text (Text) +import GHC.Exception (Exception) +import Prelude hiding (lookup) type Name = Text + type Explanation = Text + type Dimensions = [Text] + type Point = [Text] -data Dimensional a = Dimensional { - _create :: !(IO a) - , _name :: !Name - , _explanation :: !Explanation - , _dimensions :: !Dimensions - , _points :: !(IORef (HashMap Point a)) +-- | Dimensional metrics storage +data Dimensional a = Dimensional + { dimensionalCreate :: !(IO a) + , dimensionalName :: !Name + , dimensionalExplanation :: !Explanation + , dimensionalDimensions :: !Dimensions + , dimensionalPoints :: !(IORef (HashMap Point a)) } -newDimensional :: IO a -> Name -> Explanation -> Dimensions -> IO (Dimensional a) +-- | Create a new empty dimensional metrics +newDimensional :: + IO a -> Name -> Explanation -> Dimensions -> IO (Dimensional a) newDimensional new name explanation dimensions = - Dimensional new name explanation dimensions <$> newIORef empty + Dimensional new name explanation dimensions <$> newIORef empty -data DimensionError - = DimensionError !Name !Dimensions !Point +data DimensionError = + DimensionError !Name + !Dimensions + !Point deriving (Show, Ord, Eq) + instance Exception DimensionError data LookupFailure @@ -42,47 +57,48 @@ data LookupFailure | NotFound deriving (Show, Ord, Eq) +-- | Returns dimensional metric with specified labels lookup :: Dimensional a -> Point -> IO (Either LookupFailure a) lookup d pt - | not $ matchDimensions (_dimensions d) pt = pure $ Left (UnmatchedDimensions err) + | not $ matchDimensions (dimensionalDimensions d) pt = + pure $ Left (UnmatchedDimensions err) | otherwise = do - toLookupResult . HashMap.lookup pt <$> readIORef (_points d) + toLookupResult . HashMap.lookup pt <$> readIORef (dimensionalPoints d) where err :: DimensionError - err = DimensionError (_name d) (_dimensions d) pt - + err = DimensionError (dimensionalName d) (dimensionalDimensions d) pt toLookupResult Nothing = Left NotFound toLookupResult (Just x) = Right x matchDimensions :: Dimensions -> Point -> Bool matchDimensions ds ps = length ds == length ps +-- | Initialize a new empty dimensional metric with specified labels create :: Dimensional a -> Point -> IO a create d pt - | not $ matchDimensions (_dimensions d) pt = throwIO err + | not $ matchDimensions (dimensionalDimensions d) pt = throwIO err | otherwise = do - v <- _create d - atomicModifyIORef' (_points d) (\store -> (HashMap.insert pt v store, ())) - return v + v <- dimensionalCreate d + atomicModifyIORef' + (dimensionalPoints d) + (\store -> (HashMap.insert pt v store, ())) + return v where err :: DimensionError - err = DimensionError (_name d) (_dimensions d) pt + err = DimensionError (dimensionalName d) (dimensionalDimensions d) pt +-- | Returns dimensional metric with specified labels, creating it if not exists lookupOrCreate :: Dimensional a -> Point -> IO a -lookupOrCreate d pt = lookup d pt >>= \case +lookupOrCreate d pt = + lookup d pt >>= \case Left NotFound -> create d pt Left (UnmatchedDimensions err) -> throwIO err Right x -> return x type Counter = Dimensional Counter.Counter + type Gauge = Dimensional Gauge.Gauge + type Label = Dimensional Label.Label -type Distribution = Dimensional Distribution.Distribution -example :: IO () -example = do - c <- newDimensional Counter.new "foo" "a foo" ["url", "status"] - let url = "/hello" - let status = "200" - x <- lookupOrCreate c [url, status] - Counter.inc x +type Distribution = Dimensional Distribution.Distribution diff --git a/examples/Dimensional.hs b/examples/Dimensional.hs new file mode 100644 index 0000000..f6bf133 --- /dev/null +++ b/examples/Dimensional.hs @@ -0,0 +1,15 @@ +import qualified System.Metrics.Counter as Counter +import System.Metrics.Dimensional + +main :: IO () +main = do + c <- + newDimensional + Counter.new + "wai.response" + "endpoints status response" + ["url", "status"] + let url = "/hello" + let status = "200" + x <- lookupOrCreate c [url, status] + Counter.inc x From de569d4f9ecc2a02e14c0d98a58e472878402dbc Mon Sep 17 00:00:00 2001 From: Matthieu Morel Date: Mon, 19 Nov 2018 19:45:22 +0100 Subject: [PATCH 4/5] fix for ghc-7.6 and 7.8 --- System/Metrics.hs | 1 + System/Metrics/Dimensional.hs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/System/Metrics.hs b/System/Metrics.hs index d745645..eeaf083 100644 --- a/System/Metrics.hs +++ b/System/Metrics.hs @@ -77,6 +77,7 @@ import Control.Applicative ((<$>)) import Control.Monad (forM) import Data.Either (partitionEithers) import Data.Int (Int64) +import Data.Traversable (traverse) import Data.Monoid ((<>)) import qualified Data.IntMap.Strict as IM import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef) diff --git a/System/Metrics/Dimensional.hs b/System/Metrics/Dimensional.hs index d6ee962..15304ff 100644 --- a/System/Metrics/Dimensional.hs +++ b/System/Metrics/Dimensional.hs @@ -1,5 +1,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DeriveDataTypeable #-} -- | This module defines a type for mutable dimensional string-valued label. -- Dimensional label are variable values and can be used to track e.g. the @@ -18,6 +19,7 @@ import Data.HashMap.Strict (HashMap, empty) import qualified Data.HashMap.Strict as HashMap import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) import Data.Text (Text) +import Data.Typeable import GHC.Exception (Exception) import Prelude hiding (lookup) @@ -48,7 +50,7 @@ data DimensionError = DimensionError !Name !Dimensions !Point - deriving (Show, Ord, Eq) + deriving (Show, Ord, Eq, Typeable) instance Exception DimensionError From b9bb4db4803e8a03c089976309fa3cde8d530d02 Mon Sep 17 00:00:00 2001 From: Matthieu Morel Date: Sat, 24 Nov 2018 11:36:35 +0100 Subject: [PATCH 5/5] avoid lazyness in sampling --- System/Compat.hs | 23 +++++++++++++++++++++++ System/Metrics.hs | 10 ++++++++-- System/Metrics/Dimensional.hs | 8 +++++++- ekg-core.cabal | 1 + 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 System/Compat.hs diff --git a/System/Compat.hs b/System/Compat.hs new file mode 100644 index 0000000..71c36f2 --- /dev/null +++ b/System/Compat.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE CPP #-} + +module System.Compat +#if !MIN_VERSION_base(4,8,0) + ((<$!>)) +#else +#endif + where + +-- | Import <$!> from base-4.8 for ghc 7 compat +#if !MIN_VERSION_base(4,8,0) +infixl 4 <$!> + +-- | Strict version of 'Data.Functor.<$>'. +-- +-- @since 4.8.0.0 +(<$!>) :: Monad m => (a -> b) -> m a -> m b +{-# INLINE (<$!>) #-} +f <$!> m = do + x <- m + let z = f x + z `seq` return z +#endif diff --git a/System/Metrics.hs b/System/Metrics.hs index eeaf083..34e4e05 100644 --- a/System/Metrics.hs +++ b/System/Metrics.hs @@ -74,7 +74,12 @@ module System.Metrics ) where import Control.Applicative ((<$>)) +#if MIN_VERSION_base(4,8,0) +import Control.Monad (forM, (<$!>)) +#else import Control.Monad (forM) +import System.Compat ((<$!>)) +#endif import Data.Either (partitionEithers) import Data.Int (Int64) import Data.Traversable (traverse) @@ -655,6 +660,7 @@ gcParTotBytesCopied = Stats.parAvgBytesCopied -- | A sample of some metrics. type Sample = M.HashMap (T.Text, [(T.Text, T.Text)]) Value + -- | Sample all metrics. Sampling is /not/ atomic in the sense that -- some metrics might have been mutated before they're sampled but -- after some other metrics have already been sampled. @@ -692,7 +698,7 @@ sampleOne (CounterS m) = Right . Counter <$> m sampleOne (GaugeS m) = Right . Gauge <$> m sampleOne (LabelS m) = Right . Label <$> m sampleOne (DistributionS m) = Right . Distribution <$> m -sampleOne (DimensionalS dims m) = Left . (\pairs -> (dims, pairs)) . M.toList <$> m +sampleOne (DimensionalS dims m) = Left . (\pairs -> (dims, pairs)) . M.toList <$!> m -- | Get a snapshot of all values. Note that we're not guaranteed to -- see a consistent snapshot of the whole map. @@ -701,6 +707,6 @@ readAllRefs :: M.HashMap T.Text (Either MetricSampler GroupId) readAllRefs m = do forM ([(name, ref) | (name, Left ref) <- M.toList m]) $ \ (name, ref) -> do val <- sampleOne ref - return $ case val of + return $! case val of Left (dims, pairs) -> Left (name, dims, pairs) Right v -> Right (name, v) diff --git a/System/Metrics/Dimensional.hs b/System/Metrics/Dimensional.hs index 15304ff..9ff2f72 100644 --- a/System/Metrics/Dimensional.hs +++ b/System/Metrics/Dimensional.hs @@ -1,6 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE CPP #-} -- | This module defines a type for mutable dimensional string-valued label. -- Dimensional label are variable values and can be used to track e.g. the @@ -15,6 +16,11 @@ import qualified System.Metrics.Label as Label import Control.Applicative ((<$>), pure) import Control.Exception (throwIO) +#if MIN_VERSION_base(4,8,0) +import Control.Monad ((<$!>)) +#else +import System.Compat ((<$!>)) +#endif import Data.HashMap.Strict (HashMap, empty) import qualified Data.HashMap.Strict as HashMap import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) @@ -65,7 +71,7 @@ lookup d pt | not $ matchDimensions (dimensionalDimensions d) pt = pure $ Left (UnmatchedDimensions err) | otherwise = do - toLookupResult . HashMap.lookup pt <$> readIORef (dimensionalPoints d) + toLookupResult . HashMap.lookup pt <$!> readIORef (dimensionalPoints d) where err :: DimensionError err = DimensionError (dimensionalName d) (dimensionalDimensions d) pt diff --git a/ekg-core.cabal b/ekg-core.cabal index 0a5d4cf..aade445 100644 --- a/ekg-core.cabal +++ b/ekg-core.cabal @@ -21,6 +21,7 @@ tested-with: GHC == 8.6.2, GHC == 8.4.4, GHC == 8.2.2, library exposed-modules: System.Metrics + System.Compat System.Metrics.Counter System.Metrics.Dimensional System.Metrics.Distribution