Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade python3-prometheus-client to v0.21.0. #16

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ workflows:
matrix:
parameters:
python:
- "3.8"
- "3.9"
- "3.8.18"
- "3.9.18"
- "3.10"
- "3.11"
- "3.12"
Expand Down
4 changes: 2 additions & 2 deletions debian/patches/0001-import-unvendorized-decorator.patch
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ Index: python3-prometheus-client/tests/test_core.py
+ return spec.args, spec.varargs, spec.varkw, spec.defaults
+

def assert_not_observable(fn, *args, **kwargs):
"""
def is_locked(lock):
"Tries to obtain a lock, returns True on success, False on failure."
21 changes: 20 additions & 1 deletion docs/content/exporting/http/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,23 @@ chain is used (see Python [ssl.SSLContext.load_default_certs()](https://docs.pyt
from prometheus_client import start_http_server

start_http_server(8000, certfile="server.crt", keyfile="server.key")
```
```

# Supported HTTP methods

The prometheus client will handle the following HTTP methods and resources:

* `OPTIONS (any)` - returns HTTP status 200 and an 'Allow' header indicating the
allowed methods (OPTIONS, GET)
* `GET (any)` - returns HTTP status 200 and the metrics data
* `GET /favicon.ico` - returns HTTP status 200 and an empty response body. Some
browsers support this to display the returned icon in the browser tab.

Other HTTP methods than these are rejected with HTTP status 405 "Method Not Allowed"
and an 'Allow' header indicating the allowed methods (OPTIONS, GET).

Any returned HTTP errors are also displayed in the response body after a hash
sign and with a brief hint. Example:
```
# HTTP 405 Method Not Allowed: XXX; use OPTIONS or GET
```
4 changes: 2 additions & 2 deletions docs/content/exporting/http/asgi.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ app = make_asgi_app()
Such an application can be useful when integrating Prometheus metrics with ASGI
apps.

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

```python
app = make_asgi_app(disable_compression=True)
```
```
4 changes: 2 additions & 2 deletions docs/content/exporting/http/fastapi-gunicorn.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ metrics_app = make_asgi_app()
app.mount("/metrics", metrics_app)
```

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

```python
from fastapi import FastAPI
Expand Down Expand Up @@ -47,4 +47,4 @@ pip install gunicorn
gunicorn -b 127.0.0.1:8000 myapp:app -k uvicorn.workers.UvicornWorker
```

Visit http://localhost:8000/metrics to see the metrics
Visit http://localhost:8000/metrics to see the metrics
14 changes: 13 additions & 1 deletion prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,24 @@ def prometheus_app(environ, start_response):
accept_header = environ.get('HTTP_ACCEPT')
accept_encoding_header = environ.get('HTTP_ACCEPT_ENCODING')
params = parse_qs(environ.get('QUERY_STRING', ''))
if environ['PATH_INFO'] == '/favicon.ico':
method = environ['REQUEST_METHOD']

if method == 'OPTIONS':
status = '200 OK'
headers = [('Allow', 'OPTIONS,GET')]
output = b''
elif method != 'GET':
status = '405 Method Not Allowed'
headers = [('Allow', 'OPTIONS,GET')]
output = '# HTTP {}: {}; use OPTIONS or GET\n'.format(status, method).encode()
elif environ['PATH_INFO'] == '/favicon.ico':
# Serve empty response for browsers
status = '200 OK'
headers = [('', '')]
output = b''
else:
# Note: For backwards compatibility, the URI path for GET is not
# constrained to the documented /metrics, but any path is allowed.
# Bake output
status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression)
# Return output
Expand Down
10 changes: 6 additions & 4 deletions prometheus_client/metrics.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from threading import Lock
from threading import RLock
import time
import types
from typing import (
Expand Down Expand Up @@ -144,7 +144,7 @@ def __init__(self: T,

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

if self._is_observable():
Expand Down Expand Up @@ -697,14 +697,16 @@ class Info(MetricWrapperBase):

def _metric_init(self):
self._labelname_set = set(self._labelnames)
self._lock = Lock()
self._lock = RLock()
self._value = {}

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

Expand Down Expand Up @@ -757,7 +759,7 @@ def __init__(self,

def _metric_init(self) -> None:
self._value = 0
self._lock = Lock()
self._lock = RLock()

def state(self, state: str) -> None:
"""Set enum metric state."""
Expand Down
4 changes: 2 additions & 2 deletions prometheus_client/registry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import ABC, abstractmethod
import copy
from threading import Lock
from threading import RLock
from typing import Dict, Iterable, List, Optional

from .metrics_core import Metric
Expand Down Expand Up @@ -30,7 +30,7 @@ def __init__(self, auto_describe: bool = False, target_info: Optional[Dict[str,
self._collector_to_names: Dict[Collector, List[str]] = {}
self._names_to_collectors: Dict[str, Collector] = {}
self._auto_describe = auto_describe
self._lock = Lock()
self._lock = RLock()
self._target_info: Optional[Dict[str, str]] = {}
self.set_target_info(target_info)

Expand Down
4 changes: 2 additions & 2 deletions prometheus_client/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ def __ne__(self, other: object) -> bool:
return not self == other

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

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


# Timestamp and exemplar are optional.
Expand Down
6 changes: 3 additions & 3 deletions prometheus_client/values.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from threading import Lock
from threading import RLock
import warnings

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

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

class MmapedValue:
"""A float protected by a mutex backed by a per-process mmaped file."""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="prometheus_client",
version="0.20.0",
version="0.21.0",
author="Brian Brazil",
author_email="[email protected]",
description="Python client for the Prometheus monitoring system.",
Expand Down
11 changes: 10 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
from prometheus_client.metrics import _get_use_created


def is_locked(lock):
"Tries to obtain a lock, returns True on success, False on failure."
locked = lock.acquire(blocking=False)
if locked:
lock.release()
return not locked


def assert_not_observable(fn, *args, **kwargs):
"""
Assert that a function call falls with a ValueError exception containing
Expand Down Expand Up @@ -534,6 +542,7 @@ def test_info(self):

def test_labels(self):
self.assertRaises(ValueError, self.labels.labels('a').info, {'l': ''})
self.assertRaises(ValueError, self.labels.labels('a').info, {'il': None})

self.labels.labels('a').info({'foo': 'bar'})
self.assertEqual(1, self.registry.get_sample_value('il_info', {'l': 'a', 'foo': 'bar'}))
Expand Down Expand Up @@ -962,7 +971,7 @@ def test_restricted_registry_does_not_yield_while_locked(self):
m = Metric('target', 'Target metadata', 'info')
m.samples = [Sample('target_info', {'foo': 'bar'}, 1)]
for _ in registry.restricted_registry(['target_info', 's_sum']).collect():
self.assertFalse(registry._lock.locked())
self.assertFalse(is_locked(registry._lock))


if __name__ == '__main__':
Expand Down
4 changes: 4 additions & 0 deletions tests/test_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def test_gt(self):
self.assertEqual(samples.Timestamp(1, 2) > samples.Timestamp(1, 1), True)
self.assertEqual(samples.Timestamp(2, 1) > samples.Timestamp(1, 1), True)
self.assertEqual(samples.Timestamp(2, 2) > samples.Timestamp(1, 1), True)
self.assertEqual(samples.Timestamp(0, 2) > samples.Timestamp(1, 1), False)
self.assertEqual(samples.Timestamp(2, 0) > samples.Timestamp(1, 1), True)

def test_lt(self):
self.assertEqual(samples.Timestamp(1, 1) < samples.Timestamp(1, 1), False)
Expand All @@ -21,6 +23,8 @@ def test_lt(self):
self.assertEqual(samples.Timestamp(1, 2) < samples.Timestamp(1, 1), False)
self.assertEqual(samples.Timestamp(2, 1) < samples.Timestamp(1, 1), False)
self.assertEqual(samples.Timestamp(2, 2) < samples.Timestamp(1, 1), False)
self.assertEqual(samples.Timestamp(0, 2) < samples.Timestamp(1, 1), True)
self.assertEqual(samples.Timestamp(2, 0) < samples.Timestamp(1, 1), False)


if __name__ == '__main__':
Expand Down