metrics.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. import os
  2. from threading import Lock
  3. import time
  4. import types
  5. from typing import (
  6. Any, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Tuple,
  7. Type, TypeVar, Union,
  8. )
  9. import warnings
  10. from . import values # retain this import style for testability
  11. from .context_managers import ExceptionCounter, InprogressTracker, Timer
  12. from .metrics_core import Metric
  13. from .registry import Collector, CollectorRegistry, REGISTRY
  14. from .samples import Exemplar, Sample
  15. from .utils import floatToGoString, INF
  16. from .validation import (
  17. _validate_exemplar, _validate_labelnames, _validate_metric_name,
  18. )
  19. T = TypeVar('T', bound='MetricWrapperBase')
  20. F = TypeVar("F", bound=Callable[..., Any])
  21. def _build_full_name(metric_type, name, namespace, subsystem, unit):
  22. if not name:
  23. raise ValueError('Metric name should not be empty')
  24. full_name = ''
  25. if namespace:
  26. full_name += namespace + '_'
  27. if subsystem:
  28. full_name += subsystem + '_'
  29. full_name += name
  30. if metric_type == 'counter' and full_name.endswith('_total'):
  31. full_name = full_name[:-6] # Munge to OpenMetrics.
  32. if unit and not full_name.endswith("_" + unit):
  33. full_name += "_" + unit
  34. if unit and metric_type in ('info', 'stateset'):
  35. raise ValueError('Metric name is of a type that cannot have a unit: ' + full_name)
  36. return full_name
  37. def _get_use_created() -> bool:
  38. return os.environ.get("PROMETHEUS_DISABLE_CREATED_SERIES", 'False').lower() not in ('true', '1', 't')
  39. _use_created = _get_use_created()
  40. def disable_created_metrics():
  41. """Disable exporting _created metrics on counters, histograms, and summaries."""
  42. global _use_created
  43. _use_created = False
  44. def enable_created_metrics():
  45. """Enable exporting _created metrics on counters, histograms, and summaries."""
  46. global _use_created
  47. _use_created = True
  48. class MetricWrapperBase(Collector):
  49. _type: Optional[str] = None
  50. _reserved_labelnames: Sequence[str] = ()
  51. def _is_observable(self):
  52. # Whether this metric is observable, i.e.
  53. # * a metric without label names and values, or
  54. # * the child of a labelled metric.
  55. return not self._labelnames or (self._labelnames and self._labelvalues)
  56. def _raise_if_not_observable(self):
  57. # Functions that mutate the state of the metric, for example incrementing
  58. # a counter, will fail if the metric is not observable, because only if a
  59. # metric is observable will the value be initialized.
  60. if not self._is_observable():
  61. raise ValueError('%s metric is missing label values' % str(self._type))
  62. def _is_parent(self):
  63. return self._labelnames and not self._labelvalues
  64. def _get_metric(self):
  65. return Metric(self._name, self._documentation, self._type, self._unit)
  66. def describe(self) -> Iterable[Metric]:
  67. return [self._get_metric()]
  68. def collect(self) -> Iterable[Metric]:
  69. metric = self._get_metric()
  70. for suffix, labels, value, timestamp, exemplar, native_histogram_value in self._samples():
  71. metric.add_sample(self._name + suffix, labels, value, timestamp, exemplar, native_histogram_value)
  72. return [metric]
  73. def __str__(self) -> str:
  74. return f"{self._type}:{self._name}"
  75. def __repr__(self) -> str:
  76. metric_type = type(self)
  77. return f"{metric_type.__module__}.{metric_type.__name__}({self._name})"
  78. def __init__(self: T,
  79. name: str,
  80. documentation: str,
  81. labelnames: Iterable[str] = (),
  82. namespace: str = '',
  83. subsystem: str = '',
  84. unit: str = '',
  85. registry: Optional[CollectorRegistry] = REGISTRY,
  86. _labelvalues: Optional[Sequence[str]] = None,
  87. ) -> None:
  88. self._original_name = name
  89. self._namespace = namespace
  90. self._subsystem = subsystem
  91. self._name = _build_full_name(self._type, name, namespace, subsystem, unit)
  92. self._labelnames = _validate_labelnames(self, labelnames)
  93. self._labelvalues = tuple(_labelvalues or ())
  94. self._kwargs: Dict[str, Any] = {}
  95. self._documentation = documentation
  96. self._unit = unit
  97. _validate_metric_name(self._name)
  98. if self._is_parent():
  99. # Prepare the fields needed for child metrics.
  100. self._lock = Lock()
  101. self._metrics: Dict[Sequence[str], T] = {}
  102. if self._is_observable():
  103. self._metric_init()
  104. if not self._labelvalues:
  105. # Register the multi-wrapper parent metric, or if a label-less metric, the whole shebang.
  106. if registry:
  107. registry.register(self)
  108. def labels(self: T, *labelvalues: Any, **labelkwargs: Any) -> T:
  109. """Return the child for the given labelset.
  110. All metrics can have labels, allowing grouping of related time series.
  111. Taking a counter as an example:
  112. from prometheus_client import Counter
  113. c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
  114. c.labels('get', '/').inc()
  115. c.labels('post', '/submit').inc()
  116. Labels can also be provided as keyword arguments:
  117. from prometheus_client import Counter
  118. c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
  119. c.labels(method='get', endpoint='/').inc()
  120. c.labels(method='post', endpoint='/submit').inc()
  121. See the best practices on [naming](http://prometheus.io/docs/practices/naming/)
  122. and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels).
  123. """
  124. if not self._labelnames:
  125. raise ValueError('No label names were set when constructing %s' % self)
  126. if self._labelvalues:
  127. raise ValueError('{} already has labels set ({}); can not chain calls to .labels()'.format(
  128. self,
  129. dict(zip(self._labelnames, self._labelvalues))
  130. ))
  131. if labelvalues and labelkwargs:
  132. raise ValueError("Can't pass both *args and **kwargs")
  133. if labelkwargs:
  134. if sorted(labelkwargs) != sorted(self._labelnames):
  135. raise ValueError('Incorrect label names')
  136. labelvalues = tuple(str(labelkwargs[l]) for l in self._labelnames)
  137. else:
  138. if len(labelvalues) != len(self._labelnames):
  139. raise ValueError('Incorrect label count')
  140. labelvalues = tuple(str(l) for l in labelvalues)
  141. with self._lock:
  142. if labelvalues not in self._metrics:
  143. original_name = getattr(self, '_original_name', self._name)
  144. namespace = getattr(self, '_namespace', '')
  145. subsystem = getattr(self, '_subsystem', '')
  146. unit = getattr(self, '_unit', '')
  147. child_kwargs = dict(self._kwargs) if self._kwargs else {}
  148. for k in ('namespace', 'subsystem', 'unit'):
  149. child_kwargs.pop(k, None)
  150. self._metrics[labelvalues] = self.__class__(
  151. original_name,
  152. documentation=self._documentation,
  153. labelnames=self._labelnames,
  154. namespace=namespace,
  155. subsystem=subsystem,
  156. unit=unit,
  157. _labelvalues=labelvalues,
  158. **child_kwargs
  159. )
  160. return self._metrics[labelvalues]
  161. def remove(self, *labelvalues: Any) -> None:
  162. if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
  163. warnings.warn(
  164. "Removal of labels has not been implemented in multi-process mode yet.",
  165. UserWarning)
  166. if not self._labelnames:
  167. raise ValueError('No label names were set when constructing %s' % self)
  168. """Remove the given labelset from the metric."""
  169. if len(labelvalues) != len(self._labelnames):
  170. raise ValueError('Incorrect label count (expected %d, got %s)' % (len(self._labelnames), labelvalues))
  171. labelvalues = tuple(str(l) for l in labelvalues)
  172. with self._lock:
  173. if labelvalues in self._metrics:
  174. del self._metrics[labelvalues]
  175. def remove_by_labels(self, labels: dict[str, str]) -> None:
  176. """Remove all series whose labelset partially matches the given labels."""
  177. if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
  178. warnings.warn(
  179. "Removal of labels has not been implemented in multi-process mode yet.",
  180. UserWarning
  181. )
  182. if not self._labelnames:
  183. raise ValueError('No label names were set when constructing %s' % self)
  184. if not isinstance(labels, dict):
  185. raise TypeError("labels must be a dict of {label_name: label_value}")
  186. if not labels:
  187. return # no operation
  188. invalid = [k for k in labels.keys() if k not in self._labelnames]
  189. if invalid:
  190. raise ValueError(
  191. 'Unknown label names: %s; expected %s' % (invalid, self._labelnames)
  192. )
  193. pos_filter = {self._labelnames.index(k): str(v) for k, v in labels.items()}
  194. with self._lock:
  195. # list(...) to avoid "dictionary changed size during iteration"
  196. for lv in list(self._metrics.keys()):
  197. if all(lv[pos] == want for pos, want in pos_filter.items()):
  198. # pop with default avoids KeyError if concurrently removed
  199. self._metrics.pop(lv, None)
  200. def clear(self) -> None:
  201. """Remove all labelsets from the metric"""
  202. if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
  203. warnings.warn(
  204. "Clearing labels has not been implemented in multi-process mode yet",
  205. UserWarning)
  206. with self._lock:
  207. self._metrics = {}
  208. def _samples(self) -> Iterable[Sample]:
  209. if self._is_parent():
  210. return self._multi_samples()
  211. else:
  212. return self._child_samples()
  213. def _multi_samples(self) -> Iterable[Sample]:
  214. with self._lock:
  215. metrics = self._metrics.copy()
  216. for labels, metric in metrics.items():
  217. series_labels = list(zip(self._labelnames, labels))
  218. for suffix, sample_labels, value, timestamp, exemplar, native_histogram_value in metric._samples():
  219. yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar, native_histogram_value)
  220. def _child_samples(self) -> Iterable[Sample]: # pragma: no cover
  221. raise NotImplementedError('_child_samples() must be implemented by %r' % self)
  222. def _metric_init(self): # pragma: no cover
  223. """
  224. Initialize the metric object as a child, i.e. when it has labels (if any) set.
  225. This is factored as a separate function to allow for deferred initialization.
  226. """
  227. raise NotImplementedError('_metric_init() must be implemented by %r' % self)
  228. class Counter(MetricWrapperBase):
  229. """A Counter tracks counts of events or running totals.
  230. Example use cases for Counters:
  231. - Number of requests processed
  232. - Number of items that were inserted into a queue
  233. - Total amount of data that a system has processed
  234. Counters can only go up (and be reset when the process restarts). If your use case can go down,
  235. you should use a Gauge instead.
  236. An example for a Counter:
  237. from prometheus_client import Counter
  238. c = Counter('my_failures_total', 'Description of counter')
  239. c.inc() # Increment by 1
  240. c.inc(1.6) # Increment by given value
  241. There are utilities to count exceptions raised:
  242. @c.count_exceptions()
  243. def f():
  244. pass
  245. with c.count_exceptions():
  246. pass
  247. # Count only one type of exception
  248. with c.count_exceptions(ValueError):
  249. pass
  250. You can also reset the counter to zero in case your logical "process" restarts
  251. without restarting the actual python process.
  252. c.reset()
  253. """
  254. _type = 'counter'
  255. def _metric_init(self) -> None:
  256. self._value = values.ValueClass(self._type, self._name, self._name + '_total', self._labelnames,
  257. self._labelvalues, self._documentation)
  258. self._created = time.time()
  259. def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None:
  260. """Increment counter by the given amount."""
  261. self._raise_if_not_observable()
  262. if amount < 0:
  263. raise ValueError('Counters can only be incremented by non-negative amounts.')
  264. self._value.inc(amount)
  265. if exemplar:
  266. _validate_exemplar(exemplar)
  267. self._value.set_exemplar(Exemplar(exemplar, amount, time.time()))
  268. def reset(self) -> None:
  269. """Reset the counter to zero. Use this when a logical process restarts without restarting the actual python process."""
  270. self._value.set(0)
  271. self._created = time.time()
  272. def count_exceptions(self, exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = Exception) -> ExceptionCounter:
  273. """Count exceptions in a block of code or function.
  274. Can be used as a function decorator or context manager.
  275. Increments the counter when an exception of the given
  276. type is raised up out of the code.
  277. """
  278. self._raise_if_not_observable()
  279. return ExceptionCounter(self, exception)
  280. def _child_samples(self) -> Iterable[Sample]:
  281. sample = Sample('_total', {}, self._value.get(), None, self._value.get_exemplar())
  282. if _use_created:
  283. return (
  284. sample,
  285. Sample('_created', {}, self._created, None, None)
  286. )
  287. return (sample,)
  288. class Gauge(MetricWrapperBase):
  289. """Gauge metric, to report instantaneous values.
  290. Examples of Gauges include:
  291. - Inprogress requests
  292. - Number of items in a queue
  293. - Free memory
  294. - Total memory
  295. - Temperature
  296. Gauges can go both up and down.
  297. from prometheus_client import Gauge
  298. g = Gauge('my_inprogress_requests', 'Description of gauge')
  299. g.inc() # Increment by 1
  300. g.dec(10) # Decrement by given value
  301. g.set(4.2) # Set to a given value
  302. There are utilities for common use cases:
  303. g.set_to_current_time() # Set to current unixtime
  304. # Increment when entered, decrement when exited.
  305. @g.track_inprogress()
  306. def f():
  307. pass
  308. with g.track_inprogress():
  309. pass
  310. A Gauge can also take its value from a callback:
  311. d = Gauge('data_objects', 'Number of objects')
  312. my_dict = {}
  313. d.set_function(lambda: len(my_dict))
  314. """
  315. _type = 'gauge'
  316. _MULTIPROC_MODES = frozenset(('all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent'))
  317. _MOST_RECENT_MODES = frozenset(('mostrecent', 'livemostrecent'))
  318. def __init__(self,
  319. name: str,
  320. documentation: str,
  321. labelnames: Iterable[str] = (),
  322. namespace: str = '',
  323. subsystem: str = '',
  324. unit: str = '',
  325. registry: Optional[CollectorRegistry] = REGISTRY,
  326. _labelvalues: Optional[Sequence[str]] = None,
  327. multiprocess_mode: Literal['all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent'] = 'all',
  328. ):
  329. self._multiprocess_mode = multiprocess_mode
  330. if multiprocess_mode not in self._MULTIPROC_MODES:
  331. raise ValueError('Invalid multiprocess mode: ' + multiprocess_mode)
  332. super().__init__(
  333. name=name,
  334. documentation=documentation,
  335. labelnames=labelnames,
  336. namespace=namespace,
  337. subsystem=subsystem,
  338. unit=unit,
  339. registry=registry,
  340. _labelvalues=_labelvalues,
  341. )
  342. self._kwargs['multiprocess_mode'] = self._multiprocess_mode
  343. self._is_most_recent = self._multiprocess_mode in self._MOST_RECENT_MODES
  344. def _metric_init(self) -> None:
  345. self._value = values.ValueClass(
  346. self._type, self._name, self._name, self._labelnames, self._labelvalues,
  347. self._documentation, multiprocess_mode=self._multiprocess_mode
  348. )
  349. def inc(self, amount: float = 1) -> None:
  350. """Increment gauge by the given amount."""
  351. if self._is_most_recent:
  352. raise RuntimeError("inc must not be used with the mostrecent mode")
  353. self._raise_if_not_observable()
  354. self._value.inc(amount)
  355. def dec(self, amount: float = 1) -> None:
  356. """Decrement gauge by the given amount."""
  357. if self._is_most_recent:
  358. raise RuntimeError("dec must not be used with the mostrecent mode")
  359. self._raise_if_not_observable()
  360. self._value.inc(-amount)
  361. def set(self, value: float) -> None:
  362. """Set gauge to the given value."""
  363. self._raise_if_not_observable()
  364. if self._is_most_recent:
  365. self._value.set(float(value), timestamp=time.time())
  366. else:
  367. self._value.set(float(value))
  368. def set_to_current_time(self) -> None:
  369. """Set gauge to the current unixtime."""
  370. self.set(time.time())
  371. def track_inprogress(self) -> InprogressTracker:
  372. """Track inprogress blocks of code or functions.
  373. Can be used as a function decorator or context manager.
  374. Increments the gauge when the code is entered,
  375. and decrements when it is exited.
  376. """
  377. self._raise_if_not_observable()
  378. return InprogressTracker(self)
  379. def time(self) -> Timer:
  380. """Time a block of code or function, and set the duration in seconds.
  381. Can be used as a function decorator or context manager.
  382. """
  383. return Timer(self, 'set')
  384. def set_function(self, f: Callable[[], float]) -> None:
  385. """Call the provided function to return the Gauge value.
  386. The function must return a float, and may be called from
  387. multiple threads. All other methods of the Gauge become NOOPs.
  388. """
  389. self._raise_if_not_observable()
  390. def samples(_: Gauge) -> Iterable[Sample]:
  391. return (Sample('', {}, float(f()), None, None),)
  392. self._child_samples = types.MethodType(samples, self) # type: ignore
  393. def _child_samples(self) -> Iterable[Sample]:
  394. return (Sample('', {}, self._value.get(), None, None),)
  395. class Summary(MetricWrapperBase):
  396. """A Summary tracks the size and number of events.
  397. Example use cases for Summaries:
  398. - Response latency
  399. - Request size
  400. Example for a Summary:
  401. from prometheus_client import Summary
  402. s = Summary('request_size_bytes', 'Request size (bytes)')
  403. s.observe(512) # Observe 512 (bytes)
  404. Example for a Summary using time:
  405. from prometheus_client import Summary
  406. REQUEST_TIME = Summary('response_latency_seconds', 'Response latency (seconds)')
  407. @REQUEST_TIME.time()
  408. def create_response(request):
  409. '''A dummy function'''
  410. time.sleep(1)
  411. Example for using the same Summary object as a context manager:
  412. with REQUEST_TIME.time():
  413. pass # Logic to be timed
  414. """
  415. _type = 'summary'
  416. _reserved_labelnames = ['quantile']
  417. def _metric_init(self) -> None:
  418. self._count = values.ValueClass(self._type, self._name, self._name + '_count', self._labelnames,
  419. self._labelvalues, self._documentation)
  420. self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation)
  421. self._created = time.time()
  422. def observe(self, amount: float) -> None:
  423. """Observe the given amount.
  424. The amount is usually positive or zero. Negative values are
  425. accepted but prevent current versions of Prometheus from
  426. properly detecting counter resets in the sum of
  427. observations. See
  428. https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
  429. for details.
  430. """
  431. self._raise_if_not_observable()
  432. self._count.inc(1)
  433. self._sum.inc(amount)
  434. def time(self) -> Timer:
  435. """Time a block of code or function, and observe the duration in seconds.
  436. Can be used as a function decorator or context manager.
  437. """
  438. return Timer(self, 'observe')
  439. def _child_samples(self) -> Iterable[Sample]:
  440. samples = [
  441. Sample('_count', {}, self._count.get(), None, None),
  442. Sample('_sum', {}, self._sum.get(), None, None),
  443. ]
  444. if _use_created:
  445. samples.append(Sample('_created', {}, self._created, None, None))
  446. return tuple(samples)
  447. class Histogram(MetricWrapperBase):
  448. """A Histogram tracks the size and number of events in buckets.
  449. You can use Histograms for aggregatable calculation of quantiles.
  450. Example use cases:
  451. - Response latency
  452. - Request size
  453. Example for a Histogram:
  454. from prometheus_client import Histogram
  455. h = Histogram('request_size_bytes', 'Request size (bytes)')
  456. h.observe(512) # Observe 512 (bytes)
  457. Example for a Histogram using time:
  458. from prometheus_client import Histogram
  459. REQUEST_TIME = Histogram('response_latency_seconds', 'Response latency (seconds)')
  460. @REQUEST_TIME.time()
  461. def create_response(request):
  462. '''A dummy function'''
  463. time.sleep(1)
  464. Example of using the same Histogram object as a context manager:
  465. with REQUEST_TIME.time():
  466. pass # Logic to be timed
  467. The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
  468. They can be overridden by passing `buckets` keyword argument to `Histogram`.
  469. """
  470. _type = 'histogram'
  471. _reserved_labelnames = ['le']
  472. DEFAULT_BUCKETS = (.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, INF)
  473. def __init__(self,
  474. name: str,
  475. documentation: str,
  476. labelnames: Iterable[str] = (),
  477. namespace: str = '',
  478. subsystem: str = '',
  479. unit: str = '',
  480. registry: Optional[CollectorRegistry] = REGISTRY,
  481. _labelvalues: Optional[Sequence[str]] = None,
  482. buckets: Sequence[Union[float, str]] = DEFAULT_BUCKETS,
  483. ):
  484. self._prepare_buckets(buckets)
  485. super().__init__(
  486. name=name,
  487. documentation=documentation,
  488. labelnames=labelnames,
  489. namespace=namespace,
  490. subsystem=subsystem,
  491. unit=unit,
  492. registry=registry,
  493. _labelvalues=_labelvalues,
  494. )
  495. self._kwargs['buckets'] = buckets
  496. def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
  497. buckets = [float(b) for b in source_buckets]
  498. if buckets != sorted(buckets):
  499. # This is probably an error on the part of the user,
  500. # so raise rather than sorting for them.
  501. raise ValueError('Buckets not in sorted order')
  502. if buckets and buckets[-1] != INF:
  503. buckets.append(INF)
  504. if len(buckets) < 2:
  505. raise ValueError('Must have at least two buckets')
  506. self._upper_bounds = buckets
  507. def _metric_init(self) -> None:
  508. self._buckets: List[values.ValueClass] = []
  509. self._created = time.time()
  510. bucket_labelnames = self._labelnames + ('le',)
  511. self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation)
  512. for b in self._upper_bounds:
  513. self._buckets.append(values.ValueClass(
  514. self._type,
  515. self._name,
  516. self._name + '_bucket',
  517. bucket_labelnames,
  518. self._labelvalues + (floatToGoString(b),),
  519. self._documentation)
  520. )
  521. def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> None:
  522. """Observe the given amount.
  523. The amount is usually positive or zero. Negative values are
  524. accepted but prevent current versions of Prometheus from
  525. properly detecting counter resets in the sum of
  526. observations. See
  527. https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
  528. for details.
  529. """
  530. self._raise_if_not_observable()
  531. self._sum.inc(amount)
  532. for i, bound in enumerate(self._upper_bounds):
  533. if amount <= bound:
  534. self._buckets[i].inc(1)
  535. if exemplar:
  536. _validate_exemplar(exemplar)
  537. self._buckets[i].set_exemplar(Exemplar(exemplar, amount, time.time()))
  538. break
  539. def time(self) -> Timer:
  540. """Time a block of code or function, and observe the duration in seconds.
  541. Can be used as a function decorator or context manager.
  542. """
  543. return Timer(self, 'observe')
  544. def _child_samples(self) -> Iterable[Sample]:
  545. samples = []
  546. acc = 0.0
  547. for i, bound in enumerate(self._upper_bounds):
  548. acc += self._buckets[i].get()
  549. samples.append(Sample('_bucket', {'le': floatToGoString(bound)}, acc, None, self._buckets[i].get_exemplar()))
  550. samples.append(Sample('_count', {}, acc, None, None))
  551. if self._upper_bounds[0] >= 0:
  552. samples.append(Sample('_sum', {}, self._sum.get(), None, None))
  553. if _use_created:
  554. samples.append(Sample('_created', {}, self._created, None, None))
  555. return tuple(samples)
  556. class Info(MetricWrapperBase):
  557. """Info metric, key-value pairs.
  558. Examples of Info include:
  559. - Build information
  560. - Version information
  561. - Potential target metadata
  562. Example usage:
  563. from prometheus_client import Info
  564. i = Info('my_build', 'Description of info')
  565. i.info({'version': '1.2.3', 'buildhost': 'foo@bar'})
  566. Info metrics do not work in multiprocess mode.
  567. """
  568. _type = 'info'
  569. def _metric_init(self):
  570. self._labelname_set = set(self._labelnames)
  571. self._lock = Lock()
  572. self._value = {}
  573. def info(self, val: Dict[str, str]) -> None:
  574. """Set info metric."""
  575. if self._labelname_set.intersection(val.keys()):
  576. raise ValueError('Overlapping labels for Info metric, metric: {} child: {}'.format(
  577. self._labelnames, val))
  578. if any(i is None for i in val.values()):
  579. raise ValueError('Label value cannot be None')
  580. with self._lock:
  581. self._value = dict(val)
  582. def _child_samples(self) -> Iterable[Sample]:
  583. with self._lock:
  584. return (Sample('_info', self._value, 1.0, None, None),)
  585. class Enum(MetricWrapperBase):
  586. """Enum metric, which of a set of states is true.
  587. Example usage:
  588. from prometheus_client import Enum
  589. e = Enum('task_state', 'Description of enum',
  590. states=['starting', 'running', 'stopped'])
  591. e.state('running')
  592. The first listed state will be the default.
  593. Enum metrics do not work in multiprocess mode.
  594. """
  595. _type = 'stateset'
  596. def __init__(self,
  597. name: str,
  598. documentation: str,
  599. labelnames: Sequence[str] = (),
  600. namespace: str = '',
  601. subsystem: str = '',
  602. unit: str = '',
  603. registry: Optional[CollectorRegistry] = REGISTRY,
  604. _labelvalues: Optional[Sequence[str]] = None,
  605. states: Optional[Sequence[str]] = None,
  606. ):
  607. super().__init__(
  608. name=name,
  609. documentation=documentation,
  610. labelnames=labelnames,
  611. namespace=namespace,
  612. subsystem=subsystem,
  613. unit=unit,
  614. registry=registry,
  615. _labelvalues=_labelvalues,
  616. )
  617. if name in labelnames:
  618. raise ValueError(f'Overlapping labels for Enum metric: {name}')
  619. if not states:
  620. raise ValueError(f'No states provided for Enum metric: {name}')
  621. self._kwargs['states'] = self._states = states
  622. def _metric_init(self) -> None:
  623. self._value = 0
  624. self._lock = Lock()
  625. def state(self, state: str) -> None:
  626. """Set enum metric state."""
  627. self._raise_if_not_observable()
  628. with self._lock:
  629. self._value = self._states.index(state)
  630. def _child_samples(self) -> Iterable[Sample]:
  631. with self._lock:
  632. return [
  633. Sample('', {self._name: s}, 1 if i == self._value else 0, None, None)
  634. for i, s
  635. in enumerate(self._states)
  636. ]