Skip to content

Commit

Permalink
Merge pull request #16 from faucetsdn/v0.21.0
Browse files Browse the repository at this point in the history
Upgrade python3-prometheus-client to v0.21.0.
  • Loading branch information
gizmoguy authored Oct 9, 2024
2 parents eb6c724 + b445cad commit 4ab33db
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 23 deletions.
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

0 comments on commit 4ab33db

Please sign in to comment.