Skip to content

Commit

Permalink
Merge pull request #56 from kpetremann/crash_bad_char_metric_name
Browse files Browse the repository at this point in the history
Crash bad char metric name
  • Loading branch information
kpetremann authored Jun 25, 2023
2 parents 068c59e + 97d54b5 commit 86a6dee
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 4 deletions.
30 changes: 28 additions & 2 deletions mqtt_exporter/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys

import paho.mqtt.client as mqtt
from prometheus_client import Counter, Gauge, start_http_server
from prometheus_client import Counter, Gauge, metrics, start_http_server

from mqtt_exporter import settings

Expand Down Expand Up @@ -61,6 +61,24 @@ def subscribe(client, _, __, result_code, *args):
LOG.error("MQTT %s", mqtt.connack_string(result_code))


def _normalize_prometheus_metric_name(prom_metric_name):
"""Transform an invalid prometheus metric to a valid one.
https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
"""
if metrics.METRIC_NAME_RE.match(prom_metric_name):
return prom_metric_name

# clean invalid characted
prom_metric_name = re.sub(r"[^a-zA-Z0-9_:]", "", prom_metric_name)

# ensure to start with valid character
if not re.match(r"^[a-zA-Z_:]", prom_metric_name):
prom_metric_name = ":" + prom_metric_name

return prom_metric_name


def _create_prometheus_metric(prom_metric_name):
"""Create Prometheus metric if does not exist."""
if not prom_metrics.get(prom_metric_name):
Expand All @@ -75,6 +93,9 @@ def _create_prometheus_metric(prom_metric_name):


def _add_prometheus_sample(topic, prom_metric_name, metric_value, client_id):
if prom_metric_name not in prom_metrics:
return

labels = {settings.TOPIC_LABEL: topic}
if settings.MQTT_EXPOSE_CLIENT_ID:
labels["client_id"] = client_id
Expand Down Expand Up @@ -142,7 +163,12 @@ def _parse_metrics(data, topic, client_id, prefix=""):
.replace("/", "_")
)
prom_metric_name = re.sub(r"\((.*?)\)", "", prom_metric_name)
_create_prometheus_metric(prom_metric_name)
prom_metric_name = _normalize_prometheus_metric_name(prom_metric_name)
try:
_create_prometheus_metric(prom_metric_name)
except ValueError as error:
LOG.error("unable to create prometheus metric '%s': %s", prom_metric_name, error)
return

# expose the sample to prometheus
_add_prometheus_sample(topic, prom_metric_name, metric_value, client_id)
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_expose_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ def _exec(client_id, mocker):
userdata = {"client_id": client_id}
msg = mocker.Mock()
msg.topic = "zigbee2mqtt/garage"
msg.payload = '{"temperature": "23.5", "humidity": "40.5"}'
msg.payload = '{"temperature°C": "23.5", "humidity": "40.5"}'
main.expose_metrics(None, userdata, msg)

temperatures = main.prom_metrics["mqtt_temperature"].collect()
temperatures = main.prom_metrics["mqtt_temperatureC"].collect()
humidity = main.prom_metrics["mqtt_humidity"].collect()

return temperatures, humidity
Expand Down
1 change: 1 addition & 0 deletions tests/unit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests."""
15 changes: 15 additions & 0 deletions tests/unit/test_normalize_prometheus_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Tests of Prometheus normalization metrics."""
from mqtt_exporter.main import _normalize_prometheus_metric_name


def test_normalize_prometheus_metric_name():
"""Test _normalize_prometheus_metric_name."""
tests = {
"1234invalid": ":1234invalid",
"valid1234": "valid1234",
"_this_is_valid": "_this_is_valid",
"not_so_valid%_name": "not_so_valid_name",
}

for candidate, wanted in tests.items():
assert _normalize_prometheus_metric_name(candidate) == wanted

0 comments on commit 86a6dee

Please sign in to comment.