Skip to content

Commit 9d60b18

Browse files
authored
Add setting to set prefix on Prometheus metrics (#3483)
* Add setting to set prefix on Prometheus metrics * Add test to cover empty prefix branch
1 parent 3f646fb commit 9d60b18

File tree

3 files changed

+50
-14
lines changed

3 files changed

+50
-14
lines changed

docs/configuration/settings.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ Prometheus metrics can be enabled with (disabled by default):
503503
504504
kinto.includes = kinto.plugins.prometheus
505505
506+
# kinto.prometheus_prefix = kinto-prod
507+
506508
Metrics can then be crawled from the ``/__metrics__`` endpoint.
507509

508510

kinto/plugins/prometheus.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_registry():
2828

2929

3030
def _fix_metric_name(s):
31-
return s.replace("-", "_").replace(".", "_")
31+
return s.replace("-", "_").replace(".", "_").replace(" ", "_")
3232

3333

3434
class Timer:
@@ -68,8 +68,20 @@ def stop(self):
6868

6969
@implementer(metrics.IMetricsService)
7070
class PrometheusService:
71+
def __init__(self, prefix=""):
72+
prefix_clean = ""
73+
if prefix:
74+
# In GCP Console, the metrics are grouped by the first
75+
# word before the first underscore. Here we make sure the specified
76+
# prefix is not mixed up with metrics names.
77+
# (eg. `remote-settings` -> `remotesettings_`, `kinto_` -> `kinto_`)
78+
prefix_clean = _fix_metric_name(prefix).replace("_", "") + "_"
79+
self.prefix = prefix_clean.lower()
80+
7181
def timer(self, key):
7282
global _METRICS
83+
key = self.prefix + key
84+
7385
if key not in _METRICS:
7486
_METRICS[key] = prometheus_module.Summary(
7587
_fix_metric_name(key), f"Summary of {key}", registry=get_registry()
@@ -84,6 +96,7 @@ def timer(self, key):
8496

8597
def observe(self, key, value, labels=[]):
8698
global _METRICS
99+
key = self.prefix + key
87100

88101
if key not in _METRICS:
89102
_METRICS[key] = prometheus_module.Summary(
@@ -106,6 +119,7 @@ def observe(self, key, value, labels=[]):
106119

107120
def count(self, key, count=1, unique=None):
108121
global _METRICS
122+
key = self.prefix + key
109123

110124
labels = []
111125

@@ -183,4 +197,7 @@ def includeme(config):
183197
pass
184198
_METRICS.clear()
185199

186-
config.registry.registerUtility(PrometheusService(), metrics.IMetricsService)
200+
settings = config.get_settings()
201+
prefix = settings.get("prometheus_prefix", settings["project_name"])
202+
203+
config.registry.registerUtility(PrometheusService(prefix=prefix), metrics.IMetricsService)

tests/plugins/test_prometheus.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class PrometheusWebTest(support.BaseWebTest, unittest.TestCase):
3131
def get_app_settings(cls, extras=None):
3232
settings = super().get_app_settings(extras)
3333
settings["includes"] = "kinto.plugins.prometheus"
34+
settings["project_name"] = "kinto PROD"
3435
return settings
3536

3637

@@ -59,15 +60,15 @@ def test_timer_can_be_used_as_context_manager(self):
5960
self.assertEqual(my_func(1, 1), 2)
6061

6162
resp = self.app.get("/__metrics__")
62-
self.assertIn("TYPE func_latency_context summary", resp.text)
63+
self.assertIn("TYPE kintoprod_func_latency_context summary", resp.text)
6364

6465
def test_timer_can_be_used_as_decorator(self):
6566
decorated = self.app.app.registry.metrics.timer("func.latency.decorator")(my_func)
6667

6768
self.assertEqual(decorated(1, 1), 2)
6869

6970
resp = self.app.get("/__metrics__")
70-
self.assertIn("TYPE func_latency_decorator summary", resp.text)
71+
self.assertIn("TYPE kintoprod_func_latency_decorator summary", resp.text)
7172

7273
def test_timer_can_be_used_as_decorator_on_partial_function(self):
7374
partial = functools.partial(my_func, 3)
@@ -76,39 +77,39 @@ def test_timer_can_be_used_as_decorator_on_partial_function(self):
7677
self.assertEqual(decorated(3), 6)
7778

7879
resp = self.app.get("/__metrics__")
79-
self.assertIn("TYPE func_latency_partial summary", resp.text)
80+
self.assertIn("TYPE kintoprod_func_latency_partial summary", resp.text)
8081

8182
def test_observe_a_single_value(self):
8283
self.app.app.registry.metrics.observe("price", 111)
8384

8485
resp = self.app.get("/__metrics__")
85-
self.assertIn("price_sum 111", resp.text)
86+
self.assertIn("kintoprod_price_sum 111", resp.text)
8687

8788
def test_observe_a_single_value_with_labels(self):
8889
self.app.app.registry.metrics.observe("size", 3.14, labels=[("endpoint", "/buckets")])
8990

9091
resp = self.app.get("/__metrics__")
91-
self.assertIn('size_sum{endpoint="/buckets"} 3.14', resp.text)
92+
self.assertIn('kintoprod_size_sum{endpoint="/buckets"} 3.14', resp.text)
9293

9394
def test_count_by_key(self):
9495
self.app.app.registry.metrics.count("key")
9596

9697
resp = self.app.get("/__metrics__")
97-
self.assertIn("key_total 1.0", resp.text)
98+
self.assertIn("kintoprod_key_total 1.0", resp.text)
9899

99100
def test_count_by_key_value(self):
100101
self.app.app.registry.metrics.count("bigstep", count=2)
101102

102103
resp = self.app.get("/__metrics__")
103-
self.assertIn("bigstep_total 2.0", resp.text)
104+
self.assertIn("kintoprod_bigstep_total 2.0", resp.text)
104105

105106
def test_count_by_key_grouped(self):
106107
self.app.app.registry.metrics.count("http", unique=[("status", "500")])
107108
self.app.app.registry.metrics.count("http", unique=[("status", "200")])
108109

109110
resp = self.app.get("/__metrics__")
110-
self.assertIn('http_total{status="500"} 1.0', resp.text)
111-
self.assertIn('http_total{status="200"} 1.0', resp.text)
111+
self.assertIn('kintoprod_http_total{status="500"} 1.0', resp.text)
112+
self.assertIn('kintoprod_http_total{status="200"} 1.0', resp.text)
112113

113114
def test_metrics_cant_be_mixed(self):
114115
self.app.app.registry.metrics.count("counter")
@@ -127,16 +128,32 @@ def test_metrics_names_and_labels_are_transformed(self):
127128
self.app.app.registry.metrics.count("http.home.status", unique=[("code.get", "200")])
128129

129130
resp = self.app.get("/__metrics__")
130-
self.assertIn('http_home_status_total{code_get="200"} 1.0', resp.text)
131+
self.assertIn('kintoprod_http_home_status_total{code_get="200"} 1.0', resp.text)
131132

132133
def test_count_with_legacy_string_generic_group(self):
133134
self.app.app.registry.metrics.count("champignons", unique="boletus")
134135

135136
resp = self.app.get("/__metrics__")
136-
self.assertIn('champignons_total{group="boletus"} 1.0', resp.text)
137+
self.assertIn('kintoprod_champignons_total{group="boletus"} 1.0', resp.text)
137138

138139
def test_count_with_legacy_string_basic_group(self):
139140
self.app.app.registry.metrics.count("mushrooms", unique="species.boletus")
140141

141142
resp = self.app.get("/__metrics__")
142-
self.assertIn('mushrooms_total{species="boletus"} 1.0', resp.text)
143+
self.assertIn('kintoprod_mushrooms_total{species="boletus"} 1.0', resp.text)
144+
145+
146+
@skip_if_no_prometheus
147+
class PrometheusNoPrefixTest(PrometheusWebTest):
148+
@classmethod
149+
def get_app_settings(cls, extras=None):
150+
settings = super().get_app_settings(extras)
151+
settings["project_name"] = "Some Project"
152+
settings["prometheus_prefix"] = ""
153+
return settings
154+
155+
def test_metrics_have_no_prefix(self):
156+
self.app.app.registry.metrics.observe("price", 111)
157+
158+
resp = self.app.get("/__metrics__")
159+
self.assertIn("TYPE price summary", resp.text)

0 commit comments

Comments
 (0)