Skip to content

Commit 4ab33db

Browse files
authored
Merge pull request #16 from faucetsdn/v0.21.0
Upgrade python3-prometheus-client to v0.21.0.
2 parents eb6c724 + b445cad commit 4ab33db

File tree

13 files changed

+69
-23
lines changed

13 files changed

+69
-23
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ workflows:
7575
matrix:
7676
parameters:
7777
python:
78-
- "3.8"
79-
- "3.9"
78+
- "3.8.18"
79+
- "3.9.18"
8080
- "3.10"
8181
- "3.11"
8282
- "3.12"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,5 @@ Index: python3-prometheus-client/tests/test_core.py
3838
+ return spec.args, spec.varargs, spec.varkw, spec.defaults
3939
+
4040

41-
def assert_not_observable(fn, *args, **kwargs):
42-
"""
41+
def is_locked(lock):
42+
"Tries to obtain a lock, returns True on success, False on failure."

docs/content/exporting/http/_index.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,23 @@ chain is used (see Python [ssl.SSLContext.load_default_certs()](https://docs.pyt
5252
from prometheus_client import start_http_server
5353

5454
start_http_server(8000, certfile="server.crt", keyfile="server.key")
55-
```
55+
```
56+
57+
# Supported HTTP methods
58+
59+
The prometheus client will handle the following HTTP methods and resources:
60+
61+
* `OPTIONS (any)` - returns HTTP status 200 and an 'Allow' header indicating the
62+
allowed methods (OPTIONS, GET)
63+
* `GET (any)` - returns HTTP status 200 and the metrics data
64+
* `GET /favicon.ico` - returns HTTP status 200 and an empty response body. Some
65+
browsers support this to display the returned icon in the browser tab.
66+
67+
Other HTTP methods than these are rejected with HTTP status 405 "Method Not Allowed"
68+
and an 'Allow' header indicating the allowed methods (OPTIONS, GET).
69+
70+
Any returned HTTP errors are also displayed in the response body after a hash
71+
sign and with a brief hint. Example:
72+
```
73+
# HTTP 405 Method Not Allowed: XXX; use OPTIONS or GET
74+
```

docs/content/exporting/http/asgi.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ app = make_asgi_app()
1414
Such an application can be useful when integrating Prometheus metrics with ASGI
1515
apps.
1616

17-
By default, the WSGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
17+
By default, the ASGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
1818
and compress the response if such a header is present. This behaviour can be disabled by passing
1919
`disable_compression=True` when creating the app, like this:
2020

2121
```python
2222
app = make_asgi_app(disable_compression=True)
23-
```
23+
```

docs/content/exporting/http/fastapi-gunicorn.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ metrics_app = make_asgi_app()
1919
app.mount("/metrics", metrics_app)
2020
```
2121

22-
For Multiprocessing support, use this modified code snippet. Full multiprocessing instructions are provided [here](https://github.com/prometheus/client_python#multiprocess-mode-eg-gunicorn).
22+
For Multiprocessing support, use this modified code snippet. Full multiprocessing instructions are provided [here]({{< ref "/multiprocess" >}}).
2323

2424
```python
2525
from fastapi import FastAPI
@@ -47,4 +47,4 @@ pip install gunicorn
4747
gunicorn -b 127.0.0.1:8000 myapp:app -k uvicorn.workers.UvicornWorker
4848
```
4949

50-
Visit http://localhost:8000/metrics to see the metrics
50+
Visit http://localhost:8000/metrics to see the metrics

prometheus_client/exposition.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,24 @@ def prometheus_app(environ, start_response):
118118
accept_header = environ.get('HTTP_ACCEPT')
119119
accept_encoding_header = environ.get('HTTP_ACCEPT_ENCODING')
120120
params = parse_qs(environ.get('QUERY_STRING', ''))
121-
if environ['PATH_INFO'] == '/favicon.ico':
121+
method = environ['REQUEST_METHOD']
122+
123+
if method == 'OPTIONS':
124+
status = '200 OK'
125+
headers = [('Allow', 'OPTIONS,GET')]
126+
output = b''
127+
elif method != 'GET':
128+
status = '405 Method Not Allowed'
129+
headers = [('Allow', 'OPTIONS,GET')]
130+
output = '# HTTP {}: {}; use OPTIONS or GET\n'.format(status, method).encode()
131+
elif environ['PATH_INFO'] == '/favicon.ico':
122132
# Serve empty response for browsers
123133
status = '200 OK'
124134
headers = [('', '')]
125135
output = b''
126136
else:
137+
# Note: For backwards compatibility, the URI path for GET is not
138+
# constrained to the documented /metrics, but any path is allowed.
127139
# Bake output
128140
status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression)
129141
# Return output

prometheus_client/metrics.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from threading import Lock
2+
from threading import RLock
33
import time
44
import types
55
from typing import (
@@ -144,7 +144,7 @@ def __init__(self: T,
144144

145145
if self._is_parent():
146146
# Prepare the fields needed for child metrics.
147-
self._lock = Lock()
147+
self._lock = RLock()
148148
self._metrics: Dict[Sequence[str], T] = {}
149149

150150
if self._is_observable():
@@ -697,14 +697,16 @@ class Info(MetricWrapperBase):
697697

698698
def _metric_init(self):
699699
self._labelname_set = set(self._labelnames)
700-
self._lock = Lock()
700+
self._lock = RLock()
701701
self._value = {}
702702

703703
def info(self, val: Dict[str, str]) -> None:
704704
"""Set info metric."""
705705
if self._labelname_set.intersection(val.keys()):
706706
raise ValueError('Overlapping labels for Info metric, metric: {} child: {}'.format(
707707
self._labelnames, val))
708+
if any(i is None for i in val.values()):
709+
raise ValueError('Label value cannot be None')
708710
with self._lock:
709711
self._value = dict(val)
710712

@@ -757,7 +759,7 @@ def __init__(self,
757759

758760
def _metric_init(self) -> None:
759761
self._value = 0
760-
self._lock = Lock()
762+
self._lock = RLock()
761763

762764
def state(self, state: str) -> None:
763765
"""Set enum metric state."""

prometheus_client/registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from abc import ABC, abstractmethod
22
import copy
3-
from threading import Lock
3+
from threading import RLock
44
from typing import Dict, Iterable, List, Optional
55

66
from .metrics_core import Metric
@@ -30,7 +30,7 @@ def __init__(self, auto_describe: bool = False, target_info: Optional[Dict[str,
3030
self._collector_to_names: Dict[Collector, List[str]] = {}
3131
self._names_to_collectors: Dict[str, Collector] = {}
3232
self._auto_describe = auto_describe
33-
self._lock = Lock()
33+
self._lock = RLock()
3434
self._target_info: Optional[Dict[str, str]] = {}
3535
self.set_target_info(target_info)
3636

prometheus_client/samples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ def __ne__(self, other: object) -> bool:
2828
return not self == other
2929

3030
def __gt__(self, other: "Timestamp") -> bool:
31-
return self.sec > other.sec or self.nsec > other.nsec
31+
return self.nsec > other.nsec if self.sec == other.sec else self.sec > other.sec
3232

3333
def __lt__(self, other: "Timestamp") -> bool:
34-
return self.sec < other.sec or self.nsec < other.nsec
34+
return self.nsec < other.nsec if self.sec == other.sec else self.sec < other.sec
3535

3636

3737
# Timestamp and exemplar are optional.

prometheus_client/values.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from threading import Lock
2+
from threading import RLock
33
import warnings
44

55
from .mmap_dict import mmap_key, MmapedDict
@@ -13,7 +13,7 @@ class MutexValue:
1313
def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs):
1414
self._value = 0.0
1515
self._exemplar = None
16-
self._lock = Lock()
16+
self._lock = RLock()
1717

1818
def inc(self, amount):
1919
with self._lock:
@@ -50,7 +50,7 @@ def MultiProcessValue(process_identifier=os.getpid):
5050
# Use a single global lock when in multi-processing mode
5151
# as we presume this means there is no threading going on.
5252
# This avoids the need to also have mutexes in __MmapDict.
53-
lock = Lock()
53+
lock = RLock()
5454

5555
class MmapedValue:
5656
"""A float protected by a mutex backed by a per-process mmaped file."""

0 commit comments

Comments
 (0)