Skip to content

Commit 03a033a

Browse files
authored
Support multiprocessing metrics (#179)
* Support multiprocessing metrics * Test metrics with enabled multiprocess
1 parent 9479ed3 commit 03a033a

File tree

2 files changed

+101
-5
lines changed

2 files changed

+101
-5
lines changed

pyms/flask/services/metrics.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Text
44

55
from flask import Blueprint, Response, request
6-
from prometheus_client import Counter, Histogram, generate_latest
6+
from prometheus_client import multiprocess, Counter, Histogram, generate_latest, CollectorRegistry, REGISTRY
77
from pyms.flask.services.driver import DriverService
88

99
# Based on https://github.com/sbarratt/flask-prometheus
@@ -51,8 +51,17 @@ class Service(DriverService):
5151
def __init__(self, *args, **kwargs):
5252
super().__init__(*args, **kwargs)
5353
self.metrics_blueprint = Blueprint("metrics", __name__)
54+
self.init_registry()
5455
self.serve_metrics()
5556

57+
def init_registry(self):
58+
try:
59+
multiprocess_registry = CollectorRegistry()
60+
multiprocess.MultiProcessCollector(multiprocess_registry)
61+
self.registry = multiprocess_registry
62+
except ValueError:
63+
self.registry = REGISTRY
64+
5665
@staticmethod
5766
def monitor(app_name, app):
5867
metric = FlaskMetricsWrapper(app_name)
@@ -63,7 +72,7 @@ def serve_metrics(self):
6372
@self.metrics_blueprint.route("/metrics", methods=["GET"])
6473
def metrics(): # pylint: disable=unused-variable
6574
return Response(
66-
generate_latest(),
75+
generate_latest(self.registry),
6776
mimetype="text/print()lain",
6877
content_type="text/plain; charset=utf-8",
6978
)

tests/test_metrics.py

+90-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
import os
22
import unittest.mock
3+
from tempfile import TemporaryDirectory
4+
from pathlib import Path
35

46
from prometheus_client import generate_latest
5-
7+
from prometheus_client import values
8+
from opentracing import global_tracer
69
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT
10+
from pyms.flask.services.metrics import LOGGER_TOTAL_MESSAGES, FLASK_REQUEST_COUNT, FLASK_REQUEST_LATENCY
711
from tests.common import MyMicroserviceNoSingleton
812

13+
def reset_metric(metric):
14+
metric._metric_init() # pylint: disable=protected-access
15+
metric._metrics = {} # pylint: disable=protected-access
16+
17+
def reset_metrics():
18+
reset_metric(LOGGER_TOTAL_MESSAGES)
19+
reset_metric(FLASK_REQUEST_COUNT)
20+
reset_metric(FLASK_REQUEST_LATENCY)
21+
try:
22+
for metric in global_tracer().metrics_factory._cache.values(): # pylint: disable=protected-access
23+
reset_metric(metric)
24+
except AttributeError: # Not a Jaeger tracer
25+
pass
926

1027
class TestMetricsFlask(unittest.TestCase):
1128
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -20,15 +37,15 @@ def setUp(self):
2037
def test_metrics_latency(self):
2138
self.client.get("/")
2239
self.client.get("/metrics")
23-
generated_latency_root = b'http_server_requests_seconds_bucket{le="0.005",method="GET",service="Python Microservice",status="200",uri="/"}'
40+
generated_latency_root = b'http_server_requests_seconds_bucket{le="0.005",method="GET",service="Python Microservice with Jaeger",status="404",uri="/"}'
2441
generated_latency_metrics = b'http_server_requests_seconds_bucket{le="0.005",method="GET",service="Python Microservice with Jaeger",status="200",uri="/metrics"}'
2542
assert generated_latency_root in generate_latest()
2643
assert generated_latency_metrics in generate_latest()
2744

2845
def test_metrics_count(self):
2946
self.client.get("/")
3047
self.client.get("/metrics")
31-
generated_count_root = b'http_server_requests_count_total{method="GET",service="Python Microservice",status="200",uri="/"}'
48+
generated_count_root = b'http_server_requests_count_total{method="GET",service="Python Microservice with Jaeger",status="404",uri="/"}'
3249
generated_count_metrics = b'http_server_requests_count_total{method="GET",service="Python Microservice with Jaeger",status="200",uri="/metrics"}'
3350
assert generated_count_root in generate_latest()
3451
assert generated_count_metrics in generate_latest()
@@ -44,3 +61,73 @@ def test_metrics_jaeger(self):
4461
self.client.get("/metrics")
4562
generated_logger = b'jaeger:reporter_spans_total'
4663
assert generated_logger in generate_latest()
64+
65+
class TestMultiprocessMetricsFlask(unittest.TestCase):
66+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
67+
current = None
68+
69+
@classmethod
70+
def current_test(cls):
71+
return "not_in_test" if cls.current is None else cls.current
72+
73+
@classmethod
74+
def setUpClass(cls):
75+
cls.temp_dir = TemporaryDirectory()
76+
os.environ["prometheus_multiproc_dir"] = cls.temp_dir.name
77+
cls.patch_value_class = unittest.mock.patch.object(values, "ValueClass", values.MultiProcessValue(cls.current_test))
78+
cls.patch_value_class.start()
79+
80+
def setUp(self):
81+
TestMultiprocessMetricsFlask.current = self._testMethodName
82+
os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-tests-metrics.yml")
83+
ms = MyMicroserviceNoSingleton(path=__file__)
84+
ms.reload_conf()
85+
reset_metrics()
86+
self.app = ms.create_app()
87+
self.client = self.app.test_client()
88+
for path in Path(self.temp_dir.name).iterdir():
89+
if self._testMethodName not in path.name:
90+
path.unlink()
91+
92+
@classmethod
93+
def tearDownClass(cls):
94+
cls.patch_value_class.stop()
95+
os.environ.pop("prometheus_multiproc_dir")
96+
reset_metrics()
97+
98+
def test_metrics_stored_in_directory(self):
99+
assert TestMultiprocessMetricsFlask.current_test() is not None
100+
self.client.get("/")
101+
self.client.get("/metrics")
102+
metrics = os.listdir(path=self.temp_dir.name)
103+
104+
assert f"counter_{self._testMethodName}.db" in metrics
105+
assert f"histogram_{self._testMethodName}.db" in metrics
106+
107+
def test_metrics_latency(self):
108+
self.client.get("/")
109+
self.client.get("/metrics")
110+
generated_latency_root = b'http_server_requests_seconds_bucket{le="0.005",method="GET",service="Python Microservice with Jaeger",status="404",uri="/"}'
111+
generated_latency_metrics = b'http_server_requests_seconds_bucket{le="0.005",method="GET",service="Python Microservice with Jaeger",status="200",uri="/metrics"}'
112+
assert generated_latency_root in generate_latest(self.app.ms.metrics.registry)
113+
assert generated_latency_metrics in generate_latest(self.app.ms.metrics.registry)
114+
115+
def test_metrics_count(self):
116+
self.client.get("/")
117+
self.client.get("/metrics")
118+
generated_count_root = b'http_server_requests_count_total{method="GET",service="Python Microservice with Jaeger",status="404",uri="/"}'
119+
generated_count_metrics = b'http_server_requests_count_total{method="GET",service="Python Microservice with Jaeger",status="200",uri="/metrics"}'
120+
assert generated_count_root in generate_latest(self.app.ms.metrics.registry)
121+
assert generated_count_metrics in generate_latest(self.app.ms.metrics.registry)
122+
123+
def test_metrics_logger(self):
124+
self.client.get("/")
125+
self.client.get("/metrics")
126+
generated_logger = b'logger_messages_total{level="DEBUG",service="Python Microservice with Jaeger"}'
127+
assert generated_logger in generate_latest(self.app.ms.metrics.registry)
128+
129+
def test_metrics_jaeger(self):
130+
self.client.get("/")
131+
self.client.get("/metrics")
132+
generated_logger = b'jaeger:reporter_spans_total'
133+
assert generated_logger in generate_latest(self.app.ms.metrics.registry)

0 commit comments

Comments
 (0)