Skip to content

Commit

Permalink
Merge pull request #9 from faucetsdn/v0.16.0
Browse files Browse the repository at this point in the history
Upgrade python3-prometheus-client to v0.16.0.
  • Loading branch information
gizmoguy authored Feb 1, 2023
2 parents 3d9d97c + 02806fd commit a024d98
Show file tree
Hide file tree
Showing 17 changed files with 105 additions and 44 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
python:
type: string
docker:
- image: circleci/python:<< parameters.python >>
- image: cimg/python:<< parameters.python >>
environment:
TOXENV: "py<< parameters.python >>"
steps:
Expand Down Expand Up @@ -80,6 +80,7 @@ workflows:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- test_nooptionals:
matrix:
parameters:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ TLS Auth is also supported when using the push gateway with a special handler.

```python
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
from prometheus_client.exposition import tls_handler
from prometheus_client.exposition import tls_auth_handler


def my_auth_handler(url, method, timeout, headers, data):
Expand Down
2 changes: 1 addition & 1 deletion prometheus_client/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async def prometheus_app(scope, receive, send):
assert scope.get("type") == "http"
# Prepare parameters
params = parse_qs(scope.get('query_string', b''))
accept_header = "Accept: " + ",".join([
accept_header = ",".join([
value.decode("utf8") for (name, value) in scope.get('headers')
if name.decode("utf8").lower() == 'accept'
])
Expand Down
6 changes: 4 additions & 2 deletions prometheus_client/context_managers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import sys
from timeit import default_timer
from types import TracebackType
from typing import Any, Callable, Optional, Type, TYPE_CHECKING, TypeVar
from typing import (
Any, Callable, Optional, Tuple, Type, TYPE_CHECKING, TypeVar, Union,
)

if sys.version_info >= (3, 8, 0):
from typing import Literal
Expand All @@ -14,7 +16,7 @@


class ExceptionCounter:
def __init__(self, counter: "Counter", exception: Type[BaseException]) -> None:
def __init__(self, counter: "Counter", exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]]) -> None:
self._counter = counter
self._exception = exception

Expand Down
4 changes: 2 additions & 2 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ def basic_auth_handler(
timeout: Optional[float],
headers: List[Tuple[str, str]],
data: bytes,
username: str = None,
password: str = None,
username: Optional[str] = None,
password: Optional[str] = None,
) -> Callable[[], None]:
"""Handler that implements HTTP/HTTPS connections with Basic Auth.
Expand Down
19 changes: 10 additions & 9 deletions prometheus_client/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import time
import types
from typing import (
Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, TypeVar,
Union,
Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type,
TypeVar, Union,
)

from . import values # retain this import style for testability
Expand Down Expand Up @@ -275,7 +275,7 @@ def f():

def _metric_init(self) -> None:
self._value = values.ValueClass(self._type, self._name, self._name + '_total', self._labelnames,
self._labelvalues)
self._labelvalues, self._documentation)
self._created = time.time()

def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None:
Expand All @@ -288,7 +288,7 @@ def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> N
_validate_exemplar(exemplar)
self._value.set_exemplar(Exemplar(exemplar, amount, time.time()))

def count_exceptions(self, exception: Type[BaseException] = Exception) -> ExceptionCounter:
def count_exceptions(self, exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = Exception) -> ExceptionCounter:
"""Count exceptions in a block of code or function.
Can be used as a function decorator or context manager.
Expand Down Expand Up @@ -377,7 +377,7 @@ def __init__(self,
def _metric_init(self) -> None:
self._value = values.ValueClass(
self._type, self._name, self._name, self._labelnames, self._labelvalues,
multiprocess_mode=self._multiprocess_mode
self._documentation, multiprocess_mode=self._multiprocess_mode
)

def inc(self, amount: float = 1) -> None:
Expand Down Expand Up @@ -469,8 +469,8 @@ def create_response(request):

def _metric_init(self) -> None:
self._count = values.ValueClass(self._type, self._name, self._name + '_count', self._labelnames,
self._labelvalues)
self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues)
self._labelvalues, self._documentation)
self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation)
self._created = time.time()

def observe(self, amount: float) -> None:
Expand Down Expand Up @@ -583,14 +583,15 @@ def _metric_init(self) -> None:
self._buckets: List[values.ValueClass] = []
self._created = time.time()
bucket_labelnames = self._labelnames + ('le',)
self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues)
self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation)
for b in self._upper_bounds:
self._buckets.append(values.ValueClass(
self._type,
self._name,
self._name + '_bucket',
bucket_labelnames,
self._labelvalues + (floatToGoString(b),))
self._labelvalues + (floatToGoString(b),),
self._documentation)
)

def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> None:
Expand Down
2 changes: 1 addition & 1 deletion prometheus_client/metrics_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(self,
name: str,
documentation: str,
value: Optional[float] = None,
labels: Sequence[str] = None,
labels: Optional[Sequence[str]] = None,
created: Optional[float] = None,
unit: str = '',
):
Expand Down
7 changes: 4 additions & 3 deletions prometheus_client/mmap_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import mmap
import os
import struct
from typing import List

_INITIAL_MMAP_SIZE = 1 << 20
_INITIAL_MMAP_SIZE = 1 << 16
_pack_integer_func = struct.Struct(b'i').pack
_pack_double_func = struct.Struct(b'd').pack
_unpack_integer = struct.Struct(b'i').unpack_from
Expand Down Expand Up @@ -137,8 +138,8 @@ def close(self):
self._f = None


def mmap_key(metric_name, name, labelnames, labelvalues):
def mmap_key(metric_name: str, name: str, labelnames: List[str], labelvalues: List[str], help_text: str) -> str:
"""Format a key for use in the mmap file."""
# ensure labels are in consistent order for identity
labels = dict(zip(labelnames, labelvalues))
return json.dumps([metric_name, name, labels], sort_keys=True)
return json.dumps([metric_name, name, labels, help_text], sort_keys=True)
10 changes: 4 additions & 6 deletions prometheus_client/multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
except NameError: # Python >= 2.5
FileNotFoundError = IOError

MP_METRIC_HELP = 'Multiprocess metric'


class MultiProcessCollector:
"""Collector for files for multi-process mode."""
Expand Down Expand Up @@ -53,9 +51,9 @@ def _read_metrics(files):
def _parse_key(key):
val = key_cache.get(key)
if not val:
metric_name, name, labels = json.loads(key)
metric_name, name, labels, help_text = json.loads(key)
labels_key = tuple(sorted(labels.items()))
val = key_cache[key] = (metric_name, name, labels, labels_key)
val = key_cache[key] = (metric_name, name, labels, labels_key, help_text)
return val

for f in files:
Expand All @@ -71,11 +69,11 @@ def _parse_key(key):
continue
raise
for key, value, _ in file_values:
metric_name, name, labels, labels_key = _parse_key(key)
metric_name, name, labels, labels_key, help_text = _parse_key(key)

metric = metrics.get(metric_name)
if metric is None:
metric = Metric(metric_name, MP_METRIC_HELP, typ)
metric = Metric(metric_name, help_text, typ)
metrics[metric_name] = metric

if typ == 'gauge':
Expand Down
4 changes: 2 additions & 2 deletions prometheus_client/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ def _parse_sample(text):
name = text[:label_start].strip()
# We ignore the starting curly brace
label = text[label_start + 1:label_end]
# The value is after the label end (ignoring curly brace and space)
value, timestamp = _parse_value_and_timestamp(text[label_end + 2:])
# The value is after the label end (ignoring curly brace)
value, timestamp = _parse_value_and_timestamp(text[label_end + 1:])
return Sample(name, _parse_labels(label), value, timestamp)

# We don't have labels
Expand Down
10 changes: 5 additions & 5 deletions prometheus_client/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class MutexValue:

_multiprocess = False

def __init__(self, typ, metric_name, name, labelnames, labelvalues, **kwargs):
def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs):
self._value = 0.0
self._exemplar = None
self._lock = Lock()
Expand Down Expand Up @@ -57,8 +57,8 @@ class MmapedValue:

_multiprocess = True

def __init__(self, typ, metric_name, name, labelnames, labelvalues, multiprocess_mode='', **kwargs):
self._params = typ, metric_name, name, labelnames, labelvalues, multiprocess_mode
def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode='', **kwargs):
self._params = typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode
# This deprecation warning can go away in a few releases when removing the compatibility
if 'prometheus_multiproc_dir' in os.environ and 'PROMETHEUS_MULTIPROC_DIR' not in os.environ:
os.environ['PROMETHEUS_MULTIPROC_DIR'] = os.environ['prometheus_multiproc_dir']
Expand All @@ -69,7 +69,7 @@ def __init__(self, typ, metric_name, name, labelnames, labelvalues, multiprocess
values.append(self)

def __reset(self):
typ, metric_name, name, labelnames, labelvalues, multiprocess_mode = self._params
typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode = self._params
if typ == 'gauge':
file_prefix = typ + '_' + multiprocess_mode
else:
Expand All @@ -81,7 +81,7 @@ def __reset(self):

files[file_prefix] = MmapedDict(filename)
self._file = files[file_prefix]
self._key = mmap_key(metric_name, name, labelnames, labelvalues)
self._key = mmap_key(metric_name, name, labelnames, labelvalues, help_text)
self._value = self._file.read_value(self._key)

def __check_for_pid_change(self):
Expand Down
3 changes: 2 additions & 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.15.0",
version="0.16.0",
author="Brian Brazil",
author_email="[email protected]",
description="Python client for the Prometheus monitoring system.",
Expand Down Expand Up @@ -43,6 +43,7 @@
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: System :: Monitoring",
Expand Down
2 changes: 2 additions & 0 deletions tests/openmetrics/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,8 @@ def test_invalid_input(self):
('a{a""} 1\n# EOF\n'),
('a{a=} 1\n# EOF\n'),
('a{a="} 1\n# EOF\n'),
# Missing delimiters.
('a{a="1"}1\n# EOF\n'),
# Missing or extra commas.
('a{a="1"b="2"} 1\n# EOF\n'),
('a{a="1",,b="2"} 1\n# EOF\n'),
Expand Down
32 changes: 32 additions & 0 deletions tests/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ def get_all_output(self):
break
return outputs

def get_all_response_headers(self):
outputs = self.get_all_output()
response_start = next(o for o in outputs if o["type"] == "http.response.start")
return response_start["headers"]

def get_response_header_value(self, header_name):
response_headers = self.get_all_response_headers()
return next(
value.decode("utf-8")
for name, value in response_headers
if name.decode("utf-8") == header_name
)

def increment_metrics(self, metric_name, help_text, increments):
c = Counter(metric_name, help_text, registry=self.registry)
for _ in range(increments):
Expand Down Expand Up @@ -158,3 +171,22 @@ def test_gzip_disabled(self):
# Assert outputs are not compressed.
outputs = self.get_all_output()
self.assert_outputs(outputs, metric_name, help_text, increments, compressed=False)

def test_openmetrics_encoding(self):
"""Response content type is application/openmetrics-text when appropriate Accept header is in request"""
app = make_asgi_app(self.registry)
self.seed_app(app)
self.scope["headers"] = [(b"Accept", b"application/openmetrics-text")]
self.send_input({"type": "http.request", "body": b""})

content_type = self.get_response_header_value('Content-Type').split(";")[0]
assert content_type == "application/openmetrics-text"

def test_plaintext_encoding(self):
"""Response content type is text/plain when Accept header is missing in request"""
app = make_asgi_app(self.registry)
self.seed_app(app)
self.send_input({"type": "http.request", "body": b""})

content_type = self.get_response_header_value('Content-Type').split(";")[0]
assert content_type == "text/plain"
25 changes: 25 additions & 0 deletions tests/test_multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,31 @@ def add_label(key, value):

self.assertEqual(metrics['h'].samples, expected_histogram)

def test_collect_preserves_help(self):
pid = 0
values.ValueClass = MultiProcessValue(lambda: pid)
labels = {i: i for i in 'abcd'}

c = Counter('c', 'c help', labelnames=labels.keys(), registry=None)
g = Gauge('g', 'g help', labelnames=labels.keys(), registry=None)
h = Histogram('h', 'h help', labelnames=labels.keys(), registry=None)

c.labels(**labels).inc(1)
g.labels(**labels).set(1)
h.labels(**labels).observe(1)

pid = 1

c.labels(**labels).inc(1)
g.labels(**labels).set(1)
h.labels(**labels).observe(5)

metrics = {m.name: m for m in self.collector.collect()}

self.assertEqual(metrics['c'].documentation, 'c help')
self.assertEqual(metrics['g'].documentation, 'g help')
self.assertEqual(metrics['h'].documentation, 'h help')

def test_merge_no_accumulate(self):
pid = 0
values.ValueClass = MultiProcessValue(lambda: pid)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,15 @@ def test_spaces(self):
a { foo = "buz" } 3
a\t { \t foo\t = "biz"\t } \t 4
a \t{\t foo = "boz"\t}\t 5
a{foo="bez"}6
""")
metric_family = CounterMetricFamily("a", "help", labels=["foo"])
metric_family.add_metric(["bar"], 1)
metric_family.add_metric(["baz"], 2)
metric_family.add_metric(["buz"], 3)
metric_family.add_metric(["biz"], 4)
metric_family.add_metric(["boz"], 5)
metric_family.add_metric(["bez"], 6)
self.assertEqualMetrics([metric_family], list(families))

def test_commas(self):
Expand Down
Loading

0 comments on commit a024d98

Please sign in to comment.