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.string cimport string from cython.operator cimport address, dereference as deref import datetime as dt import sys cdef extern from "" namespace "std" nogil: cdef IHistogramCollectorPtr&& move(IHistogramCollectorPtr t) 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(name.encode('utf-8')), TString(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 = 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)