mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Avoid creating Prometheus metrics for non-numeric states (#127262)
This commit is contained in:
parent
cca6965cd1
commit
6d48316436
@ -334,8 +334,8 @@ class PrometheusMetrics:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def state_as_number(state: State) -> float:
|
def state_as_number(state: State) -> float | None:
|
||||||
"""Return a state casted to a float."""
|
"""Return state as a float, or None if state cannot be converted."""
|
||||||
try:
|
try:
|
||||||
if state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP:
|
if state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP:
|
||||||
value = as_timestamp(state.state)
|
value = as_timestamp(state.state)
|
||||||
@ -343,7 +343,7 @@ class PrometheusMetrics:
|
|||||||
value = state_helper.state_as_number(state)
|
value = state_helper.state_as_number(state)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.debug("Could not convert %s to float", state)
|
_LOGGER.debug("Could not convert %s to float", state)
|
||||||
value = 0
|
value = None
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -373,7 +373,7 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"State of the binary sensor (0/1)",
|
"State of the binary sensor (0/1)",
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_input_boolean(self, state: State) -> None:
|
def _handle_input_boolean(self, state: State) -> None:
|
||||||
@ -382,7 +382,7 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"State of the input boolean (0/1)",
|
"State of the input boolean (0/1)",
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _numeric_handler(self, state: State, domain: str, title: str) -> None:
|
def _numeric_handler(self, state: State, domain: str, title: str) -> None:
|
||||||
@ -399,8 +399,7 @@ class PrometheusMetrics:
|
|||||||
f"State of the {title}",
|
f"State of the {title}",
|
||||||
)
|
)
|
||||||
|
|
||||||
with suppress(ValueError):
|
if (value := self.state_as_number(state)) is not None:
|
||||||
value = self.state_as_number(state)
|
|
||||||
if (
|
if (
|
||||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
== UnitOfTemperature.FAHRENHEIT
|
== UnitOfTemperature.FAHRENHEIT
|
||||||
@ -422,14 +421,14 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"State of the device tracker (0/1)",
|
"State of the device tracker (0/1)",
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_person(self, state: State) -> None:
|
def _handle_person(self, state: State) -> None:
|
||||||
metric = self._metric(
|
metric = self._metric(
|
||||||
"person_state", prometheus_client.Gauge, "State of the person (0/1)"
|
"person_state", prometheus_client.Gauge, "State of the person (0/1)"
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_cover(self, state: State) -> None:
|
def _handle_cover(self, state: State) -> None:
|
||||||
@ -471,22 +470,18 @@ class PrometheusMetrics:
|
|||||||
"Light brightness percentage (0..100)",
|
"Light brightness percentage (0..100)",
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if (value := self.state_as_number(state)) is not None:
|
||||||
brightness = state.attributes.get(ATTR_BRIGHTNESS)
|
brightness = state.attributes.get(ATTR_BRIGHTNESS)
|
||||||
if state.state == STATE_ON and brightness is not None:
|
if state.state == STATE_ON and brightness is not None:
|
||||||
value = brightness / 255.0
|
value = float(brightness) / 255.0
|
||||||
else:
|
|
||||||
value = self.state_as_number(state)
|
|
||||||
value = value * 100
|
value = value * 100
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _handle_lock(self, state: State) -> None:
|
def _handle_lock(self, state: State) -> None:
|
||||||
metric = self._metric(
|
metric = self._metric(
|
||||||
"lock_state", prometheus_client.Gauge, "State of the lock (0/1)"
|
"lock_state", prometheus_client.Gauge, "State of the lock (0/1)"
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_climate_temp(
|
def _handle_climate_temp(
|
||||||
@ -599,11 +594,8 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"State of the humidifier (0/1)",
|
"State of the humidifier (0/1)",
|
||||||
)
|
)
|
||||||
try:
|
if (value := self.state_as_number(state)) is not None:
|
||||||
value = self.state_as_number(state)
|
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
current_mode = state.attributes.get(ATTR_MODE)
|
current_mode = state.attributes.get(ATTR_MODE)
|
||||||
available_modes = state.attributes.get(ATTR_AVAILABLE_MODES)
|
available_modes = state.attributes.get(ATTR_AVAILABLE_MODES)
|
||||||
@ -634,8 +626,7 @@ class PrometheusMetrics:
|
|||||||
|
|
||||||
_metric = self._metric(metric, prometheus_client.Gauge, documentation)
|
_metric = self._metric(metric, prometheus_client.Gauge, documentation)
|
||||||
|
|
||||||
try:
|
if (value := self.state_as_number(state)) is not None:
|
||||||
value = self.state_as_number(state)
|
|
||||||
if (
|
if (
|
||||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
== UnitOfTemperature.FAHRENHEIT
|
== UnitOfTemperature.FAHRENHEIT
|
||||||
@ -644,8 +635,6 @@ class PrometheusMetrics:
|
|||||||
value, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
|
value, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
|
||||||
)
|
)
|
||||||
_metric.labels(**self._labels(state)).set(value)
|
_metric.labels(**self._labels(state)).set(value)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._battery(state)
|
self._battery(state)
|
||||||
|
|
||||||
@ -684,14 +673,9 @@ class PrometheusMetrics:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _sensor_fallback_metric(state: State, unit: str | None) -> str | None:
|
def _sensor_fallback_metric(state: State, unit: str | None) -> str | None:
|
||||||
"""Get metric from fallback logic for compatibility."""
|
"""Get metric from fallback logic for compatibility."""
|
||||||
if unit in (None, ""):
|
if unit not in (None, ""):
|
||||||
try:
|
|
||||||
state_helper.state_as_number(state)
|
|
||||||
except ValueError:
|
|
||||||
_LOGGER.debug("Unsupported sensor: %s", state.entity_id)
|
|
||||||
return None
|
|
||||||
return "sensor_state"
|
|
||||||
return f"sensor_unit_{unit}"
|
return f"sensor_unit_{unit}"
|
||||||
|
return "sensor_state"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unit_string(unit: str | None) -> str | None:
|
def _unit_string(unit: str | None) -> str | None:
|
||||||
@ -713,11 +697,8 @@ class PrometheusMetrics:
|
|||||||
"switch_state", prometheus_client.Gauge, "State of the switch (0/1)"
|
"switch_state", prometheus_client.Gauge, "State of the switch (0/1)"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if (value := self.state_as_number(state)) is not None:
|
||||||
value = self.state_as_number(state)
|
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._handle_attributes(state)
|
self._handle_attributes(state)
|
||||||
|
|
||||||
@ -726,11 +707,8 @@ class PrometheusMetrics:
|
|||||||
"fan_state", prometheus_client.Gauge, "State of the fan (0/1)"
|
"fan_state", prometheus_client.Gauge, "State of the fan (0/1)"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if (value := self.state_as_number(state)) is not None:
|
||||||
value = self.state_as_number(state)
|
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
fan_speed_percent = state.attributes.get(ATTR_PERCENTAGE)
|
fan_speed_percent = state.attributes.get(ATTR_PERCENTAGE)
|
||||||
if fan_speed_percent is not None:
|
if fan_speed_percent is not None:
|
||||||
@ -796,8 +774,8 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"Value of counter entities",
|
"Value of counter entities",
|
||||||
)
|
)
|
||||||
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(self.state_as_number(state))
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_update(self, state: State) -> None:
|
def _handle_update(self, state: State) -> None:
|
||||||
metric = self._metric(
|
metric = self._metric(
|
||||||
@ -805,7 +783,7 @@ class PrometheusMetrics:
|
|||||||
prometheus_client.Gauge,
|
prometheus_client.Gauge,
|
||||||
"Update state, indicating if an update is available (0/1)",
|
"Update state, indicating if an update is available (0/1)",
|
||||||
)
|
)
|
||||||
value = self.state_as_number(state)
|
if (value := self.state_as_number(state)) is not None:
|
||||||
metric.labels(**self._labels(state)).set(value)
|
metric.labels(**self._labels(state)).set(value)
|
||||||
|
|
||||||
def _handle_alarm_control_panel(self, state: State) -> None:
|
def _handle_alarm_control_panel(self, state: State) -> None:
|
||||||
|
@ -642,7 +642,7 @@ async def test_sensor_without_unit(
|
|||||||
domain="sensor",
|
domain="sensor",
|
||||||
friendly_name="Text Unit",
|
friendly_name="Text Unit",
|
||||||
entity="sensor.text_unit",
|
entity="sensor.text_unit",
|
||||||
).withValue(0.0).assert_in_metrics(body)
|
).assert_not_in_metrics(body)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("namespace", [""])
|
@pytest.mark.parametrize("namespace", [""])
|
||||||
@ -716,6 +716,13 @@ async def test_input_number(
|
|||||||
entity="input_number.target_temperature",
|
entity="input_number.target_temperature",
|
||||||
).withValue(22.7).assert_in_metrics(body)
|
).withValue(22.7).assert_in_metrics(body)
|
||||||
|
|
||||||
|
EntityMetric(
|
||||||
|
metric_name="input_number_state_celsius",
|
||||||
|
domain="input_number",
|
||||||
|
friendly_name="Converted temperature",
|
||||||
|
entity="input_number.converted_temperature",
|
||||||
|
).withValue(100).assert_in_metrics(body)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("namespace", [""])
|
@pytest.mark.parametrize("namespace", [""])
|
||||||
async def test_number(
|
async def test_number(
|
||||||
@ -2207,6 +2214,17 @@ async def input_number_fixture(
|
|||||||
set_state_with_entry(hass, input_number_3, 22.7)
|
set_state_with_entry(hass, input_number_3, 22.7)
|
||||||
data["input_number_3"] = input_number_3
|
data["input_number_3"] = input_number_3
|
||||||
|
|
||||||
|
input_number_4 = entity_registry.async_get_or_create(
|
||||||
|
domain=input_number.DOMAIN,
|
||||||
|
platform="test",
|
||||||
|
unique_id="input_number_4",
|
||||||
|
suggested_object_id="converted_temperature",
|
||||||
|
original_name="Converted temperature",
|
||||||
|
unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
)
|
||||||
|
set_state_with_entry(hass, input_number_4, 212)
|
||||||
|
data["input_number_4"] = input_number_4
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user