diff --git a/README.md b/README.md new file mode 100644 index 0000000..547924c --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# purescript-metrics + +A metrics library for PureScript inspired by the Haskell library [ekg](https://github.com/tibbe/ekg-core). It is a wrapper upon the JavaScript [metrics library](https://github.com/mikejihbe/metrics) which itself is a port of the Java Dropwizard [metrics library](http://metrics.dropwizard.io/). + +API documentation can be found in [Pursuit](https://pursuit.purescript.org/packages/purescript-metrics). + +## Sample Usage + +```haskell +main = do + store <- newStore + counter <- createOrGetCounter "counter" store + gauge <- createOrGetGauge "gauge" (pure 3) store + hist <- createOrGetHistogramWithExponentialDecaySampling "hist" 1028 0.015 store + meter <- createOrGetMeter "meter" store + timer <- createOrGetTimer "timer" store + Counter.inc counter 2 + Histogram.update hist 1.2 + Histogram.update hist 2.1 + Meter.mark meter + Timer.update timer (Milliseconds 1000.0) + launchAff $ sample store >>= logShow +``` diff --git a/src/System/Metrics.purs b/src/System/Metrics.purs index acd5125..0b48396 100644 --- a/src/System/Metrics.purs +++ b/src/System/Metrics.purs @@ -12,7 +12,7 @@ , registerMeter , registerTimer , createOrGetCounter - , createOrGetGuage + , createOrGetGauge , createOrGetHistogramWithExponentialDecaySampling , createOrGetHistogramWithUniformSampling , createOrGetMeter @@ -52,18 +52,22 @@ newtype Store = Store (Ref State) -- | The 'Store' state. type State = Map.Map String MetricSampler +-- | Different types of metric samplers. data MetricSampler = CounterS Counter | GaugeS Gauge | HistogramS Histogram | MeterS Meter | TimerS Timer --- | Create a new, empty metric store. +-- | Creates a new, empty metric store. newStore ::forall eff. Eff (ref :: REF | eff) Store newStore = do ref <- newRef Map.empty pure $ Store ref +-- | Registers a metric sampler with the given name, to the given metric store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. register :: forall eff. String -> MetricSampler -> Store -> Eff (ref :: REF | eff) Boolean register name sampler (Store store) = modifyRef' store $ \state -> @@ -71,6 +75,8 @@ register name sampler (Store store) = modifyRef' store $ \state -> Just s -> { state : state, value : false } Nothing -> { state : Map.insert name sampler state, value : true } +-- | Registers and returns a metric sampler with the given name, to the given metric store if another one +-- | is not registered with the given name. Else, returns the metric sampler registered with the given name. registerOrGet :: forall eff. String -> MetricSampler -> Store -> Eff (ref :: REF | eff) MetricSampler registerOrGet name sampler (Store store) = modifyRef' store $ \state -> @@ -78,17 +84,20 @@ registerOrGet name sampler (Store store) = modifyRef' store $ \state -> Just s -> { state : state, value : s } Nothing -> { state : Map.insert name sampler state, value : sampler } +-- | Returns a metric sampler registered to the given metric store with the given name. get :: forall eff. String -> Store -> Eff (ref :: REF | eff) (Maybe MetricSampler) get name (Store store) = readRef store >>= pure <<< Map.lookup name --- | Register a non-negative, monotonically increasing, integer-valued metric. --- | --- | Also see 'createCounter'. +-- | Register a Counter with the given name to the given store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. registerCounter :: forall eff. String -> Counter -> Store -> Eff (ref :: REF | eff) Boolean registerCounter name counter = register name (CounterS counter) --- | Create and register a zero-initialized counter. Throw exception is the counter is already created. +-- | Creates, registers and returns a Counter with the given name to the given store. +-- | If a Counter is already registered with the given name, returns it. +-- | Throws exception if a non-Counter metric sampler is already registered with the given name. createOrGetCounter :: forall eff. String -> Store @@ -99,29 +108,37 @@ createOrGetCounter name store = do CounterS c -> pure c _ -> throw $ "Metric name is already registered: " <> name --- | Register an integer-valued metric. --- | --- | Also see 'createGuage'. +-- | Register a Gauge with the given name to the given store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. registerGauge :: forall eff. String -> Gauge -> Store -> Eff (ref :: REF | eff) Boolean registerGauge name gauge = register name (GaugeS gauge) --- | Create and register a guage. Throw exception is the guage is already created. -createOrGetGuage :: forall eff. +-- | Creates, registers and returns a Gauge with the given name to the given store. +-- | If a Gauge is already registered with the given name, returns it. +-- | Throws exception if a non-Gauge metric sampler is already registered with the given name. +createOrGetGauge :: forall eff. String -> (forall e. Aff e Int) -> Store -> Eff (ref :: REF, exception :: EXCEPTION | eff) Gauge -createOrGetGuage name f store = do +createOrGetGauge name f store = do let gauge = Gauge.new f registerOrGet name (GaugeS gauge) store >>= case _ of GaugeS g -> pure g _ -> throw $ "Metric name is already registered: " <> name +-- | Register a Histogram with the given name to the given store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. registerHistogram :: forall eff. String -> Histogram -> Store -> Eff (ref :: REF | eff) Boolean registerHistogram name hist = register name (HistogramS hist) +-- | Creates, registers and returns a Histogram with exponential decay sampling, with the given name to the given store. +-- | If a Histogram is already registered with the given name, returns it. +-- | Throws exception if a non-Histogram metric sampler is already registered with the given name. createOrGetHistogramWithExponentialDecaySampling :: forall eff. String @@ -135,6 +152,9 @@ createOrGetHistogramWithExponentialDecaySampling name size alpha store = do HistogramS c -> pure c _ -> throw $ "Metric name is already registered: " <> name +-- | Creates, registers and returns a Histogram with uniform sampling, with the given name to the given store. +-- | If a Histogram is already registered with the given name, returns it. +-- | Throws exception if a non-Histogram metric sampler is already registered with the given name. createOrGetHistogramWithUniformSampling :: forall eff. String @@ -147,9 +167,15 @@ createOrGetHistogramWithUniformSampling name size store = do HistogramS c -> pure c _ -> throw $ "Metric name is already registered: " <> name +-- | Register a Meter with the given name to the given store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. registerMeter :: forall eff. String -> Meter -> Store -> Eff (ref :: REF | eff) Boolean registerMeter name meter = register name (MeterS meter) +-- | Creates, registers and returns a Meter with the given name to the given store. +-- | If a Meter is already registered with the given name, returns it. +-- | Throws exception if a non-Meter metric sampler is already registered with the given name. createOrGetMeter :: forall eff. String @@ -161,9 +187,15 @@ createOrGetMeter name store = do MeterS c -> pure c _ -> throw $ "Metric name is already registered: " <> name +-- | Register a Timer with the given name to the given store. +-- | Returns true if registration succeeds, false if registration fails because another metric sampler +-- | is already registered with the given name. registerTimer :: forall eff. String -> Timer -> Store -> Eff (ref :: REF | eff) Boolean registerTimer name timer = register name (TimerS timer) +-- | Creates, registers and returns a Timer with the given name to the given store. +-- | If a Timer is already registered with the given name, returns it. +-- | Throws exception if a non-Timer metric sampler is already registered with the given name. createOrGetTimer :: forall eff. String @@ -175,8 +207,11 @@ createOrGetTimer name store = do TimerS c -> pure c _ -> throw $ "Metric name is already registered: " <> name +-- | A sample of the metics in a metric store represented as a map with the metric names as the keys. type Sample = Map.Map String Value +-- | Value of different metric samplers. Counter and Gauge values are their Int values. +-- | Histogram, Meter, and Timer values are the summary records of their values. data Value = CounterV Int | GaugeV Int | HistogramV Histogram.Summary @@ -190,6 +225,7 @@ instance showVal :: Show Value where instance encodeVal :: Encode Value where encode = genericEncode $ defaultOptions { unwrapSingleConstructors = true } +-- | Samples the value of a metric sampler. sampleOne :: forall eff. MetricSampler -> Aff (ref :: REF | eff) Value sampleOne (CounterS c) = CounterV <$> liftEff (Counter.read c) sampleOne (GaugeS g) = GaugeV <$> Gauge.read g @@ -197,6 +233,7 @@ sampleOne (HistogramS h) = HistogramV <$> liftEff (Histogram.read h) sampleOne (MeterS h) = MeterV <$> liftEff (Meter.read h) sampleOne (TimerS h) = TimerV <$> liftEff (Timer.read h) +-- | Samples the value of all metric samplers in the given store. sample :: forall eff. Store -> Aff (ref :: REF | eff) Sample sample (Store store) = do state <- liftEff $ readRef store @@ -205,7 +242,7 @@ sample (Store store) = do -- main = do -- store <- newStore -- counter <- createOrGetCounter "testc" store --- gauge <- createOrGetGuage "testg" (pure 3) store +-- gauge <- createOrGetGauge "testg" (pure 3) store -- hist <- createOrGetHistogramWithExponentialDecaySampling "hizz" 1028 0.015 store -- meter <- createOrGetMeter "mmm" store -- timer <- createOrGetTimer "ttt" store diff --git a/src/System/Metrics/Counter.purs b/src/System/Metrics/Counter.purs index c37af28..70990a1 100644 --- a/src/System/Metrics/Counter.purs +++ b/src/System/Metrics/Counter.purs @@ -1,18 +1,25 @@ -module System.Metrics.Counter (Counter, new, read, inc) where +module System.Metrics.Counter (Counter, new, read, reset, inc) where import Control.Monad.Eff (Eff) import Control.Monad.Eff.Ref (REF) import Data.Function.Uncurried (Fn2, runFn2) import Prelude (Unit) +-- | A mutable, integer valued counter. foreign import data Counter :: Type foreign import _new :: forall eff. Eff (ref :: REF | eff) Counter + +-- | Returns the current value of the counter. foreign import read :: forall eff. Counter -> Eff (ref :: REF | eff) Int + +-- | Resets the counter value to zero. foreign import reset :: forall eff. Counter -> Eff (ref :: REF | eff) Unit foreign import _inc :: forall eff. Fn2 Counter Int (Eff (ref :: REF | eff) Unit) +-- | Creates a new counter with zero value. new :: forall eff. Eff (ref :: REF | eff) Counter new = _new +-- | Increments the counter value by the given count. inc :: forall eff. Counter -> Int -> Eff (ref :: REF | eff) Unit inc c = runFn2 _inc c diff --git a/src/System/Metrics/Gauge.purs b/src/System/Metrics/Gauge.purs index 2ed4f1d..4851529 100644 --- a/src/System/Metrics/Gauge.purs +++ b/src/System/Metrics/Gauge.purs @@ -2,10 +2,13 @@ module System.Metrics.Gauge (Gauge, new, read) where import Control.Monad.Aff (Aff) +-- | An integer valued metric. newtype Gauge = Gauge (forall eff. Aff eff Int) +-- | Creates a new gauge given the underlying measurement function. new :: (forall eff. Aff eff Int) -> Gauge new = Gauge +-- | Returns the current value of the gauge. read :: forall eff. Gauge -> Aff eff Int read (Gauge f) = f diff --git a/src/System/Metrics/Histogram.purs b/src/System/Metrics/Histogram.purs index 9d18310..c2f42ed 100644 --- a/src/System/Metrics/Histogram.purs +++ b/src/System/Metrics/Histogram.purs @@ -1,6 +1,6 @@ module System.Metrics.Histogram ( Histogram - , Summary + , Summary(..) , newWithExponentialDecaySampling , newWithUniformSampling , clear @@ -30,11 +30,16 @@ import Data.Generic.Rep.Show (genericShow) import Data.Maybe (Maybe) import Data.Nullable (Nullable, toMaybe) +-- | A mutable distribution of values in a stream of data. foreign import data Histogram :: Type foreign import _newWithExponentialDecaySampling :: forall eff. Fn2 Int Number (Eff (ref :: REF | eff) Histogram) + +-- | Creates a new histogram with uniform sampling with the given size. foreign import newWithUniformSampling :: forall eff. Int -> Eff (ref :: REF | eff) Histogram + +-- | Clears the histogram. foreign import clear :: forall eff. Histogram -> Eff (ref :: REF | eff) Unit foreign import _update :: forall eff. Fn2 Histogram Number (Eff (ref :: REF | eff) Unit) foreign import _percentiles @@ -45,35 +50,68 @@ foreign import _stdDev :: forall eff. Histogram -> Eff (ref :: REF | eff) (Nulla foreign import _min :: forall eff. Histogram -> Eff (ref :: REF | eff) (Nullable Number) foreign import _max :: forall eff. Histogram -> Eff (ref :: REF | eff) (Nullable Number) foreign import _sum :: forall eff. Histogram -> Eff (ref :: REF | eff) (Nullable Number) + +-- | Returns the count of measurements in the histogram. foreign import count :: forall eff. Histogram -> Eff (ref :: REF | eff) Int +-- | Creates a new histogram with exponential decay sampling with given size and alpha. newWithExponentialDecaySampling :: forall eff. Int -> Number -> Eff (ref :: REF | eff) Histogram newWithExponentialDecaySampling = runFn2 _newWithExponentialDecaySampling +-- | Updates the histogram with the given measurement. update :: forall eff. Histogram -> Number -> Eff (ref :: REF | eff) Unit update = runFn2 _update +-- | Returns the percentiles of the measurements in the histogram for the given percentiles array. +-- | Returns 'Nothing' if there are no measurements. percentiles :: forall eff. Histogram -> Array Number -> Eff (ref :: REF | eff) (Maybe (Array Number)) percentiles h ptiles = toMaybe <$> runFn2 _percentiles h ptiles +-- | Returns the variance of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. variance :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) variance h = toMaybe <$> _variance h +-- | Returns the mean of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. mean :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) mean h = toMaybe <$> _mean h +-- | Returns the standard deviation of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. stdDev :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) stdDev h = toMaybe <$> _stdDev h +-- | Returns the minimum of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. min :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) min h = toMaybe <$> _min h +-- | Returns the maximum of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. max :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) max h = toMaybe <$> _max h +-- | Returns the sum of the measurements in the histogram. +-- | Returns 'Nothing' if there are no measurements. sum :: forall eff. Histogram -> Eff (ref :: REF | eff) (Maybe Number) sum h = toMaybe <$> _sum h +-- | Summary of the distribution of the measurements in the histogram. +-- | Contains: +-- | +-- | - min: minimum +-- | - max: maximum +-- | - sum: sum +-- | - variance: variance +-- | - mean: mean +-- | - stdDev: standard deviation +-- | - count: count +-- | - median: median +-- | - p75: 75th percentile +-- | - p95: 95th percentile +-- | - p99: 99th percentile +-- | - p999: 99.9th percentile newtype Summary = Summary { min :: Maybe Number , max :: Maybe Number @@ -130,6 +168,7 @@ derive instance genericHSummary' :: Generic Summary' _ instance encodeHSummary' :: Encode Summary' where encode = genericEncode $ defaultOptions { unwrapSingleConstructors = true } +-- | Returns the summary of the measurements in the histogram. read :: forall eff. Histogram -> Eff (ref :: REF | eff) Summary read h = do ptiles <- percentiles h [0.5, 0.75, 0.95, 0.99, 0.999] diff --git a/src/System/Metrics/Meter.purs b/src/System/Metrics/Meter.purs index 22c051d..e9999f3 100644 --- a/src/System/Metrics/Meter.purs +++ b/src/System/Metrics/Meter.purs @@ -1,6 +1,6 @@ module System.Metrics.Meter ( Meter - , Summary + , Summary(..) , new , markN , mark @@ -21,24 +21,45 @@ import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) import Prelude +-- | A mutable metric which measures the rate at which a set of events occur. foreign import data Meter :: Type foreign import _new :: forall eff. Eff (ref :: REF | eff) Meter foreign import _markN :: forall eff. Fn2 Meter Int (Eff (ref :: REF | eff) Unit) + +-- | Returns the fifteen minute moving-average rate of the events recorded by the meter. foreign import fifteenMinuteRate :: forall eff. Meter -> Eff (ref :: REF | eff) Number + +-- | Returns the five minute moving-average rate of the events recorded by the meter. foreign import fiveMinuteRate :: forall eff. Meter -> Eff (ref :: REF | eff) Number + +-- | Returns the one minute moving-average rate of the events recorded by the meter. foreign import oneMinuteRate :: forall eff. Meter -> Eff (ref :: REF | eff) Number + +-- | Returns the mean rate of the events recorded by the meter. foreign import meanRate :: forall eff. Meter -> Eff (ref :: REF | eff) Number + +-- | Returns the count of the events recorded by the meter. foreign import count :: forall eff. Meter -> Eff (ref :: REF | eff) Int +-- | Create a new meter. new :: forall eff. Eff (ref :: REF | eff) Meter new = _new +-- | Marks the meter with occurrance of n events. markN :: forall eff. Meter -> Int -> Eff (ref :: REF | eff) Unit markN = runFn2 _markN +-- | Marks the meter with occurrance of one event. mark :: forall eff. Meter -> Eff (ref :: REF | eff) Unit mark m = markN m 1 +-- | Summary of the measurements in the meter. Contains: +-- | +-- | - count: count +-- | - m1: one minute rate +-- | - m5: five minute rate +-- | - m15: fifteen minute rate +-- | - mean: mean rate newtype Summary = Summary { count :: Int , m1 :: Number @@ -54,6 +75,7 @@ instance showMSummary :: Show Summary where instance encodeMSummary :: Encode Summary where encode = genericEncode $ defaultOptions { unwrapSingleConstructors = true } +-- | Returns the summary of the measurements in the meter. read :: forall eff. Meter -> Eff (ref :: REF | eff) Summary read m = Summary <$> ({ count: _ , m1: _ diff --git a/src/System/Metrics/Timer.purs b/src/System/Metrics/Timer.purs index 22d8bf4..90ce30b 100644 --- a/src/System/Metrics/Timer.purs +++ b/src/System/Metrics/Timer.purs @@ -1,6 +1,6 @@ module System.Metrics.Timer ( Timer - , Summary + , Summary(..) , new , update , time @@ -38,19 +38,24 @@ import System.Metrics.Histogram as Histogram import System.Metrics.Meter (Meter) import System.Metrics.Meter as Meter +-- | A timer is a histogram of the duration of a type of event and a meter of the rate of its occurrence. newtype Timer = Timer { meter :: Meter, histogram :: Histogram } +-- | Creates a new timer. new :: forall eff. Eff (ref :: REF | eff) Timer new = Timer <$> ({ meter: _, histogram: _ } <$> Meter.new <*> Histogram.newWithExponentialDecaySampling 1028 0.015) +-- | Updates the timer with the duration of a new event. update :: forall a eff. Duration a => Timer -> a -> Eff (ref :: REF | eff) Unit update (Timer { meter, histogram }) d = do let (Milliseconds ms) = fromDuration d Histogram.update histogram ms Meter.mark meter +-- | Runs and times the given function, updates the given timer with the run duration, and returns +-- | the return value of the function. time :: forall a eff. Timer -> Aff (ref :: REF, now :: NOW | eff) a -> Aff (ref :: REF, now :: NOW | eff) a time timer f = do start <- liftEff now @@ -59,42 +64,58 @@ time timer f = do liftEff $ update timer (unInstant end - unInstant start) pure r +-- | Returns the fifteen minute moving average rate of the events recorded by the timer. fifteenMinuteRate :: forall eff. Timer -> Eff (ref :: REF | eff) Number fifteenMinuteRate (Timer { meter }) = Meter.fifteenMinuteRate meter +-- | Returns the five minute moving average rate of the events recorded by the timer. fiveMinuteRate :: forall eff. Timer -> Eff (ref :: REF | eff) Number fiveMinuteRate (Timer { meter }) = Meter.fiveMinuteRate meter +-- | Returns the one minute moving average rate of the events recorded by the timer. oneMinuteRate :: forall eff. Timer -> Eff (ref :: REF | eff) Number oneMinuteRate (Timer { meter }) = Meter.oneMinuteRate meter +-- | Returns the mean rate of the events recorded by the timer. meanRate :: forall eff. Timer -> Eff (ref :: REF | eff) Number meanRate (Timer { meter }) = Meter.meanRate meter +-- | Clears the timer. clear :: forall eff. Timer -> Eff (ref :: REF | eff) Unit clear (Timer { histogram }) = Histogram.clear histogram +-- | Returns the percentiles of the measurements in the timer for the given percentiles array. percentiles :: forall eff. Timer -> Array Number -> Eff (ref :: REF | eff) (Maybe (Array Number)) percentiles (Timer { histogram }) = Histogram.percentiles histogram +-- | Returns the mean of the measurements in the timer. mean :: forall eff. Timer -> Eff (ref :: REF | eff) (Maybe Number) mean (Timer { histogram }) = Histogram.mean histogram +-- | Returns the standard deviation of the measurements in the timer. stdDev :: forall eff. Timer -> Eff (ref :: REF | eff) (Maybe Number) stdDev (Timer { histogram }) = Histogram.stdDev histogram +-- | Returns the minimum of the measurements in the timer. min :: forall eff. Timer -> Eff (ref :: REF | eff) (Maybe Number) min (Timer { histogram }) = Histogram.min histogram +-- | Returns the maximum of the measurements in the timer. max :: forall eff. Timer -> Eff (ref :: REF | eff) (Maybe Number) max (Timer { histogram }) = Histogram.max histogram +-- | Returns the sum of the measurements in the timer. sum :: forall eff. Timer -> Eff (ref :: REF | eff) (Maybe Number) sum (Timer { histogram }) = Histogram.sum histogram +-- | Returns the count of the measurements in the timer. count :: forall eff. Timer -> Eff (ref :: REF | eff) Int count (Timer { histogram }) = Histogram.count histogram +-- | Summary of the measurements in the meter. Contains: +-- | +-- | - duration: the summary of the underlying event duration histogram. +-- | - rate: the summary of the underlying event rate meter. newtype Summary = Summary { duration :: Histogram.Summary, rate :: Meter.Summary } derive instance eqTSummary :: Eq Summary @@ -104,6 +125,7 @@ instance showTSummary :: Show Summary where instance encodeTSummary :: Encode Summary where encode = genericEncode $ defaultOptions { unwrapSingleConstructors = true } +-- | Returns the summary of the measurements in the timer. read :: forall eff. Timer -> Eff (ref :: REF | eff) Summary read (Timer { meter, histogram }) = Summary <$> ({ duration: _, rate: _} <$> Histogram.read histogram <*> Meter.read meter)