1
mirror of https://github.com/home-assistant/core synced 2024-09-06 10:29:55 +02:00

Refactor Prometheus tests (#60451)

* Removed prometheus from .coveragerc

* Update prometheus tests with handler categories

* Updated prometheus metrics to use the current registry
- don't use the registry created on import (needed for tests)

* Reset the prometheus CollectorRegistry before every test

* Update prometheus metrics generation
- Use latest registry when generating a response

* Add default collectors when resetting the registry

* Move entities to the specific prometheus test case

* Refactor body generation for prometheus tests

* Add test case for sensors without unit after rebase

* Fix prometheus tests
- Wait for events in prometheus tests
- Add workaround for demo platform dependecy conversation (aiohttp frozen router)

* Added prometheus tests for attribute metrics

* Added prometheus tests for binary_sensor

* Add prometheus test for input_boolean

* Add prometheus test for lights

* Add prometheus test for lock

* Add prometheus test for sensor fahrenheit conversion

* Fix prometheus test for input_number
This commit is contained in:
alim4r 2021-12-08 20:18:21 +01:00 committed by GitHub
parent 159506262a
commit 7d256f56c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 447 additions and 116 deletions

View File

@ -844,7 +844,6 @@ omit =
homeassistant/components/progettihwsw/__init__.py homeassistant/components/progettihwsw/__init__.py
homeassistant/components/progettihwsw/binary_sensor.py homeassistant/components/progettihwsw/binary_sensor.py
homeassistant/components/progettihwsw/switch.py homeassistant/components/progettihwsw/switch.py
homeassistant/components/prometheus/*
homeassistant/components/prowl/notify.py homeassistant/components/prowl/notify.py
homeassistant/components/proxmoxve/* homeassistant/components/proxmoxve/*
homeassistant/components/proxy/camera.py homeassistant/components/proxy/camera.py

View File

@ -210,7 +210,12 @@ class PrometheusMetrics:
full_metric_name = self._sanitize_metric_name( full_metric_name = self._sanitize_metric_name(
f"{self.metrics_prefix}{metric}" f"{self.metrics_prefix}{metric}"
) )
self._metrics[metric] = factory(full_metric_name, documentation, labels) self._metrics[metric] = factory(
full_metric_name,
documentation,
labels,
registry=self.prometheus_cli.REGISTRY,
)
return self._metrics[metric] return self._metrics[metric]
@staticmethod @staticmethod
@ -524,6 +529,6 @@ class PrometheusView(HomeAssistantView):
_LOGGER.debug("Received Prometheus metrics request") _LOGGER.debug("Received Prometheus metrics request")
return web.Response( return web.Response(
body=self.prometheus_cli.generate_latest(), body=self.prometheus_cli.generate_latest(self.prometheus_cli.REGISTRY),
content_type=CONTENT_TYPE_TEXT_PLAIN, content_type=CONTENT_TYPE_TEXT_PLAIN,
) )

View File

@ -4,9 +4,12 @@ import datetime
from http import HTTPStatus from http import HTTPStatus
import unittest.mock as mock import unittest.mock as mock
import prometheus_client
import pytest import pytest
from homeassistant.components import climate, humidifier, sensor from homeassistant.components import climate, humidifier, lock, sensor
from homeassistant.components.demo.binary_sensor import DemoBinarySensor
from homeassistant.components.demo.light import DemoLight
from homeassistant.components.demo.number import DemoNumber from homeassistant.components.demo.number import DemoNumber
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
@ -15,8 +18,10 @@ from homeassistant.const import (
CONTENT_TYPE_TEXT_PLAIN, CONTENT_TYPE_TEXT_PLAIN,
DEGREE, DEGREE,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
ENERGY_KILO_WATT_HOUR, ENERGY_KILO_WATT_HOUR,
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
TEMP_FAHRENHEIT,
) )
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
@ -33,24 +38,109 @@ class FilterTest:
should_pass: bool should_pass: bool
async def prometheus_client(hass, hass_client, namespace): async def setup_prometheus_client(hass, hass_client, namespace):
"""Initialize an hass_client with Prometheus component.""" """Initialize an hass_client with Prometheus component."""
# Reset registry
prometheus_client.REGISTRY = prometheus_client.CollectorRegistry(auto_describe=True)
prometheus_client.ProcessCollector(registry=prometheus_client.REGISTRY)
prometheus_client.PlatformCollector(registry=prometheus_client.REGISTRY)
prometheus_client.GCCollector(registry=prometheus_client.REGISTRY)
config = {} config = {}
if namespace is not None: if namespace is not None:
config[prometheus.CONF_PROM_NAMESPACE] = namespace config[prometheus.CONF_PROM_NAMESPACE] = namespace
await async_setup_component(hass, prometheus.DOMAIN, {prometheus.DOMAIN: config}) assert await async_setup_component(
hass, prometheus.DOMAIN, {prometheus.DOMAIN: config}
await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]})
await async_setup_component(
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
)
await async_setup_component(
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
) )
await hass.async_block_till_done() await hass.async_block_till_done()
return await hass_client()
async def generate_latest_metrics(client):
"""Generate the latest metrics and transform the body."""
resp = await client.get(prometheus.API_ENDPOINT)
assert resp.status == HTTPStatus.OK
assert resp.headers["content-type"] == CONTENT_TYPE_TEXT_PLAIN
body = await resp.text()
body = body.split("\n")
assert len(body) > 3
return body
async def test_view_empty_namespace(hass, hass_client):
"""Test prometheus metrics view."""
client = await setup_prometheus_client(hass, hass_client, "")
sensor2 = DemoSensor(
None, "Radio Energy", 14, DEVICE_CLASS_POWER, None, ENERGY_KILO_WATT_HOUR, None
)
sensor2.hass = hass
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 hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert "# HELP python_info Python platform information" in body
assert (
"# HELP python_gc_objects_collected_total "
"Objects collected during gc" 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
)
async def test_view_default_namespace(hass, hass_client):
"""Test prometheus metrics view."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, None)
assert await async_setup_component(
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
)
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert "# HELP python_info Python platform information" in body
assert (
"# HELP python_gc_objects_collected_total "
"Objects collected during gc" in body
)
assert (
'homeassistant_sensor_temperature_celsius{domain="sensor",'
'entity="sensor.outside_temperature",'
'friendly_name="Outside Temperature"} 15.6' in body
)
async def test_sensor_unit(hass, hass_client):
"""Test prometheus metrics for sensors with a unit."""
client = await setup_prometheus_client(hass, hass_client, "")
sensor1 = DemoSensor( sensor1 = DemoSensor(
None, "Television Energy", 74, None, None, ENERGY_KILO_WATT_HOUR, None None, "Television Energy", 74, None, None, ENERGY_KILO_WATT_HOUR, None
) )
@ -100,6 +190,38 @@ async def prometheus_client(hass, hass_client, namespace):
sensor5.entity_id = "sensor.sps30_pm_1um_weight_concentration" sensor5.entity_id = "sensor.sps30_pm_1um_weight_concentration"
await sensor5.async_update_ha_state() await sensor5.async_update_ha_state()
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert (
'sensor_unit_kwh{domain="sensor",'
'entity="sensor.television_energy",'
'friendly_name="Television Energy"} 74.0' in body
)
assert (
'sensor_unit_sek_per_kwh{domain="sensor",'
'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
)
assert (
'sensor_unit_u0xb5g_per_mu0xb3{domain="sensor",'
'entity="sensor.sps30_pm_1um_weight_concentration",'
'friendly_name="SPS30 PM <1µm Weight concentration"} 3.7069' in body
)
async def test_sensor_without_unit(hass, hass_client):
"""Test prometheus metrics for sensors without a unit."""
client = await setup_prometheus_client(hass, hass_client, "")
sensor6 = DemoSensor(None, "Trend Gradient", 0.002, None, None, None, None) sensor6 = DemoSensor(None, "Trend Gradient", 0.002, None, None, None, None)
sensor6.hass = hass sensor6.hass = hass
sensor6.entity_id = "sensor.trend_gradient" sensor6.entity_id = "sensor.trend_gradient"
@ -115,6 +237,90 @@ async def prometheus_client(hass, hass_client, namespace):
sensor8.entity_id = "sensor.text_unit" sensor8.entity_id = "sensor.text_unit"
await sensor8.async_update_ha_state() await sensor8.async_update_ha_state()
body = await generate_latest_metrics(client)
assert (
'sensor_state{domain="sensor",'
'entity="sensor.trend_gradient",'
'friendly_name="Trend Gradient"} 0.002' in body
)
assert (
'sensor_state{domain="sensor",'
'entity="sensor.text",'
'friendly_name="Text"} 0' not in body
)
assert (
'sensor_unit_text{domain="sensor",'
'entity="sensor.text_unit",'
'friendly_name="Text Unit"} 0' not in body
)
async def test_sensor_device_class(hass, hass_client):
"""Test prometheus metrics for sensor with a device_class."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, "")
await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]})
await hass.async_block_till_done()
sensor1 = DemoSensor(
None, "Fahrenheit", 50, DEVICE_CLASS_TEMPERATURE, None, TEMP_FAHRENHEIT, None
)
sensor1.hass = hass
sensor1.entity_id = "sensor.fahrenheit"
await sensor1.async_update_ha_state()
sensor2 = DemoSensor(
None, "Radio Energy", 14, DEVICE_CLASS_POWER, None, ENERGY_KILO_WATT_HOUR, None
)
sensor2.hass = hass
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 hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert (
'sensor_temperature_celsius{domain="sensor",'
'entity="sensor.fahrenheit",'
'friendly_name="Fahrenheit"} 10.0' in body
)
assert (
'sensor_temperature_celsius{domain="sensor",'
'entity="sensor.outside_temperature",'
'friendly_name="Outside Temperature"} 15.6' in body
)
assert (
'sensor_humidity_percent{domain="sensor",'
'entity="sensor.outside_humidity",'
'friendly_name="Outside Humidity"} 54.0' in body
)
assert (
'sensor_power_kwh{domain="sensor",'
'entity="sensor.radio_energy",'
'friendly_name="Radio Energy"} 14.0' in body
)
async def test_input_number(hass, hass_client):
"""Test prometheus metrics for input_number."""
client = await setup_prometheus_client(hass, hass_client, "")
number1 = DemoNumber(None, "Threshold", 5.2, None, False, 0, 10, 0.1) number1 = DemoNumber(None, "Threshold", 5.2, None, False, 0, 10, 0.1)
number1.hass = hass number1.hass = hass
number1.entity_id = "input_number.threshold" number1.entity_id = "input_number.threshold"
@ -126,39 +332,61 @@ async def prometheus_client(hass, hass_client, namespace):
number2._attr_name = None number2._attr_name = None
await number2.async_update_ha_state() await number2.async_update_ha_state()
return await hass_client() await hass.async_block_till_done()
body = await generate_latest_metrics(client)
async def test_view_empty_namespace(hass, hass_client):
"""Test prometheus metrics view."""
client = await prometheus_client(hass, hass_client, "")
resp = await client.get(prometheus.API_ENDPOINT)
assert resp.status == HTTPStatus.OK
assert resp.headers["content-type"] == CONTENT_TYPE_TEXT_PLAIN
body = await resp.text()
body = body.split("\n")
assert len(body) > 3
assert "# HELP python_info Python platform information" in body
assert ( assert (
"# HELP python_gc_objects_collected_total " 'input_number_state{domain="input_number",'
"Objects collected during gc" in body 'entity="input_number.threshold",'
'friendly_name="Threshold"} 5.2' in body
) )
assert ( assert (
'sensor_temperature_celsius{domain="sensor",' 'input_number_state{domain="input_number",'
'entity="sensor.outside_temperature",' 'entity="input_number.brightness",'
'friendly_name="Outside Temperature"} 15.6' in body 'friendly_name="None"} 60.0' in body
) )
async def test_battery(hass, hass_client):
"""Test prometheus metrics for battery."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, "")
await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]})
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert ( assert (
'battery_level_percent{domain="sensor",' 'battery_level_percent{domain="sensor",'
'entity="sensor.outside_temperature",' 'entity="sensor.outside_temperature",'
'friendly_name="Outside Temperature"} 12.0' in body 'friendly_name="Outside Temperature"} 12.0' in body
) )
async def test_climate(hass, hass_client):
"""Test prometheus metrics for battery."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, "")
await async_setup_component(
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
)
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert ( assert (
'climate_current_temperature_celsius{domain="climate",' 'climate_current_temperature_celsius{domain="climate",'
'entity="climate.heatpump",' 'entity="climate.heatpump",'
@ -183,6 +411,24 @@ async def test_view_empty_namespace(hass, hass_client):
'friendly_name="Ecobee"} 24.0' in body 'friendly_name="Ecobee"} 24.0' in body
) )
async def test_humidifier(hass, hass_client):
"""Test prometheus metrics for battery."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, "")
await async_setup_component(
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
)
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert ( assert (
'humidifier_target_humidity_percent{domain="humidifier",' 'humidifier_target_humidity_percent{domain="humidifier",'
'entity="humidifier.humidifier",' 'entity="humidifier.humidifier",'
@ -208,107 +454,188 @@ async def test_view_empty_namespace(hass, hass_client):
'mode="eco"} 0.0' in body 'mode="eco"} 0.0' in body
) )
async def test_attributes(hass, hass_client):
"""Test prometheus metrics for entity attributes."""
client = await setup_prometheus_client(hass, hass_client, "")
switch1 = DemoSensor(None, "Boolean", 74, None, None, None, None)
switch1.hass = hass
switch1.entity_id = "switch.boolean"
switch1._attr_extra_state_attributes = {"boolean": True}
await switch1.async_update_ha_state()
switch2 = DemoSensor(None, "Number", 42, None, None, None, None)
switch2.hass = hass
switch2.entity_id = "switch.number"
switch2._attr_extra_state_attributes = {"Number": 10.2}
await switch2.async_update_ha_state()
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert ( assert (
'sensor_humidity_percent{domain="sensor",' 'switch_state{domain="switch",'
'entity="sensor.outside_humidity",' 'entity="switch.boolean",'
'friendly_name="Outside Humidity"} 54.0' in body 'friendly_name="Boolean"} 74.0' in body
) )
assert ( assert (
'sensor_unit_kwh{domain="sensor",' 'switch_attr_boolean{domain="switch",'
'entity="sensor.television_energy",' 'entity="switch.boolean",'
'friendly_name="Television Energy"} 74.0' in body 'friendly_name="Boolean"} 1.0' in body
) )
assert ( assert (
'sensor_power_kwh{domain="sensor",' 'switch_state{domain="switch",'
'entity="sensor.radio_energy",' 'entity="switch.number",'
'friendly_name="Radio Energy"} 14.0' in body 'friendly_name="Number"} 42.0' in body
) )
assert ( assert (
'entity_available{domain="sensor",' 'switch_attr_number{domain="switch",'
'entity="sensor.radio_energy",' 'entity="switch.number",'
'friendly_name="Radio Energy"} 1.0' in body 'friendly_name="Number"} 10.2' in body
)
assert (
'last_updated_time_seconds{domain="sensor",'
'entity="sensor.radio_energy",'
'friendly_name="Radio Energy"} 86400.0' in body
)
assert (
'sensor_unit_sek_per_kwh{domain="sensor",'
'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
)
assert (
'sensor_unit_u0xb5g_per_mu0xb3{domain="sensor",'
'entity="sensor.sps30_pm_1um_weight_concentration",'
'friendly_name="SPS30 PM <1µm Weight concentration"} 3.7069' in body
)
assert (
'sensor_state{domain="sensor",'
'entity="sensor.trend_gradient",'
'friendly_name="Trend Gradient"} 0.002' in body
)
assert (
'sensor_state{domain="sensor",'
'entity="sensor.text",'
'friendly_name="Text"} 0' not in body
)
assert (
'sensor_unit_text{domain="sensor",'
'entity="sensor.text_unit",'
'friendly_name="Text Unit"} 0' not in body
)
assert (
'input_number_state{domain="input_number",'
'entity="input_number.threshold",'
'friendly_name="Threshold"} 5.2' in body
)
assert (
'input_number_state{domain="input_number",'
'entity="input_number.brightness",'
'friendly_name="None"} 60.0' in body
) )
async def test_view_default_namespace(hass, hass_client): async def test_binary_sensor(hass, hass_client):
"""Test prometheus metrics view.""" """Test prometheus metrics for binary_sensor."""
client = await prometheus_client(hass, hass_client, None) client = await setup_prometheus_client(hass, hass_client, "")
resp = await client.get(prometheus.API_ENDPOINT)
assert resp.status == HTTPStatus.OK binary_sensor1 = DemoBinarySensor(None, "Door", True, None)
assert resp.headers["content-type"] == CONTENT_TYPE_TEXT_PLAIN binary_sensor1.hass = hass
body = await resp.text() binary_sensor1.entity_id = "binary_sensor.door"
body = body.split("\n") await binary_sensor1.async_update_ha_state()
assert len(body) > 3 binary_sensor1 = DemoBinarySensor(None, "Window", False, None)
binary_sensor1.hass = hass
binary_sensor1.entity_id = "binary_sensor.window"
await binary_sensor1.async_update_ha_state()
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert "# HELP python_info Python platform information" in body
assert ( assert (
"# HELP python_gc_objects_collected_total " 'binary_sensor_state{domain="binary_sensor",'
"Objects collected during gc" in body 'entity="binary_sensor.door",'
'friendly_name="Door"} 1.0' in body
) )
assert ( assert (
'homeassistant_sensor_temperature_celsius{domain="sensor",' 'binary_sensor_state{domain="binary_sensor",'
'entity="sensor.outside_temperature",' 'entity="binary_sensor.window",'
'friendly_name="Outside Temperature"} 15.6' in body 'friendly_name="Window"} 0.0' in body
)
async def test_input_boolean(hass, hass_client):
"""Test prometheus metrics for input_boolean."""
client = await setup_prometheus_client(hass, hass_client, "")
input_boolean1 = DemoSensor(None, "Test", 1, None, None, None, None)
input_boolean1.hass = hass
input_boolean1.entity_id = "input_boolean.test"
await input_boolean1.async_update_ha_state()
input_boolean2 = DemoSensor(None, "Helper", 0, None, None, None, None)
input_boolean2.hass = hass
input_boolean2.entity_id = "input_boolean.helper"
await input_boolean2.async_update_ha_state()
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert (
'input_boolean_state{domain="input_boolean",'
'entity="input_boolean.test",'
'friendly_name="Test"} 1.0' in body
)
assert (
'input_boolean_state{domain="input_boolean",'
'entity="input_boolean.helper",'
'friendly_name="Helper"} 0.0' in body
)
async def test_light(hass, hass_client):
"""Test prometheus metrics for lights."""
client = await setup_prometheus_client(hass, hass_client, "")
light1 = DemoSensor(None, "Desk", 1, None, None, None, None)
light1.hass = hass
light1.entity_id = "light.desk"
await light1.async_update_ha_state()
light2 = DemoSensor(None, "Wall", 0, None, None, None, None)
light2.hass = hass
light2.entity_id = "light.wall"
await light2.async_update_ha_state()
light3 = DemoLight(None, "TV", True, True, 255, None, None)
light3.hass = hass
light3.entity_id = "light.tv"
await light3.async_update_ha_state()
light4 = DemoLight(None, "PC", True, True, 180, None, None)
light4.hass = hass
light4.entity_id = "light.pc"
await light4.async_update_ha_state()
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert (
'light_brightness_percent{domain="light",'
'entity="light.desk",'
'friendly_name="Desk"} 100.0' in body
)
assert (
'light_brightness_percent{domain="light",'
'entity="light.wall",'
'friendly_name="Wall"} 0.0' in body
)
assert (
'light_brightness_percent{domain="light",'
'entity="light.tv",'
'friendly_name="TV"} 100.0' in body
)
assert (
'light_brightness_percent{domain="light",'
'entity="light.pc",'
'friendly_name="PC"} 70.58823529411765' in body
)
async def test_lock(hass, hass_client):
"""Test prometheus metrics for lock."""
assert await async_setup_component(
hass,
"conversation",
{},
)
client = await setup_prometheus_client(hass, hass_client, "")
await async_setup_component(hass, lock.DOMAIN, {"lock": [{"platform": "demo"}]})
await hass.async_block_till_done()
body = await generate_latest_metrics(client)
assert (
'lock_state{domain="lock",'
'entity="lock.front_door",'
'friendly_name="Front Door"} 1.0' in body
)
assert (
'lock_state{domain="lock",'
'entity="lock.kitchen_door",'
'friendly_name="Kitchen Door"} 0.0' in body
) )