mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add generic unavailable and last_updated metrics for prometheus (#37456)
* Add generic unavailable and last_updated metrics for prometheus * Updated with feedback from the code review
This commit is contained in:
parent
c6ab2c5d0a
commit
16a947aa5f
@ -25,6 +25,7 @@ from homeassistant.const import (
|
|||||||
CONTENT_TYPE_TEXT_PLAIN,
|
CONTENT_TYPE_TEXT_PLAIN,
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
UNIT_PERCENTAGE,
|
UNIT_PERCENTAGE,
|
||||||
@ -153,13 +154,28 @@ class PrometheusMetrics:
|
|||||||
|
|
||||||
handler = f"_handle_{domain}"
|
handler = f"_handle_{domain}"
|
||||||
|
|
||||||
if hasattr(self, handler):
|
if hasattr(self, handler) and state.state != STATE_UNAVAILABLE:
|
||||||
getattr(self, handler)(state)
|
getattr(self, handler)(state)
|
||||||
|
|
||||||
metric = self._metric(
|
labels = self._labels(state)
|
||||||
|
state_change = self._metric(
|
||||||
"state_change", self.prometheus_cli.Counter, "The number of state changes"
|
"state_change", self.prometheus_cli.Counter, "The number of state changes"
|
||||||
)
|
)
|
||||||
metric.labels(**self._labels(state)).inc()
|
state_change.labels(**labels).inc()
|
||||||
|
|
||||||
|
entity_available = self._metric(
|
||||||
|
"entity_available",
|
||||||
|
self.prometheus_cli.Gauge,
|
||||||
|
"Entity is available (not in the unavailable state)",
|
||||||
|
)
|
||||||
|
entity_available.labels(**labels).set(float(state.state != STATE_UNAVAILABLE))
|
||||||
|
|
||||||
|
last_updated_time_seconds = self._metric(
|
||||||
|
"last_updated_time_seconds",
|
||||||
|
self.prometheus_cli.Gauge,
|
||||||
|
"The last_updated timestamp",
|
||||||
|
)
|
||||||
|
last_updated_time_seconds.labels(**labels).set(state.last_updated.timestamp())
|
||||||
|
|
||||||
def _handle_attributes(self, state):
|
def _handle_attributes(self, state):
|
||||||
for key, value in state.attributes.items():
|
for key, value in state.attributes.items():
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""The tests for the Prometheus exporter."""
|
"""The tests for the Prometheus exporter."""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import setup
|
|
||||||
from homeassistant.components import climate, humidifier, sensor
|
from homeassistant.components import climate, humidifier, sensor
|
||||||
from homeassistant.components.demo.sensor import DemoSensor
|
from homeassistant.components.demo.sensor import DemoSensor
|
||||||
import homeassistant.components.prometheus as prometheus
|
import homeassistant.components.prometheus as prometheus
|
||||||
@ -16,6 +16,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
import tests.async_mock as mock
|
import tests.async_mock as mock
|
||||||
|
|
||||||
@ -30,21 +31,18 @@ class FilterTest:
|
|||||||
should_pass: bool
|
should_pass: bool
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
async def prometheus_client(hass, hass_client):
|
||||||
async def prometheus_client(loop, hass, hass_client):
|
|
||||||
"""Initialize an hass_client with Prometheus component."""
|
"""Initialize an hass_client with Prometheus component."""
|
||||||
await async_setup_component(hass, prometheus.DOMAIN, {prometheus.DOMAIN: {}})
|
await async_setup_component(hass, prometheus.DOMAIN, {prometheus.DOMAIN: {}})
|
||||||
|
|
||||||
await setup.async_setup_component(
|
await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]})
|
||||||
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
|
|
||||||
)
|
|
||||||
|
|
||||||
await setup.async_setup_component(
|
await async_setup_component(
|
||||||
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await setup.async_setup_component(
|
await async_setup_component(
|
||||||
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
|
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -60,6 +58,10 @@ async def prometheus_client(loop, hass, hass_client):
|
|||||||
)
|
)
|
||||||
sensor2.hass = hass
|
sensor2.hass = hass
|
||||||
sensor2.entity_id = "sensor.radio_energy"
|
sensor2.entity_id = "sensor.radio_energy"
|
||||||
|
with mock.patch(
|
||||||
|
"homeassistant.util.dt.utcnow",
|
||||||
|
return_value=datetime.datetime(1970, 1, 2, tzinfo=dt_util.UTC),
|
||||||
|
):
|
||||||
await sensor2.async_update_ha_state()
|
await sensor2.async_update_ha_state()
|
||||||
|
|
||||||
sensor3 = DemoSensor(
|
sensor3 = DemoSensor(
|
||||||
@ -89,9 +91,10 @@ async def prometheus_client(loop, hass, hass_client):
|
|||||||
return await hass_client()
|
return await hass_client()
|
||||||
|
|
||||||
|
|
||||||
async def test_view(prometheus_client): # pylint: disable=redefined-outer-name
|
async def test_view(hass, hass_client):
|
||||||
"""Test prometheus metrics view."""
|
"""Test prometheus metrics view."""
|
||||||
resp = await prometheus_client.get(prometheus.API_ENDPOINT)
|
client = await prometheus_client(hass, hass_client)
|
||||||
|
resp = await client.get(prometheus.API_ENDPOINT)
|
||||||
|
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
assert resp.headers["content-type"] == "text/plain"
|
assert resp.headers["content-type"] == "text/plain"
|
||||||
@ -167,6 +170,18 @@ async def test_view(prometheus_client): # pylint: disable=redefined-outer-name
|
|||||||
'friendly_name="Radio Energy"} 14.0' in body
|
'friendly_name="Radio Energy"} 14.0' in body
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.radio_energy",'
|
||||||
|
'friendly_name="Radio Energy"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'last_updated_time_seconds{domain="sensor",'
|
||||||
|
'entity="sensor.radio_energy",'
|
||||||
|
'friendly_name="Radio Energy"} 86400.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
'sensor_unit_sek_per_kwh{domain="sensor",'
|
'sensor_unit_sek_per_kwh{domain="sensor",'
|
||||||
'entity="sensor.electricity_price",'
|
'entity="sensor.electricity_price",'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user