aboutsummaryrefslogtreecommitdiffstats
path: root/library/python/monlib/metric_registry.pyx
blob: aae0a5962ad781f56da0ffa5792d29ad53f2bcfe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
from library.python.monlib.encoder cimport Encoder
from library.python.monlib.labels cimport TLabels
from library.python.monlib.metric cimport (
        Gauge, IntGauge, Counter, Rate, Histogram, IHistogramCollectorPtr,
        ExponentialHistogram, ExplicitHistogram, LinearHistogram)
from library.python.monlib.metric_consumer cimport IMetricConsumer
from library.python.monlib.metric_registry cimport TMetricRegistry

from util.generic.ptr cimport THolder
from util.generic.string cimport TString
from util.datetime.base cimport TInstant
from util.system.types cimport ui32
from util.generic.vector cimport TVector

from libcpp.utility cimport move
from libcpp.string cimport string

from cython.operator cimport address, dereference as deref

import datetime as dt
import sys


def get_or_raise(kwargs, key):
    value = kwargs.get(key)
    if value is None:
        raise ValueError(key + ' argument is required but not specified')

    return value


class HistogramType(object):
    Exponential = 0
    Explicit = 1
    Linear = 2


cdef class MetricRegistry:
    """
    Represents an entity holding a set of counters of different types identified by labels

    Example usage:
    .. ::
        registry = MetricRegistry()

        response_times = registry.histogram_rate(
            {'path': 'ping', 'sensor': 'responseTimeMillis'},
            HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])

        requests = registry.rate({'path': 'ping', 'sensor': 'requestRate'})
        uptime = registry.gauge({'sensor': 'serverUptimeSeconds'})

        # ...
        requests.inc()
        uptime.set(time.time() - start_time)

        # ...
        dumps(registry)
    """
    cdef THolder[TMetricRegistry] __wrapped

    def __cinit__(self, labels=None):
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        self.__wrapped.Reset(new TMetricRegistry(native_labels))

    @staticmethod
    cdef TLabels _py_to_native_labels(dict labels):
        cdef TLabels native_labels = TLabels()

        if labels is not None:
            for name, value in labels.items():
                native_labels.Add(TString(<string>name.encode('utf-8')), TString(<string>value.encode('utf-8')))

        return native_labels

    @staticmethod
    cdef _native_to_py_labels(const TLabels& native_labels):
        result = dict()

        cdef TLabels.const_iterator it = native_labels.begin()
        while it != native_labels.end():
            name = TString(deref(it).Name())
            value = TString(deref(it).Value())
            if (isinstance(name, bytes)):
                name = name.decode('utf-8')

            if (isinstance(value, bytes)):
                value = value.decode('utf-8')

            result[name] = value
            it += 1

        return result

    def _histogram(self, labels, is_rate, hist_type, **kwargs):
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        cdef IHistogramCollectorPtr collector
        cdef TVector[double] native_buckets

        if hist_type == HistogramType.Exponential:
            buckets = int(get_or_raise(kwargs, 'bucket_count'))
            base = float(get_or_raise(kwargs, 'base'))
            scale = float(kwargs.get('scale', 1.))
            collector = move(ExponentialHistogram(buckets, base, scale))
        elif hist_type == HistogramType.Explicit:
            buckets = get_or_raise(kwargs, 'buckets')
            native_buckets = buckets
            collector = move(ExplicitHistogram(native_buckets))
        elif hist_type == HistogramType.Linear:
            buckets = get_or_raise(kwargs, 'bucket_count')
            start_value = get_or_raise(kwargs, 'start_value')
            bucket_width = get_or_raise(kwargs, 'bucket_width')
            collector = move(LinearHistogram(buckets, start_value, bucket_width))
        else:
            # XXX: string representation
            raise ValueError('histogram type {} is not supported'.format(str(hist_type)))

        cdef THistogram* native_hist
        if is_rate:
            native_hist = self.__wrapped.Get().HistogramRate(native_labels, move(collector))
        else:
            native_hist = self.__wrapped.Get().HistogramCounter(native_labels, move(collector))

        return Histogram.from_ptr(native_hist)

    @property
    def common_labels(self):
        """
        Gets labels that are common among all the counters in this registry

        :returns: Common labels as a dict
        """
        cdef const TLabels* native = address(self.__wrapped.Get().CommonLabels())
        labels = MetricRegistry._native_to_py_labels(deref(native))

        return labels

    def gauge(self, labels):
        """
        Gets a gauge counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :returns: Gauge counter
        """
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        native_gauge = self.__wrapped.Get().Gauge(native_labels)
        return Gauge.from_ptr(native_gauge)

    def int_gauge(self, labels):
        """
        Gets a gauge counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :returns: IntGauge counter
        """
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        native_gauge = self.__wrapped.Get().IntGauge(native_labels)
        return IntGauge.from_ptr(native_gauge)

    def counter(self, labels):
        """
        Gets a counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :returns: Counter counter
        """
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        native_counter = self.__wrapped.Get().Counter(native_labels)
        return Counter.from_ptr(native_counter)

    def rate(self, labels):
        """
        Gets a rate counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :returns: Rate counter
        """
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        native_rate = self.__wrapped.Get().Rate(native_labels)
        return Rate.from_ptr(native_rate)

    def histogram_counter(self, labels, hist_type, **kwargs):
        """
        Gets a histogram counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :param hist_type: Specifies the way histogram buckets are defined (allowed values: explicit, exponential, linear)

        Keyword arguments:
        :param buckets: A list of bucket upper bounds (explicit)
        :param bucket_count: Number of buckets (linear, exponential)
        :param base: the exponential growth factor for buckets' width (exponential)
        :param scale: linear scale for the buckets. Must be >= 1.0 (exponential)
        :param start_value: the upper bound of the first bucket (linear)

        :returns: Histogram counter

        Example usage:
        .. ::
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])
            # (-inf; 10] (10; 20] (20; 50] (200; 500] (500; +inf)

            # or:
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Linear, bucket_count=4, bucket_width=10, start_value=0)
            # (-inf; 0] (0; 10] (10; 20] (20; +inf)

            # or:
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Exponential, bucket_count=6, base=2, scale=3)
            # (-inf; 3] (3; 6] (6; 12] (12; 24] (24; 48] (48; +inf)
        ::
        """
        return self._histogram(labels, False, hist_type, **kwargs)

    def histogram_rate(self, labels, hist_type, **kwargs):
        """
        Gets a histogram rate counter or creates a new one in case counter with the specified labels
        does not exist

        :param labels: A dict of labels which identifies counter
        :param hist_type: Specifies the way histogram buckets are defined (allowed values: explicit, exponential, linear)

        Keyword arguments:
        :param buckets: A list of bucket upper bounds (explicit)
        :param bucket_count: Number of buckets (linear, exponential)
        :param base: the exponential growth factor for buckets' width (exponential)
        :param scale: linear scale for the buckets. Must be >= 1.0 (exponential)
        :param start_value: the upper bound of the first bucket (linear)

        :returns: Histogram counter

        Example usage:
        .. ::
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])
            # (-inf; 10] (10; 20] (20; 50] (200; 500] (500; +inf)

            # or:
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Linear, bucket_count=4, bucket_width=10, start_value=0)
            # (-inf; 0] (0; 10] (10; 20] (20; +inf)

            # or:
            my_histogram = registry.histogram_counter(
                {'path': 'ping', 'sensor': 'responseTimeMillis'},
                HistogramType.Exponential, bucket_count=6, base=2, scale=3)
            # (-inf; 3] (3; 6] (6; 12] (12; 24] (24; 48] (48; +inf)
        ::
        """
        return self._histogram(labels, True, hist_type, **kwargs)

    def reset(self):
        self.__wrapped.Get().Reset()

    def accept(self, time, Encoder encoder):
        cdef IMetricConsumer* ptr = <IMetricConsumer*>encoder.native()
        timestamp = int((time - dt.datetime(1970, 1, 1)).total_seconds())
        self.__wrapped.Get().Accept(TInstant.Seconds(timestamp), ptr)

    def remove_metric(self, labels):
        cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
        self.__wrapped.Get().RemoveMetric(native_labels)