metrics_core.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import re
  2. from .samples import Sample
  3. METRIC_TYPES = (
  4. 'counter', 'gauge', 'summary', 'histogram',
  5. 'gaugehistogram', 'unknown', 'info', 'stateset',
  6. )
  7. METRIC_NAME_RE = re.compile(r'^[a-zA-Z_:][a-zA-Z0-9_:]*$')
  8. METRIC_LABEL_NAME_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
  9. RESERVED_METRIC_LABEL_NAME_RE = re.compile(r'^__.*$')
  10. class Metric(object):
  11. """A single metric family and its samples.
  12. This is intended only for internal use by the instrumentation client.
  13. Custom collectors should use GaugeMetricFamily, CounterMetricFamily
  14. and SummaryMetricFamily instead.
  15. """
  16. def __init__(self, name, documentation, typ, unit=''):
  17. if unit and not name.endswith("_" + unit):
  18. name += "_" + unit
  19. if not METRIC_NAME_RE.match(name):
  20. raise ValueError('Invalid metric name: ' + name)
  21. self.name = name
  22. self.documentation = documentation
  23. self.unit = unit
  24. if typ == 'untyped':
  25. typ = 'unknown'
  26. if typ not in METRIC_TYPES:
  27. raise ValueError('Invalid metric type: ' + typ)
  28. self.type = typ
  29. self.samples = []
  30. def add_sample(self, name, labels, value, timestamp=None, exemplar=None):
  31. """Add a sample to the metric.
  32. Internal-only, do not use."""
  33. self.samples.append(Sample(name, labels, value, timestamp, exemplar))
  34. def __eq__(self, other):
  35. return (isinstance(other, Metric) and
  36. self.name == other.name and
  37. self.documentation == other.documentation and
  38. self.type == other.type and
  39. self.unit == other.unit and
  40. self.samples == other.samples)
  41. def __repr__(self):
  42. return "Metric(%s, %s, %s, %s, %s)" % (
  43. self.name,
  44. self.documentation,
  45. self.type,
  46. self.unit,
  47. self.samples,
  48. )
  49. class UnknownMetricFamily(Metric):
  50. """A single unknown metric and its samples.
  51. For use by custom collectors.
  52. """
  53. def __init__(self, name, documentation, value=None, labels=None, unit=''):
  54. Metric.__init__(self, name, documentation, 'unknown', unit)
  55. if labels is not None and value is not None:
  56. raise ValueError('Can only specify at most one of value and labels.')
  57. if labels is None:
  58. labels = []
  59. self._labelnames = tuple(labels)
  60. if value is not None:
  61. self.add_metric([], value)
  62. def add_metric(self, labels, value, timestamp=None):
  63. """Add a metric to the metric family.
  64. Args:
  65. labels: A list of label values
  66. value: The value of the metric.
  67. """
  68. self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp))
  69. # For backward compatibility.
  70. UntypedMetricFamily = UnknownMetricFamily
  71. class CounterMetricFamily(Metric):
  72. """A single counter and its samples.
  73. For use by custom collectors.
  74. """
  75. def __init__(self, name, documentation, value=None, labels=None, created=None, unit=''):
  76. # Glue code for pre-OpenMetrics metrics.
  77. if name.endswith('_total'):
  78. name = name[:-6]
  79. Metric.__init__(self, name, documentation, 'counter', unit)
  80. if labels is not None and value is not None:
  81. raise ValueError('Can only specify at most one of value and labels.')
  82. if labels is None:
  83. labels = []
  84. self._labelnames = tuple(labels)
  85. if value is not None:
  86. self.add_metric([], value, created)
  87. def add_metric(self, labels, value, created=None, timestamp=None):
  88. """Add a metric to the metric family.
  89. Args:
  90. labels: A list of label values
  91. value: The value of the metric
  92. created: Optional unix timestamp the child was created at.
  93. """
  94. self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, labels)), value, timestamp))
  95. if created is not None:
  96. self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, labels)), created, timestamp))
  97. class GaugeMetricFamily(Metric):
  98. """A single gauge and its samples.
  99. For use by custom collectors.
  100. """
  101. def __init__(self, name, documentation, value=None, labels=None, unit=''):
  102. Metric.__init__(self, name, documentation, 'gauge', unit)
  103. if labels is not None and value is not None:
  104. raise ValueError('Can only specify at most one of value and labels.')
  105. if labels is None:
  106. labels = []
  107. self._labelnames = tuple(labels)
  108. if value is not None:
  109. self.add_metric([], value)
  110. def add_metric(self, labels, value, timestamp=None):
  111. """Add a metric to the metric family.
  112. Args:
  113. labels: A list of label values
  114. value: A float
  115. """
  116. self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp))
  117. class SummaryMetricFamily(Metric):
  118. """A single summary and its samples.
  119. For use by custom collectors.
  120. """
  121. def __init__(self, name, documentation, count_value=None, sum_value=None, labels=None, unit=''):
  122. Metric.__init__(self, name, documentation, 'summary', unit)
  123. if (sum_value is None) != (count_value is None):
  124. raise ValueError('count_value and sum_value must be provided together.')
  125. if labels is not None and count_value is not None:
  126. raise ValueError('Can only specify at most one of value and labels.')
  127. if labels is None:
  128. labels = []
  129. self._labelnames = tuple(labels)
  130. if count_value is not None:
  131. self.add_metric([], count_value, sum_value)
  132. def add_metric(self, labels, count_value, sum_value, timestamp=None):
  133. """Add a metric to the metric family.
  134. Args:
  135. labels: A list of label values
  136. count_value: The count value of the metric.
  137. sum_value: The sum value of the metric.
  138. """
  139. self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), count_value, timestamp))
  140. self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp))
  141. class HistogramMetricFamily(Metric):
  142. """A single histogram and its samples.
  143. For use by custom collectors.
  144. """
  145. def __init__(self, name, documentation, buckets=None, sum_value=None, labels=None, unit=''):
  146. Metric.__init__(self, name, documentation, 'histogram', unit)
  147. if sum_value is not None and buckets is None:
  148. raise ValueError('sum value cannot be provided without buckets.')
  149. if labels is not None and buckets is not None:
  150. raise ValueError('Can only specify at most one of buckets and labels.')
  151. if labels is None:
  152. labels = []
  153. self._labelnames = tuple(labels)
  154. if buckets is not None:
  155. self.add_metric([], buckets, sum_value)
  156. def add_metric(self, labels, buckets, sum_value, timestamp=None):
  157. """Add a metric to the metric family.
  158. Args:
  159. labels: A list of label values
  160. buckets: A list of lists.
  161. Each inner list can be a pair of bucket name and value,
  162. or a triple of bucket name, value, and exemplar.
  163. The buckets must be sorted, and +Inf present.
  164. sum_value: The sum value of the metric.
  165. """
  166. for b in buckets:
  167. bucket, value = b[:2]
  168. exemplar = None
  169. if len(b) == 3:
  170. exemplar = b[2]
  171. self.samples.append(Sample(
  172. self.name + '_bucket',
  173. dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
  174. value,
  175. timestamp,
  176. exemplar,
  177. ))
  178. # +Inf is last and provides the count value.
  179. self.samples.append(
  180. Sample(self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp))
  181. # Don't iunclude sum if there's negative buckets.
  182. if float(buckets[0][0]) >= 0 and sum_value is not None:
  183. self.samples.append(
  184. Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp))
  185. class GaugeHistogramMetricFamily(Metric):
  186. """A single gauge histogram and its samples.
  187. For use by custom collectors.
  188. """
  189. def __init__(self, name, documentation, buckets=None, gsum_value=None, labels=None, unit=''):
  190. Metric.__init__(self, name, documentation, 'gaugehistogram', unit)
  191. if labels is not None and buckets is not None:
  192. raise ValueError('Can only specify at most one of buckets and labels.')
  193. if labels is None:
  194. labels = []
  195. self._labelnames = tuple(labels)
  196. if buckets is not None:
  197. self.add_metric([], buckets, gsum_value)
  198. def add_metric(self, labels, buckets, gsum_value, timestamp=None):
  199. """Add a metric to the metric family.
  200. Args:
  201. labels: A list of label values
  202. buckets: A list of pairs of bucket names and values.
  203. The buckets must be sorted, and +Inf present.
  204. gsum_value: The sum value of the metric.
  205. """
  206. for bucket, value in buckets:
  207. self.samples.append(Sample(
  208. self.name + '_bucket',
  209. dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
  210. value, timestamp))
  211. # +Inf is last and provides the count value.
  212. self.samples.extend([
  213. Sample(self.name + '_gcount', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp),
  214. Sample(self.name + '_gsum', dict(zip(self._labelnames, labels)), gsum_value, timestamp),
  215. ])
  216. class InfoMetricFamily(Metric):
  217. """A single info and its samples.
  218. For use by custom collectors.
  219. """
  220. def __init__(self, name, documentation, value=None, labels=None):
  221. Metric.__init__(self, name, documentation, 'info')
  222. if labels is not None and value is not None:
  223. raise ValueError('Can only specify at most one of value and labels.')
  224. if labels is None:
  225. labels = []
  226. self._labelnames = tuple(labels)
  227. if value is not None:
  228. self.add_metric([], value)
  229. def add_metric(self, labels, value, timestamp=None):
  230. """Add a metric to the metric family.
  231. Args:
  232. labels: A list of label values
  233. value: A dict of labels
  234. """
  235. self.samples.append(Sample(
  236. self.name + '_info',
  237. dict(dict(zip(self._labelnames, labels)), **value),
  238. 1,
  239. timestamp,
  240. ))
  241. class StateSetMetricFamily(Metric):
  242. """A single stateset and its samples.
  243. For use by custom collectors.
  244. """
  245. def __init__(self, name, documentation, value=None, labels=None):
  246. Metric.__init__(self, name, documentation, 'stateset')
  247. if labels is not None and value is not None:
  248. raise ValueError('Can only specify at most one of value and labels.')
  249. if labels is None:
  250. labels = []
  251. self._labelnames = tuple(labels)
  252. if value is not None:
  253. self.add_metric([], value)
  254. def add_metric(self, labels, value, timestamp=None):
  255. """Add a metric to the metric family.
  256. Args:
  257. labels: A list of label values
  258. value: A dict of string state names to booleans
  259. """
  260. labels = tuple(labels)
  261. for state, enabled in sorted(value.items()):
  262. v = (1 if enabled else 0)
  263. self.samples.append(Sample(
  264. self.name,
  265. dict(zip(self._labelnames + (self.name,), labels + (state,))),
  266. v,
  267. timestamp,
  268. ))