metric_registry.pyx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. from library.python.monlib.encoder cimport Encoder
  2. from library.python.monlib.labels cimport TLabels
  3. from library.python.monlib.metric cimport (
  4. Gauge, IntGauge, Counter, Rate, Histogram, IHistogramCollectorPtr,
  5. ExponentialHistogram, ExplicitHistogram, LinearHistogram)
  6. from library.python.monlib.metric_consumer cimport IMetricConsumer
  7. from library.python.monlib.metric_registry cimport TMetricRegistry
  8. from util.generic.ptr cimport THolder
  9. from util.generic.string cimport TString
  10. from util.datetime.base cimport TInstant
  11. from util.system.types cimport ui32
  12. from util.generic.vector cimport TVector
  13. from libcpp.utility cimport move
  14. from libcpp.string cimport string
  15. from cython.operator cimport address, dereference as deref
  16. import datetime as dt
  17. import sys
  18. def get_or_raise(kwargs, key):
  19. value = kwargs.get(key)
  20. if value is None:
  21. raise ValueError(key + ' argument is required but not specified')
  22. return value
  23. class HistogramType(object):
  24. Exponential = 0
  25. Explicit = 1
  26. Linear = 2
  27. cdef class MetricRegistry:
  28. """
  29. Represents an entity holding a set of counters of different types identified by labels
  30. Example usage:
  31. .. ::
  32. registry = MetricRegistry()
  33. response_times = registry.histogram_rate(
  34. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  35. HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])
  36. requests = registry.rate({'path': 'ping', 'sensor': 'requestRate'})
  37. uptime = registry.gauge({'sensor': 'serverUptimeSeconds'})
  38. # ...
  39. requests.inc()
  40. uptime.set(time.time() - start_time)
  41. # ...
  42. dumps(registry)
  43. """
  44. cdef THolder[TMetricRegistry] __wrapped
  45. def __cinit__(self, labels=None):
  46. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  47. self.__wrapped.Reset(new TMetricRegistry(native_labels))
  48. @staticmethod
  49. cdef TLabels _py_to_native_labels(dict labels):
  50. cdef TLabels native_labels = TLabels()
  51. if labels is not None:
  52. for name, value in labels.items():
  53. native_labels.Add(TString(<string>name.encode('utf-8')), TString(<string>value.encode('utf-8')))
  54. return native_labels
  55. @staticmethod
  56. cdef _native_to_py_labels(const TLabels& native_labels):
  57. result = dict()
  58. cdef TLabels.const_iterator it = native_labels.begin()
  59. while it != native_labels.end():
  60. name = TString(deref(it).Name())
  61. value = TString(deref(it).Value())
  62. if (isinstance(name, bytes)):
  63. name = name.decode('utf-8')
  64. if (isinstance(value, bytes)):
  65. value = value.decode('utf-8')
  66. result[name] = value
  67. it += 1
  68. return result
  69. def _histogram(self, labels, is_rate, hist_type, **kwargs):
  70. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  71. cdef IHistogramCollectorPtr collector
  72. cdef TVector[double] native_buckets
  73. if hist_type == HistogramType.Exponential:
  74. buckets = int(get_or_raise(kwargs, 'bucket_count'))
  75. base = float(get_or_raise(kwargs, 'base'))
  76. scale = float(kwargs.get('scale', 1.))
  77. collector = move(ExponentialHistogram(buckets, base, scale))
  78. elif hist_type == HistogramType.Explicit:
  79. buckets = get_or_raise(kwargs, 'buckets')
  80. native_buckets = buckets
  81. collector = move(ExplicitHistogram(native_buckets))
  82. elif hist_type == HistogramType.Linear:
  83. buckets = get_or_raise(kwargs, 'bucket_count')
  84. start_value = get_or_raise(kwargs, 'start_value')
  85. bucket_width = get_or_raise(kwargs, 'bucket_width')
  86. collector = move(LinearHistogram(buckets, start_value, bucket_width))
  87. else:
  88. # XXX: string representation
  89. raise ValueError('histogram type {} is not supported'.format(str(hist_type)))
  90. cdef THistogram* native_hist
  91. if is_rate:
  92. native_hist = self.__wrapped.Get().HistogramRate(native_labels, move(collector))
  93. else:
  94. native_hist = self.__wrapped.Get().HistogramCounter(native_labels, move(collector))
  95. return Histogram.from_ptr(native_hist)
  96. @property
  97. def common_labels(self):
  98. """
  99. Gets labels that are common among all the counters in this registry
  100. :returns: Common labels as a dict
  101. """
  102. cdef const TLabels* native = address(self.__wrapped.Get().CommonLabels())
  103. labels = MetricRegistry._native_to_py_labels(deref(native))
  104. return labels
  105. def gauge(self, labels):
  106. """
  107. Gets a gauge counter or creates a new one in case counter with the specified labels
  108. does not exist
  109. :param labels: A dict of labels which identifies counter
  110. :returns: Gauge counter
  111. """
  112. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  113. native_gauge = self.__wrapped.Get().Gauge(native_labels)
  114. return Gauge.from_ptr(native_gauge)
  115. def int_gauge(self, labels):
  116. """
  117. Gets a gauge counter or creates a new one in case counter with the specified labels
  118. does not exist
  119. :param labels: A dict of labels which identifies counter
  120. :returns: IntGauge counter
  121. """
  122. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  123. native_gauge = self.__wrapped.Get().IntGauge(native_labels)
  124. return IntGauge.from_ptr(native_gauge)
  125. def counter(self, labels):
  126. """
  127. Gets a counter or creates a new one in case counter with the specified labels
  128. does not exist
  129. :param labels: A dict of labels which identifies counter
  130. :returns: Counter counter
  131. """
  132. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  133. native_counter = self.__wrapped.Get().Counter(native_labels)
  134. return Counter.from_ptr(native_counter)
  135. def rate(self, labels):
  136. """
  137. Gets a rate counter or creates a new one in case counter with the specified labels
  138. does not exist
  139. :param labels: A dict of labels which identifies counter
  140. :returns: Rate counter
  141. """
  142. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  143. native_rate = self.__wrapped.Get().Rate(native_labels)
  144. return Rate.from_ptr(native_rate)
  145. def histogram_counter(self, labels, hist_type, **kwargs):
  146. """
  147. Gets a histogram counter or creates a new one in case counter with the specified labels
  148. does not exist
  149. :param labels: A dict of labels which identifies counter
  150. :param hist_type: Specifies the way histogram buckets are defined (allowed values: explicit, exponential, linear)
  151. Keyword arguments:
  152. :param buckets: A list of bucket upper bounds (explicit)
  153. :param bucket_count: Number of buckets (linear, exponential)
  154. :param base: the exponential growth factor for buckets' width (exponential)
  155. :param scale: linear scale for the buckets. Must be >= 1.0 (exponential)
  156. :param start_value: the upper bound of the first bucket (linear)
  157. :returns: Histogram counter
  158. Example usage:
  159. .. ::
  160. my_histogram = registry.histogram_counter(
  161. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  162. HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])
  163. # (-inf; 10] (10; 20] (20; 50] (200; 500] (500; +inf)
  164. # or:
  165. my_histogram = registry.histogram_counter(
  166. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  167. HistogramType.Linear, bucket_count=4, bucket_width=10, start_value=0)
  168. # (-inf; 0] (0; 10] (10; 20] (20; +inf)
  169. # or:
  170. my_histogram = registry.histogram_counter(
  171. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  172. HistogramType.Exponential, bucket_count=6, base=2, scale=3)
  173. # (-inf; 3] (3; 6] (6; 12] (12; 24] (24; 48] (48; +inf)
  174. ::
  175. """
  176. return self._histogram(labels, False, hist_type, **kwargs)
  177. def histogram_rate(self, labels, hist_type, **kwargs):
  178. """
  179. Gets a histogram rate counter or creates a new one in case counter with the specified labels
  180. does not exist
  181. :param labels: A dict of labels which identifies counter
  182. :param hist_type: Specifies the way histogram buckets are defined (allowed values: explicit, exponential, linear)
  183. Keyword arguments:
  184. :param buckets: A list of bucket upper bounds (explicit)
  185. :param bucket_count: Number of buckets (linear, exponential)
  186. :param base: the exponential growth factor for buckets' width (exponential)
  187. :param scale: linear scale for the buckets. Must be >= 1.0 (exponential)
  188. :param start_value: the upper bound of the first bucket (linear)
  189. :returns: Histogram counter
  190. Example usage:
  191. .. ::
  192. my_histogram = registry.histogram_counter(
  193. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  194. HistogramType.Explicit, buckets=[10, 20, 50, 200, 500])
  195. # (-inf; 10] (10; 20] (20; 50] (200; 500] (500; +inf)
  196. # or:
  197. my_histogram = registry.histogram_counter(
  198. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  199. HistogramType.Linear, bucket_count=4, bucket_width=10, start_value=0)
  200. # (-inf; 0] (0; 10] (10; 20] (20; +inf)
  201. # or:
  202. my_histogram = registry.histogram_counter(
  203. {'path': 'ping', 'sensor': 'responseTimeMillis'},
  204. HistogramType.Exponential, bucket_count=6, base=2, scale=3)
  205. # (-inf; 3] (3; 6] (6; 12] (12; 24] (24; 48] (48; +inf)
  206. ::
  207. """
  208. return self._histogram(labels, True, hist_type, **kwargs)
  209. def reset(self):
  210. self.__wrapped.Get().Reset()
  211. def accept(self, time, Encoder encoder):
  212. cdef IMetricConsumer* ptr = <IMetricConsumer*>encoder.native()
  213. timestamp = int((time - dt.datetime(1970, 1, 1)).total_seconds())
  214. self.__wrapped.Get().Accept(TInstant.Seconds(timestamp), ptr)
  215. def remove_metric(self, labels):
  216. cdef TLabels native_labels = MetricRegistry._py_to_native_labels(labels)
  217. self.__wrapped.Get().RemoveMetric(native_labels)