Encode prometheus metric names per the prom spec (#26639)

Referencing issue #26418.

Prometheus metric names can only contain chars a-zA-Z0-9, : and _
(https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).

HA currently generates invalid prometheus names, e.g. if the unit for a
sensor is a non-ASCII character containing  ° or μ. To resolve, we need
to sanitize the name before creating, replacing non-valid characters
with a valid representation. In this case, I've used
"u{unicode-hex-code}".

Also updated the test case to make sure that the ° case is handled.
pull/26723/head
Andrew Rowson 2019-09-19 11:51:49 +01:00 committed by Martin Hjelmare
parent 5e15675593
commit 770eeaf82f
2 changed files with 26 additions and 1 deletions

View File

@ -1,5 +1,6 @@
"""Support for Prometheus metrics export."""
import logging
import string
from aiohttp import web
import voluptuous as vol
@ -159,10 +160,23 @@ class PrometheusMetrics:
try:
return self._metrics[metric]
except KeyError:
full_metric_name = f"{self.metrics_prefix}{metric}"
full_metric_name = self._sanitize_metric_name(
f"{self.metrics_prefix}{metric}"
)
self._metrics[metric] = factory(full_metric_name, documentation, labels)
return self._metrics[metric]
@staticmethod
def _sanitize_metric_name(metric: str) -> str:
return "".join(
[
c
if c in string.ascii_letters or c.isdigit() or c == "_" or c == ":"
else f"u{hex(ord(c))}"
for c in metric
]
)
@staticmethod
def state_as_number(state):
"""Return a state casted to a float."""

View File

@ -41,6 +41,11 @@ async def prometheus_client(loop, hass, hass_client):
sensor3.entity_id = "sensor.electricity_price"
await sensor3.async_update_ha_state()
sensor4 = DemoSensor("Wind Direction", 25, None, "°", None)
sensor4.hass = hass
sensor4.entity_id = "sensor.wind_direction"
await sensor4.async_update_ha_state()
return await hass_client()
@ -103,3 +108,9 @@ def test_view(prometheus_client): # pylint: disable=redefined-outer-name
'entity="sensor.electricity_price",'
'friendly_name="Electricity price"} 0.123' in body
)
assert (
'sensor_unit_u0xb0{domain="sensor",'
'entity="sensor.wind_direction",'
'friendly_name="Wind Direction"} 25.0' in body
)