Skip to content

Commit dc7e54e

Browse files
authored
Merge pull request #13 from faucetsdn/v0.18.0
Upgrade python3-prometheus-client to v0.18.0.
2 parents 96c247d + 4ed6519 commit dc7e54e

18 files changed

Lines changed: 172 additions & 76 deletions

.circleci/config.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ workflows:
7575
matrix:
7676
parameters:
7777
python:
78-
- "3.6"
79-
- "3.7"
8078
- "3.8"
8179
- "3.9"
8280
- "3.10"
8381
- "3.11"
82+
- "3.12"
8483
- test_nooptionals:
8584
matrix:
8685
parameters:
@@ -90,4 +89,4 @@ workflows:
9089
matrix:
9190
parameters:
9291
python:
93-
- "3.7"
92+
- "3.8"

README.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,20 @@ h = Histogram('request_latency_seconds', 'Description of histogram')
252252
h.observe(4.7, {'trace_id': 'abc123'})
253253
```
254254

255+
Exemplars are only rendered in the OpenMetrics exposition format. If using the
256+
HTTP server or apps in this library, content negotiation can be used to specify
257+
OpenMetrics (which is done by default in Prometheus). Otherwise it will be
258+
necessary to use `generate_latest` from
259+
`prometheus_client.openmetrics.exposition` to view exemplars.
260+
261+
To view exemplars in Prometheus it is also necessary to enable the the
262+
exemplar-storage feature flag:
263+
```
264+
--enable-feature=exemplar-storage
265+
```
266+
Additional information is available in [the Prometheus
267+
documentation](https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage).
268+
255269
### Disabling `_created` metrics
256270

257271
By default counters, histograms, and summaries export an additional series
@@ -590,8 +604,9 @@ To do so you need to create a custom collector, for example:
590604

591605
```python
592606
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
607+
from prometheus_client.registry import Collector
593608

594-
class CustomCollector(object):
609+
class CustomCollector(Collector):
595610
def collect(self):
596611
yield GaugeMetricFamily('my_gauge', 'Help text', value=7)
597612
c = CounterMetricFamily('my_counter_total', 'Help text', labels=['foo'])
@@ -696,9 +711,10 @@ Gauges have several modes they can run in, which can be selected with the `multi
696711
- 'min': Return a single timeseries that is the minimum of the values of all processes (alive or dead).
697712
- 'max': Return a single timeseries that is the maximum of the values of all processes (alive or dead).
698713
- 'sum': Return a single timeseries that is the sum of the values of all processes (alive or dead).
714+
- 'mostrecent': Return a single timeseries that is the most recent value among all processes (alive or dead).
699715

700716
Prepend 'live' to the beginning of the mode to return the same result but only considering living processes
701-
(e.g., 'liveall, 'livesum', 'livemax', 'livemin').
717+
(e.g., 'liveall, 'livesum', 'livemax', 'livemin', 'livemostrecent').
702718

703719
```python
704720
from prometheus_client import Gauge
@@ -722,6 +738,34 @@ for family in text_string_to_metric_families(u"my_gauge 1.0\n"):
722738
print("Name: {0} Labels: {1} Value: {2}".format(*sample))
723739
```
724740

741+
## Restricted registry
742+
743+
Registries support restriction to only return specific metrics.
744+
If you’re using the built-in HTTP server, you can use the GET parameter "name[]", since it’s an array it can be used multiple times.
745+
If you’re directly using `generate_latest`, you can use the function `restricted_registry()`.
746+
747+
```python
748+
curl --get --data-urlencode "name[]=python_gc_objects_collected_total" --data-urlencode "name[]=python_info" http://127.0.0.1:9200/metrics
749+
```
750+
751+
```python
752+
from prometheus_client import generate_latest
753+
754+
generate_latest(REGISTRY.restricted_registry(['python_gc_objects_collected_total', 'python_info']))
755+
```
756+
757+
```python
758+
# HELP python_info Python platform information
759+
# TYPE python_info gauge
760+
python_info{implementation="CPython",major="3",minor="9",patchlevel="3",version="3.9.3"} 1.0
761+
# HELP python_gc_objects_collected_total Objects collected during gc
762+
# TYPE python_gc_objects_collected_total counter
763+
python_gc_objects_collected_total{generation="0"} 73129.0
764+
python_gc_objects_collected_total{generation="1"} 8594.0
765+
python_gc_objects_collected_total{generation="2"} 296.0
766+
```
767+
768+
725769
## Links
726770

727771
* [Releases](https://github.com/prometheus/client_python/releases): The releases page shows the history of the project and acts as a changelog.

debian/patches/0001-import-unvendorized-decorator.patch

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Index: python3-prometheus-client/prometheus_client/context_managers.py
99
===================================================================
1010
--- python3-prometheus-client.orig/prometheus_client/context_managers.py
1111
+++ python3-prometheus-client/prometheus_client/context_managers.py
12-
@@ -6,7 +6,7 @@ from typing import Any, Callable, Option
13-
if sys.version_info >= (3, 8, 0):
14-
from typing import Literal
12+
@@ -5,7 +5,7 @@ from typing import (
13+
Union,
14+
)
1515

1616
-from .decorator import decorate
1717
+from decorator import decorate

prometheus_client/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/python
1+
#!/usr/bin/env python
22

33
from . import (
44
exposition, gc_collector, metrics, metrics_core, platform_collector,
@@ -11,7 +11,10 @@
1111
write_to_textfile,
1212
)
1313
from .gc_collector import GC_COLLECTOR, GCCollector
14-
from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary
14+
from .metrics import (
15+
Counter, disable_created_metrics, enable_created_metrics, Enum, Gauge,
16+
Histogram, Info, Summary,
17+
)
1518
from .metrics_core import Metric
1619
from .platform_collector import PLATFORM_COLLECTOR, PlatformCollector
1720
from .process_collector import PROCESS_COLLECTOR, ProcessCollector
@@ -27,6 +30,8 @@
2730
'Histogram',
2831
'Info',
2932
'Enum',
33+
'enable_created_metrics',
34+
'disable_created_metrics',
3035
'CONTENT_TYPE_LATEST',
3136
'generate_latest',
3237
'MetricsHandler',

prometheus_client/context_managers.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import sys
21
from timeit import default_timer
32
from types import TracebackType
43
from typing import (
5-
Any, Callable, Optional, Tuple, Type, TYPE_CHECKING, TypeVar, Union,
4+
Any, Callable, Literal, Optional, Tuple, Type, TYPE_CHECKING, TypeVar,
5+
Union,
66
)
77

8-
if sys.version_info >= (3, 8, 0):
9-
from typing import Literal
10-
118
from .decorator import decorate
129

1310
if TYPE_CHECKING:
@@ -23,7 +20,7 @@ def __init__(self, counter: "Counter", exception: Union[Type[BaseException], Tup
2320
def __enter__(self) -> None:
2421
pass
2522

26-
def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> "Literal[False]":
23+
def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> Literal[False]:
2724
if isinstance(value, self._exception):
2825
self._counter.inc()
2926
return False

prometheus_client/exposition.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838

3939
CONTENT_TYPE_LATEST = 'text/plain; version=0.0.4; charset=utf-8'
4040
"""Content type of the latest text format"""
41-
PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)
4241

4342

4443
class _PrometheusRedirectHandler(HTTPRedirectHandler):
@@ -545,10 +544,7 @@ def _use_gateway(
545544
) -> None:
546545
gateway_url = urlparse(gateway)
547546
# See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
548-
if not gateway_url.scheme or (
549-
PYTHON376_OR_NEWER
550-
and gateway_url.scheme not in ['http', 'https']
551-
):
547+
if not gateway_url.scheme or gateway_url.scheme not in ['http', 'https']:
552548
gateway = f'http://{gateway}'
553549

554550
gateway = gateway.rstrip('/')

prometheus_client/metrics.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import time
44
import types
55
from typing import (
6-
Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type,
7-
TypeVar, Union,
6+
Any, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Tuple,
7+
Type, TypeVar, Union,
88
)
99

1010
from . import values # retain this import style for testability
@@ -70,6 +70,18 @@ def _get_use_created() -> bool:
7070
_use_created = _get_use_created()
7171

7272

73+
def disable_created_metrics():
74+
"""Disable exporting _created metrics on counters, histograms, and summaries."""
75+
global _use_created
76+
_use_created = False
77+
78+
79+
def enable_created_metrics():
80+
"""Enable exporting _created metrics on counters, histograms, and summaries."""
81+
global _use_created
82+
_use_created = True
83+
84+
7385
class MetricWrapperBase(Collector):
7486
_type: Optional[str] = None
7587
_reserved_labelnames: Sequence[str] = ()
@@ -346,7 +358,8 @@ def f():
346358
d.set_function(lambda: len(my_dict))
347359
"""
348360
_type = 'gauge'
349-
_MULTIPROC_MODES = frozenset(('all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum'))
361+
_MULTIPROC_MODES = frozenset(('all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent'))
362+
_MOST_RECENT_MODES = frozenset(('mostrecent', 'livemostrecent'))
350363

351364
def __init__(self,
352365
name: str,
@@ -357,7 +370,7 @@ def __init__(self,
357370
unit: str = '',
358371
registry: Optional[CollectorRegistry] = REGISTRY,
359372
_labelvalues: Optional[Sequence[str]] = None,
360-
multiprocess_mode: str = 'all',
373+
multiprocess_mode: Literal['all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent'] = 'all',
361374
):
362375
self._multiprocess_mode = multiprocess_mode
363376
if multiprocess_mode not in self._MULTIPROC_MODES:
@@ -373,6 +386,7 @@ def __init__(self,
373386
_labelvalues=_labelvalues,
374387
)
375388
self._kwargs['multiprocess_mode'] = self._multiprocess_mode
389+
self._is_most_recent = self._multiprocess_mode in self._MOST_RECENT_MODES
376390

377391
def _metric_init(self) -> None:
378392
self._value = values.ValueClass(
@@ -382,18 +396,25 @@ def _metric_init(self) -> None:
382396

383397
def inc(self, amount: float = 1) -> None:
384398
"""Increment gauge by the given amount."""
399+
if self._is_most_recent:
400+
raise RuntimeError("inc must not be used with the mostrecent mode")
385401
self._raise_if_not_observable()
386402
self._value.inc(amount)
387403

388404
def dec(self, amount: float = 1) -> None:
389405
"""Decrement gauge by the given amount."""
406+
if self._is_most_recent:
407+
raise RuntimeError("dec must not be used with the mostrecent mode")
390408
self._raise_if_not_observable()
391409
self._value.inc(-amount)
392410

393411
def set(self, value: float) -> None:
394412
"""Set gauge to the given value."""
395413
self._raise_if_not_observable()
396-
self._value.set(float(value))
414+
if self._is_most_recent:
415+
self._value.set(float(value), timestamp=time.time())
416+
else:
417+
self._value.set(float(value))
397418

398419
def set_to_current_time(self) -> None:
399420
"""Set gauge to the current unixtime."""

prometheus_client/mmap_dict.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,26 @@
66

77
_INITIAL_MMAP_SIZE = 1 << 16
88
_pack_integer_func = struct.Struct(b'i').pack
9-
_pack_double_func = struct.Struct(b'd').pack
9+
_pack_two_doubles_func = struct.Struct(b'dd').pack
1010
_unpack_integer = struct.Struct(b'i').unpack_from
11-
_unpack_double = struct.Struct(b'd').unpack_from
11+
_unpack_two_doubles = struct.Struct(b'dd').unpack_from
1212

1313

1414
# struct.pack_into has atomicity issues because it will temporarily write 0 into
1515
# the mmap, resulting in false reads to 0 when experiencing a lot of writes.
1616
# Using direct assignment solves this issue.
1717

18-
def _pack_double(data, pos, value):
19-
data[pos:pos + 8] = _pack_double_func(value)
18+
19+
def _pack_two_doubles(data, pos, value, timestamp):
20+
data[pos:pos + 16] = _pack_two_doubles_func(value, timestamp)
2021

2122

2223
def _pack_integer(data, pos, value):
2324
data[pos:pos + 4] = _pack_integer_func(value)
2425

2526

2627
def _read_all_values(data, used=0):
27-
"""Yield (key, value, pos). No locking is performed."""
28+
"""Yield (key, value, timestamp, pos). No locking is performed."""
2829

2930
if used <= 0:
3031
# If not valid `used` value is passed in, read it from the file.
@@ -41,9 +42,9 @@ def _read_all_values(data, used=0):
4142
encoded_key = data[pos:pos + encoded_len]
4243
padded_len = encoded_len + (8 - (encoded_len + 4) % 8)
4344
pos += padded_len
44-
value = _unpack_double(data, pos)[0]
45-
yield encoded_key.decode('utf-8'), value, pos
46-
pos += 8
45+
value, timestamp = _unpack_two_doubles(data, pos)
46+
yield encoded_key.decode('utf-8'), value, timestamp, pos
47+
pos += 16
4748

4849

4950
class MmapedDict:
@@ -53,7 +54,8 @@ class MmapedDict:
5354
Then 4 bytes of padding.
5455
There's then a number of entries, consisting of a 4 byte int which is the
5556
size of the next field, a utf-8 encoded string key, padding to a 8 byte
56-
alignment, and then a 8 byte float which is the value.
57+
alignment, and then a 8 byte float which is the value and a 8 byte float
58+
which is a UNIX timestamp in seconds.
5759
5860
Not thread safe.
5961
"""
@@ -76,7 +78,7 @@ def __init__(self, filename, read_mode=False):
7678
_pack_integer(self._m, 0, self._used)
7779
else:
7880
if not read_mode:
79-
for key, _, pos in self._read_all_values():
81+
for key, _, _, pos in self._read_all_values():
8082
self._positions[key] = pos
8183

8284
@staticmethod
@@ -95,7 +97,7 @@ def _init_value(self, key):
9597
encoded = key.encode('utf-8')
9698
# Pad to be 8-byte aligned.
9799
padded = encoded + (b' ' * (8 - (len(encoded) + 4) % 8))
98-
value = struct.pack(f'i{len(padded)}sd'.encode(), len(encoded), padded, 0.0)
100+
value = struct.pack(f'i{len(padded)}sdd'.encode(), len(encoded), padded, 0.0, 0.0)
99101
while self._used + len(value) > self._capacity:
100102
self._capacity *= 2
101103
self._f.truncate(self._capacity)
@@ -105,30 +107,28 @@ def _init_value(self, key):
105107
# Update how much space we've used.
106108
self._used += len(value)
107109
_pack_integer(self._m, 0, self._used)
108-
self._positions[key] = self._used - 8
110+
self._positions[key] = self._used - 16
109111

110112
def _read_all_values(self):
111113
"""Yield (key, value, pos). No locking is performed."""
112114
return _read_all_values(data=self._m, used=self._used)
113115

114116
def read_all_values(self):
115-
"""Yield (key, value). No locking is performed."""
116-
for k, v, _ in self._read_all_values():
117-
yield k, v
117+
"""Yield (key, value, timestamp). No locking is performed."""
118+
for k, v, ts, _ in self._read_all_values():
119+
yield k, v, ts
118120

119121
def read_value(self, key):
120122
if key not in self._positions:
121123
self._init_value(key)
122124
pos = self._positions[key]
123-
# We assume that reading from an 8 byte aligned value is atomic
124-
return _unpack_double(self._m, pos)[0]
125+
return _unpack_two_doubles(self._m, pos)
125126

126-
def write_value(self, key, value):
127+
def write_value(self, key, value, timestamp):
127128
if key not in self._positions:
128129
self._init_value(key)
129130
pos = self._positions[key]
130-
# We assume that writing to an 8 byte aligned value is atomic
131-
_pack_double(self._m, pos, value)
131+
_pack_two_doubles(self._m, pos, value, timestamp)
132132

133133
def close(self):
134134
if self._f:

0 commit comments

Comments
 (0)