mirror of
https://github.com/home-assistant/core.git
synced 2026-09-29 01:48:12 +01:00
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Robert Resch <robert@resch.dev> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
"""CodSpeed benchmarks for the template engine.
|
|
|
|
Templates render on dashboards, in automations and in many entity attributes.
|
|
Both the compile step and the warm render matter, and templates that walk the
|
|
state machine scale with the number of entities.
|
|
|
|
Run locally with: ``pytest benchmarks --codspeed``.
|
|
"""
|
|
|
|
from collections.abc import Callable
|
|
|
|
import pytest
|
|
from pytest_codspeed import BenchmarkFixture
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.template import Template
|
|
|
|
_STATES_TEMPLATE = (
|
|
"{{ (states('sensor.power') | float / 1000) | round(2) }} kW "
|
|
"{{ is_state('binary_sensor.motion', 'on') }} "
|
|
"{{ state_attr('sensor.power', 'unit_of_measurement') }}"
|
|
)
|
|
|
|
|
|
def test_template_compile(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
|
"""Compile a template from source (parse plus codegen).
|
|
|
|
A unique source on every call (a varying Jinja comment) keeps the
|
|
environment's template cache missing, so each run actually compiles instead
|
|
of returning cached bytecode. The comment is stripped during compilation, so
|
|
the cost matches the real template.
|
|
"""
|
|
counter = 0
|
|
|
|
def _compile() -> Template:
|
|
nonlocal counter
|
|
counter += 1
|
|
template = Template(f"{{# {counter} #}}{_STATES_TEMPLATE}", hass)
|
|
template.ensure_valid()
|
|
return template
|
|
|
|
template = benchmark(_compile)
|
|
|
|
assert template.is_static is False
|
|
|
|
|
|
def test_template_render_simple(
|
|
benchmark: BenchmarkFixture, hass: HomeAssistant
|
|
) -> None:
|
|
"""Render a pure-math template (warm), the engine's baseline overhead."""
|
|
template = Template("{{ 1 + 1 }}", hass)
|
|
template.ensure_valid()
|
|
|
|
result = benchmark(template.async_render)
|
|
|
|
assert result == 2
|
|
|
|
|
|
def test_template_render_states(
|
|
benchmark: BenchmarkFixture, hass: HomeAssistant
|
|
) -> None:
|
|
"""Render a template that reads states, attributes and a filter (warm)."""
|
|
hass.states.async_set("sensor.power", "1200", {"unit_of_measurement": "W"})
|
|
hass.states.async_set("binary_sensor.motion", "on")
|
|
template = Template(_STATES_TEMPLATE, hass)
|
|
template.ensure_valid()
|
|
|
|
result = benchmark(template.async_render)
|
|
|
|
assert result.startswith("1.2 kW")
|
|
|
|
|
|
def test_template_render_to_info(
|
|
benchmark: BenchmarkFixture, hass: HomeAssistant
|
|
) -> None:
|
|
"""Render and collect the entity dependency filter (the tracking path).
|
|
|
|
``async_render_to_info`` is what template triggers and template entities use
|
|
to learn which entities to subscribe to.
|
|
"""
|
|
hass.states.async_set("sensor.power", "1200", {"unit_of_measurement": "W"})
|
|
hass.states.async_set("binary_sensor.motion", "on")
|
|
template = Template(_STATES_TEMPLATE, hass)
|
|
template.ensure_valid()
|
|
|
|
info = benchmark(template.async_render_to_info)
|
|
|
|
assert info.entities or info.all_states
|
|
|
|
|
|
@pytest.mark.parametrize("count", [10, 100, 1000])
|
|
def test_template_iterate_states(
|
|
benchmark: BenchmarkFixture,
|
|
populate_states: Callable[[int], None],
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
) -> None:
|
|
"""Render a template that walks every sensor state, at several sizes.
|
|
|
|
This is where an O(n) template touches an O(n) state machine; the cost
|
|
should grow linearly and a worse-than-linear regression should stand out.
|
|
"""
|
|
populate_states(count)
|
|
template = Template(
|
|
"{{ states.sensor | selectattr('state', 'eq', '1') | list | count }}",
|
|
hass,
|
|
)
|
|
template.ensure_valid()
|
|
|
|
result = benchmark(template.async_render)
|
|
|
|
assert result == 1
|