Skip to content

Commit c4cdbcd

Browse files
Make Counter polymorphic
1 parent 563a533 commit c4cdbcd

4 files changed

Lines changed: 107 additions & 26 deletions

File tree

src/Prometheus/V3/Metric/Counter.hs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
{-# LANGUAGE AllowAmbiguousTypes #-}
2+
{-# LANGUAGE ConstraintKinds #-}
3+
{-# LANGUAGE FlexibleContexts #-}
24
{-# LANGUAGE FlexibleInstances #-}
35
{-# LANGUAGE ImportQualifiedPost #-}
46
{-# LANGUAGE NamedFieldPuns #-}
57
{-# LANGUAGE ScopedTypeVariables #-}
8+
{-# LANGUAGE TypeFamilies #-}
9+
{-# LANGUAGE TypeOperators #-}
10+
{-# LANGUAGE UndecidableInstances #-}
611
{-# LANGUAGE NoFieldSelectors #-}
712
-- Remove after inlining V2.Counter
813
{-# OPTIONS_GHC -Wno-orphans #-}
914

1015
module Prometheus.V3.Metric.Counter (
1116
V2.Counter,
17+
V2.Counter',
1218
register,
1319
new,
1420

1521
-- * Methods
1622
IsCounter,
23+
IsNumCounter,
1724
inc,
1825
add,
1926
reset,
@@ -40,12 +47,16 @@ import UnliftIO.Exception (Exception, withException)
4047

4148

4249
-- | An alias for @'Registry.register' (new ...)@.
43-
register :: MetricName -> Description -> V2.Counter
50+
register ::
51+
(Num a, Ord a, ToSampleValue a, V2.HasCounterBackend a) =>
52+
MetricName -> Description -> (V2.Counter' a)
4453
register name description = Registry.register $ new name description
4554
{-# INLINE register #-}
4655

4756

48-
new :: MetricName -> Description -> Metric V2.Counter
57+
new ::
58+
(Num a, V2.HasCounterBackend a) =>
59+
MetricName -> Description -> Metric (V2.Counter' a)
4960
new name description =
5061
Metric
5162
{ name
@@ -54,42 +65,54 @@ new name description =
5465
}
5566

5667

57-
instance IsMetric V2.Counter where
68+
instance (SampleValueNum a, Ord a, V2.HasCounterBackend a) => IsMetric (V2.Counter' a) where
5869
getMetricType _ = MetricTypeCounter
5970
getMetricSamples counter = do
6071
n <- sample counter
6172
pure [defaultSample{value = toSampleValue n}]
6273

6374

6475
class IsCounter c where
65-
getCounter :: c -> IO V2.Counter
66-
instance IsCounter V2.Counter where
76+
type CounterElem c
77+
getCounter :: c -> IO (V2.Counter' (CounterElem c))
78+
instance IsCounter (V2.Counter' a) where
79+
type CounterElem (V2.Counter' a) = a
6780
getCounter = pure
68-
instance IsCounter (IO V2.Counter) where
81+
instance IsCounter (IO (V2.Counter' a)) where
82+
type CounterElem (IO (V2.Counter' a)) = a
6983
getCounter = id
7084

7185

72-
inc :: (IsCounter c, MonadIO m) => c -> m ()
86+
type IsNumCounter c a =
87+
( IsCounter c
88+
, a ~ CounterElem c
89+
, V2.HasCounterBackend a
90+
, Num a
91+
, Ord a
92+
)
93+
94+
95+
inc :: (IsNumCounter c a, MonadIO m) => c -> m ()
7396
inc c = liftIO $ V2.inc =<< getCounter c
7497
{-# INLINE inc #-}
7598

7699

77-
add :: (IsCounter c, MonadIO m) => c -> Int -> m ()
100+
add :: (IsNumCounter c a, MonadIO m) => c -> a -> m ()
78101
add c n = liftIO $ V2.add n =<< getCounter c
79102
{-# INLINE add #-}
80103

81104

82-
reset :: (IsCounter c, MonadIO m) => c -> m ()
105+
reset :: (IsNumCounter c a, MonadIO m) => c -> m ()
83106
reset c = liftIO $ V2.set 0 =<< getCounter c
84107
{-# INLINE reset #-}
85108

86109

87-
sample :: (IsCounter c, MonadIO m) => c -> m Int
110+
sample :: (IsNumCounter c a, MonadIO m) => c -> m a
88111
sample c = liftIO $ fmap V2.unCounterSample . V2.sample =<< getCounter c
89112
{-# INLINE sample #-}
90113

91114

92-
addAndSample :: (IsCounter c, MonadIO m) => c -> Int -> m Int
115+
addAndSample :: (IsNumCounter c a, MonadIO m) => c -> a -> m a
93116
addAndSample c n = liftIO $ fmap V2.unCounterSample . V2.addAndSample n =<< getCounter c
94117
{-# INLINE addAndSample #-}
95118

@@ -99,7 +122,7 @@ addAndSample c n = liftIO $ fmap V2.unCounterSample . V2.addAndSample n =<< getC
99122
-- | Count the number of times a particular exception is thrown.
100123
countExceptions ::
101124
forall e c m a.
102-
(IsCounter c, Exception e, MonadUnliftIO m) =>
125+
(IsNumCounter c a, Exception e, MonadUnliftIO m) =>
103126
c -> m a -> m a
104127
countExceptions c = countExceptionsWhere c (\(_ :: e) -> True)
105128
{-# INLINE countExceptions #-}
@@ -109,7 +132,7 @@ countExceptions c = countExceptionsWhere c (\(_ :: e) -> True)
109132
-- include the exception in the count.
110133
countExceptionsWhere ::
111134
forall e c m a.
112-
(IsCounter c, Exception e, MonadUnliftIO m) =>
135+
(IsNumCounter c a, Exception e, MonadUnliftIO m) =>
113136
c -> (e -> Bool) -> m a -> m a
114137
countExceptionsWhere c f io = io `withException` \e -> when (f e) (inc c)
115138
{-# INLINE countExceptionsWhere #-}

src/System/Metrics/Prometheus/Encode/Text.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ encodeMetric (mid, sample) =
6060
encodeSummary = undefined
6161

6262

63-
encodeCounter :: MetricId -> CounterSample -> Builder
63+
encodeCounter :: MetricId -> CounterSample Int -> Builder
6464
encodeCounter mid counter = encodeMetricId mid <> space <> encodeInt (unCounterSample counter)
6565

6666

src/System/Metrics/Prometheus/Metric.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ data Metric
1919

2020

2121
data MetricSample
22-
= CounterMetricSample CounterSample
22+
= CounterMetricSample (CounterSample Int)
2323
| GaugeMetricSample (GaugeSample Double)
2424
| HistogramMetricSample HistogramSample
2525
| SummaryMetricSample SummarySample
2626

2727

2828
metricSample ::
29-
(CounterSample -> a) ->
29+
(CounterSample Int -> a) ->
3030
(GaugeSample Double -> a) ->
3131
(HistogramSample -> a) ->
3232
(SummarySample -> a) ->
Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,100 @@
1+
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE TypeFamilies #-}
4+
{-# LANGUAGE TypeOperators #-}
5+
16
module System.Metrics.Prometheus.Metric.Counter (
27
Counter,
8+
Counter',
39
CounterSample (..),
410
new,
511
add,
612
inc,
713
sample,
814
addAndSample,
915
set,
16+
17+
-- * Backends
18+
HasCounterBackend,
19+
CounterBackend,
20+
IsCounterBackend,
1021
) where
1122

1223
import Control.Applicative ((<$>))
1324
import Control.Monad (when)
14-
import Data.Atomics.Counter (AtomicCounter, incrCounter, newCounter, writeCounter)
25+
import Data.Atomics.Counter (AtomicCounter)
26+
import qualified Data.Atomics.Counter as Atomic
27+
import Data.IORef (IORef, atomicModifyIORef', newIORef, writeIORef)
28+
29+
30+
-- | The default Counter tracking an Int.
31+
type Counter = Counter' Int
32+
33+
34+
-- | A Counter that works for any type, but with worse performance.
35+
newtype Counter' a = Counter {unCounter :: CounterBackend a}
36+
37+
38+
newtype CounterSample a = CounterSample {unCounterSample :: a} deriving (Show)
39+
40+
41+
type family CounterBackend a where
42+
CounterBackend Int = AtomicCounter
43+
CounterBackend a = IORef a
44+
45+
46+
class IsCounterBackend a where
47+
type CounterElem a
48+
newCounter :: CounterElem a -> IO a
49+
incrCounter :: CounterElem a -> a -> IO (CounterElem a)
50+
writeCounter :: a -> CounterElem a -> IO ()
51+
instance IsCounterBackend AtomicCounter where
52+
type CounterElem AtomicCounter = Int
53+
newCounter = Atomic.newCounter
54+
incrCounter = Atomic.incrCounter
55+
writeCounter = Atomic.writeCounter
56+
instance (Num a) => IsCounterBackend (IORef a) where
57+
type CounterElem (IORef a) = a
58+
newCounter = newIORef
59+
incrCounter x ref = atomicModifyIORef' ref $ \a -> (a + x, a + x)
60+
writeCounter = writeIORef
1561

1662

17-
newtype Counter = Counter {unCounter :: AtomicCounter}
18-
newtype CounterSample = CounterSample {unCounterSample :: Int} deriving Show
63+
type HasCounterBackend a =
64+
( IsCounterBackend (CounterBackend a)
65+
, a ~ CounterElem (CounterBackend a)
66+
)
1967

2068

21-
new :: IO Counter
69+
new ::
70+
(Num a, HasCounterBackend a) =>
71+
IO (Counter' a)
2272
new = Counter <$> newCounter 0
2373

2474

25-
addAndSample :: Int -> Counter -> IO CounterSample
75+
addAndSample ::
76+
(Num a, Ord a, HasCounterBackend a) =>
77+
a -> Counter' a -> IO (CounterSample a)
2678
addAndSample by
2779
| by >= 0 = fmap CounterSample . incrCounter by . unCounter
2880
| otherwise = error "must be >= 0"
2981

3082

31-
add :: Int -> Counter -> IO ()
83+
add ::
84+
(Num a, Ord a, HasCounterBackend a) =>
85+
a -> Counter' a -> IO ()
3286
add by c = addAndSample by c >> pure ()
3387

3488

35-
inc :: Counter -> IO ()
89+
inc ::
90+
(Num a, Ord a, HasCounterBackend a) =>
91+
Counter' a -> IO ()
3692
inc = add 1
3793

3894

39-
sample :: Counter -> IO CounterSample
95+
sample ::
96+
(Num a, Ord a, HasCounterBackend a) =>
97+
Counter' a -> IO (CounterSample a)
4098
sample = addAndSample 0
4199

42100

@@ -46,7 +104,7 @@ sample = addAndSample 0
46104
--
47105
-- WARNING: For multiple writers, the most recent one wins, which may not
48106
-- preserve the increasing property. If you have stronger requirements than this,
49-
-- please check with the maintainers.
107+
-- please check with the maintainers.
50108
-- See <https://github.com/bitnomial/prometheus/pull/44> for discussion.
51-
set :: Int -> Counter -> IO ()
109+
set :: (HasCounterBackend a) => a -> Counter' a -> IO ()
52110
set i (Counter c) = writeCounter c i

0 commit comments

Comments
 (0)