Skip to content

Commit eb7c8b2

Browse files
committed
Vendorized prometheus_client 0.20.0.post1
Signed-off-by: Andreas Maier <maiera@de.ibm.com>
1 parent c322e78 commit eb7c8b2

33 files changed

+4462
-12
lines changed

Makefile

+7-2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ package_dir := $(package_name)
100100
package_py_files := \
101101
$(wildcard $(package_dir)/*.py) \
102102
$(wildcard $(package_dir)/*/*.py) \
103+
$(wildcard $(package_dir)/*/*/*.py) \
104+
$(wildcard $(package_dir)/*/*/*/*.py) \
105+
106+
src_py_files := \
107+
$(wildcard $(package_dir)/*.py) \
103108

104109
test_dir := tests
105110
test_py_files := \
@@ -237,14 +242,14 @@ develop: $(done_dir)/develop_$(pymn)_$(PACKAGE_LEVEL).done
237242
.PHONY: check
238243
check: $(done_dir)/develop_$(pymn)_$(PACKAGE_LEVEL).done
239244
@echo "Makefile: Performing flake8 checks with PACKAGE_LEVEL=$(PACKAGE_LEVEL)"
240-
flake8 --config .flake8 $(package_py_files) $(test_py_files) setup.py $(doc_dir)/conf.py
245+
flake8 --config .flake8 $(src_py_files) $(test_py_files) setup.py $(doc_dir)/conf.py
241246
@echo "Makefile: Done performing flake8 checks"
242247
@echo "Makefile: $@ done."
243248

244249
.PHONY: pylint
245250
pylint: $(done_dir)/develop_$(pymn)_$(PACKAGE_LEVEL).done
246251
@echo "Makefile: Performing pylint checks with PACKAGE_LEVEL=$(PACKAGE_LEVEL)"
247-
pylint --rcfile=.pylintrc --disable=fixme $(package_py_files) $(test_py_files) setup.py $(doc_dir)/conf.py
252+
pylint --rcfile=.pylintrc --disable=fixme $(src_py_files) $(test_py_files) setup.py $(doc_dir)/conf.py
248253
@echo "Makefile: Done performing pylint checks"
249254
@echo "Makefile: $@ done."
250255

docs/changes.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ Released: not yet
4242

4343
* Fixed AttributeError when using 'storage_groups' on 'Client' object.
4444

45-
* Used a forked version 0.20.0.post1 of the 'prometheus_client' package to pick
46-
up the following fixes:
45+
* Vendorized a forked version 0.20.0.post1 of the 'prometheus_client' package
46+
to pick up the following fixes:
4747

4848
- Fixed HTTP verb tampering test failures. (issue #494)
4949
- Fixed vulnerabilities in Prometheus server detected by testssl.sh.

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ zhmcclient>=1.14.0
1414
# prometheus-client 0.20.0.post1 (on forked repo) adds the following PRs:
1515
# - Removed CBC ciphers to address CVE-2013-0169 (LUCKY13) (PR https://github.com/prometheus/client_python/pull/1051)
1616
# - Reject invalid HTTP methods and resources (PR https://github.com/prometheus/client_python/pull/1019)
17+
# For now, 0.20.0.post1 has been vendorized.
1718
# TODO: Use official prometheus-client version (0.21.0 ?) with these PRs once released.
18-
prometheus-client @ git+https://github.com/andy-maier/client_python.git@release_0.20.0.post1
1919
# prometheus-client>=0.21.0
2020

2121
urllib3>=1.26.19

setup.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,12 @@ def read_file(a_file):
8282
name='zhmc_prometheus_exporter',
8383
version=package_version,
8484
packages=[
85-
'zhmc_prometheus_exporter'
85+
'zhmc_prometheus_exporter',
86+
'zhmc_prometheus_exporter.vendor',
87+
'zhmc_prometheus_exporter.vendor.prometheus_client',
88+
'zhmc_prometheus_exporter.vendor.prometheus_client.bridge',
89+
'zhmc_prometheus_exporter.vendor.prometheus_client.openmetrics',
90+
'zhmc_prometheus_exporter.vendor.prometheus_client.twisted',
8691
],
8792
package_data={
8893
'zhmc_prometheus_exporter': ['schemas/*.yaml'],

tests/test_all.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import pytest
2929
import zhmcclient
3030
import zhmcclient_mock
31-
import prometheus_client
3231

32+
from zhmc_prometheus_exporter.vendor import prometheus_client
3333
from zhmc_prometheus_exporter import zhmc_prometheus_exporter
3434

3535

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Versions of the vendored packages:
2+
3+
# prometheus_client: https://github.com/andy-maier/client_python/releases/tag/0.20.0.post1
4+
prometheus_client_version = '0.20.0.post1'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python
2+
3+
from . import (
4+
exposition, gc_collector, metrics, metrics_core, platform_collector,
5+
process_collector, registry,
6+
)
7+
from .exposition import (
8+
CONTENT_TYPE_LATEST, delete_from_gateway, generate_latest,
9+
instance_ip_grouping_key, make_asgi_app, make_wsgi_app, MetricsHandler,
10+
push_to_gateway, pushadd_to_gateway, start_http_server, start_wsgi_server,
11+
write_to_textfile,
12+
)
13+
from .gc_collector import GC_COLLECTOR, GCCollector
14+
from .metrics import (
15+
Counter, disable_created_metrics, enable_created_metrics, Enum, Gauge,
16+
Histogram, Info, Summary,
17+
)
18+
from .metrics_core import Metric
19+
from .platform_collector import PLATFORM_COLLECTOR, PlatformCollector
20+
from .process_collector import PROCESS_COLLECTOR, ProcessCollector
21+
from .registry import CollectorRegistry, REGISTRY
22+
23+
__all__ = (
24+
'CollectorRegistry',
25+
'REGISTRY',
26+
'Metric',
27+
'Counter',
28+
'Gauge',
29+
'Summary',
30+
'Histogram',
31+
'Info',
32+
'Enum',
33+
'enable_created_metrics',
34+
'disable_created_metrics',
35+
'CONTENT_TYPE_LATEST',
36+
'generate_latest',
37+
'MetricsHandler',
38+
'make_wsgi_app',
39+
'make_asgi_app',
40+
'start_http_server',
41+
'start_wsgi_server',
42+
'write_to_textfile',
43+
'push_to_gateway',
44+
'pushadd_to_gateway',
45+
'delete_from_gateway',
46+
'instance_ip_grouping_key',
47+
'ProcessCollector',
48+
'PROCESS_COLLECTOR',
49+
'PlatformCollector',
50+
'PLATFORM_COLLECTOR',
51+
'GCCollector',
52+
'GC_COLLECTOR',
53+
)
54+
55+
if __name__ == '__main__':
56+
c = Counter('cc', 'A counter')
57+
c.inc()
58+
59+
g = Gauge('gg', 'A gauge')
60+
g.set(17)
61+
62+
s = Summary('ss', 'A summary', ['a', 'b'])
63+
s.labels('c', 'd').observe(17)
64+
65+
h = Histogram('hh', 'A histogram')
66+
h.observe(.6)
67+
68+
start_http_server(8000)
69+
import time
70+
71+
while True:
72+
time.sleep(1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from typing import Callable
2+
from urllib.parse import parse_qs
3+
4+
from .exposition import _bake_output
5+
from .registry import CollectorRegistry, REGISTRY
6+
7+
8+
def make_asgi_app(registry: CollectorRegistry = REGISTRY, disable_compression: bool = False) -> Callable:
9+
"""Create a ASGI app which serves the metrics from a registry."""
10+
11+
async def prometheus_app(scope, receive, send):
12+
assert scope.get("type") == "http"
13+
# Prepare parameters
14+
params = parse_qs(scope.get('query_string', b''))
15+
accept_header = ",".join([
16+
value.decode("utf8") for (name, value) in scope.get('headers')
17+
if name.decode("utf8").lower() == 'accept'
18+
])
19+
accept_encoding_header = ",".join([
20+
value.decode("utf8") for (name, value) in scope.get('headers')
21+
if name.decode("utf8").lower() == 'accept-encoding'
22+
])
23+
# Bake output
24+
status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression)
25+
formatted_headers = []
26+
for header in headers:
27+
formatted_headers.append(tuple(x.encode('utf8') for x in header))
28+
# Return output
29+
payload = await receive()
30+
if payload.get("type") == "http.request":
31+
await send(
32+
{
33+
"type": "http.response.start",
34+
"status": int(status.split(' ')[0]),
35+
"headers": formatted_headers,
36+
}
37+
)
38+
await send({"type": "http.response.body", "body": output})
39+
40+
return prometheus_app

zhmc_prometheus_exporter/vendor/prometheus_client/bridge/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
import re
5+
import socket
6+
import threading
7+
import time
8+
from timeit import default_timer
9+
from typing import Callable, Tuple
10+
11+
from ..registry import CollectorRegistry, REGISTRY
12+
13+
# Roughly, have to keep to what works as a file name.
14+
# We also remove periods, so labels can be distinguished.
15+
16+
_INVALID_GRAPHITE_CHARS = re.compile(r"[^a-zA-Z0-9_-]")
17+
18+
19+
def _sanitize(s):
20+
return _INVALID_GRAPHITE_CHARS.sub('_', s)
21+
22+
23+
class _RegularPush(threading.Thread):
24+
def __init__(self, pusher, interval, prefix):
25+
super().__init__()
26+
self._pusher = pusher
27+
self._interval = interval
28+
self._prefix = prefix
29+
30+
def run(self):
31+
wait_until = default_timer()
32+
while True:
33+
while True:
34+
now = default_timer()
35+
if now >= wait_until:
36+
# May need to skip some pushes.
37+
while wait_until < now:
38+
wait_until += self._interval
39+
break
40+
# time.sleep can return early.
41+
time.sleep(wait_until - now)
42+
try:
43+
self._pusher.push(prefix=self._prefix)
44+
except OSError:
45+
logging.exception("Push failed")
46+
47+
48+
class GraphiteBridge:
49+
def __init__(self,
50+
address: Tuple[str, int],
51+
registry: CollectorRegistry = REGISTRY,
52+
timeout_seconds: float = 30,
53+
_timer: Callable[[], float] = time.time,
54+
tags: bool = False,
55+
):
56+
self._address = address
57+
self._registry = registry
58+
self._tags = tags
59+
self._timeout = timeout_seconds
60+
self._timer = _timer
61+
62+
def push(self, prefix: str = '') -> None:
63+
now = int(self._timer())
64+
output = []
65+
66+
prefixstr = ''
67+
if prefix:
68+
prefixstr = prefix + '.'
69+
70+
for metric in self._registry.collect():
71+
for s in metric.samples:
72+
if s.labels:
73+
if self._tags:
74+
sep = ';'
75+
fmt = '{0}={1}'
76+
else:
77+
sep = '.'
78+
fmt = '{0}.{1}'
79+
labelstr = sep + sep.join(
80+
[fmt.format(
81+
_sanitize(k), _sanitize(v))
82+
for k, v in sorted(s.labels.items())])
83+
else:
84+
labelstr = ''
85+
output.append(f'{prefixstr}{_sanitize(s.name)}{labelstr} {float(s.value)} {now}\n')
86+
87+
conn = socket.create_connection(self._address, self._timeout)
88+
conn.sendall(''.join(output).encode('ascii'))
89+
conn.close()
90+
91+
def start(self, interval: float = 60.0, prefix: str = '') -> None:
92+
t = _RegularPush(self, interval, prefix)
93+
t.daemon = True
94+
t.start()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from timeit import default_timer
2+
from types import TracebackType
3+
from typing import (
4+
Any, Callable, Literal, Optional, Tuple, Type, TYPE_CHECKING, TypeVar,
5+
Union,
6+
)
7+
8+
from .decorator import decorate
9+
10+
if TYPE_CHECKING:
11+
from . import Counter
12+
F = TypeVar("F", bound=Callable[..., Any])
13+
14+
15+
class ExceptionCounter:
16+
def __init__(self, counter: "Counter", exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]]) -> None:
17+
self._counter = counter
18+
self._exception = exception
19+
20+
def __enter__(self) -> None:
21+
pass
22+
23+
def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> Literal[False]:
24+
if isinstance(value, self._exception):
25+
self._counter.inc()
26+
return False
27+
28+
def __call__(self, f: "F") -> "F":
29+
def wrapped(func, *args, **kwargs):
30+
with self:
31+
return func(*args, **kwargs)
32+
33+
return decorate(f, wrapped)
34+
35+
36+
class InprogressTracker:
37+
def __init__(self, gauge):
38+
self._gauge = gauge
39+
40+
def __enter__(self):
41+
self._gauge.inc()
42+
43+
def __exit__(self, typ, value, traceback):
44+
self._gauge.dec()
45+
46+
def __call__(self, f: "F") -> "F":
47+
def wrapped(func, *args, **kwargs):
48+
with self:
49+
return func(*args, **kwargs)
50+
51+
return decorate(f, wrapped)
52+
53+
54+
class Timer:
55+
def __init__(self, metric, callback_name):
56+
self._metric = metric
57+
self._callback_name = callback_name
58+
59+
def _new_timer(self):
60+
return self.__class__(self._metric, self._callback_name)
61+
62+
def __enter__(self):
63+
self._start = default_timer()
64+
return self
65+
66+
def __exit__(self, typ, value, traceback):
67+
# Time can go backwards.
68+
duration = max(default_timer() - self._start, 0)
69+
callback = getattr(self._metric, self._callback_name)
70+
callback(duration)
71+
72+
def labels(self, *args, **kw):
73+
self._metric = self._metric.labels(*args, **kw)
74+
75+
def __call__(self, f: "F") -> "F":
76+
def wrapped(func, *args, **kwargs):
77+
# Obtaining new instance of timer every time
78+
# ensures thread safety and reentrancy.
79+
with self._new_timer():
80+
return func(*args, **kwargs)
81+
82+
return decorate(f, wrapped)

0 commit comments

Comments
 (0)