From 97a476b4755636528abb5c60e812f6f20af493c3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 8 Jul 2025 09:52:44 -0600 Subject: [PATCH] stats --- tests/integration/fixtures/runtime_stats.yaml | 37 ++++++++ tests/integration/test_runtime_stats.py | 88 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/integration/fixtures/runtime_stats.yaml create mode 100644 tests/integration/test_runtime_stats.py diff --git a/tests/integration/fixtures/runtime_stats.yaml b/tests/integration/fixtures/runtime_stats.yaml new file mode 100644 index 0000000000..47ad30d95c --- /dev/null +++ b/tests/integration/fixtures/runtime_stats.yaml @@ -0,0 +1,37 @@ +esphome: + name: runtime-stats-test + +host: + +api: + +logger: + level: INFO + +runtime_stats: + log_interval: 1s + +# Add some components that will execute periodically to generate stats +sensor: + - platform: template + name: "Test Sensor 1" + id: test_sensor_1 + lambda: return 42.0; + update_interval: 0.1s + + - platform: template + name: "Test Sensor 2" + id: test_sensor_2 + lambda: return 24.0; + update_interval: 0.2s + +switch: + - platform: template + name: "Test Switch" + id: test_switch + optimistic: true + +interval: + - interval: 0.5s + then: + - switch.toggle: test_switch diff --git a/tests/integration/test_runtime_stats.py b/tests/integration/test_runtime_stats.py new file mode 100644 index 0000000000..f0af04c2d8 --- /dev/null +++ b/tests/integration/test_runtime_stats.py @@ -0,0 +1,88 @@ +"""Test runtime statistics component.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_runtime_stats( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test runtime stats logs statistics at configured interval and tracks components.""" + loop = asyncio.get_running_loop() + + # Track how many times we see the total stats + stats_count = 0 + first_stats_future = loop.create_future() + second_stats_future = loop.create_future() + + # Track component stats + component_stats_found = set() + + # Patterns to match + total_stats_pattern = re.compile(r"Total stats \(since boot\):") + component_pattern = re.compile(r"^\s+(\w+):\s+count=(\d+),\s+avg=([\d.]+)ms") + + def check_output(line: str) -> None: + """Check log output for runtime stats messages.""" + nonlocal stats_count + + # Debug: print ALL lines to see what we're getting + if "[I]" in line or "[D]" in line or "[W]" in line or "[E]" in line: + print(f"LOG: {line}") + + # Check for total stats line + if total_stats_pattern.search(line): + stats_count += 1 + + if stats_count == 1 and not first_stats_future.done(): + first_stats_future.set_result(True) + elif stats_count == 2 and not second_stats_future.done(): + second_stats_future.set_result(True) + + # Check for component stats + match = component_pattern.match(line) + if match: + component_name = match.group(1) + component_stats_found.add(component_name) + + async with run_compiled(yaml_config, line_callback=check_output): + async with api_client_connected() as client: + # Verify device is connected + device_info = await client.device_info() + assert device_info is not None + + # Wait for first "Total stats" log (should happen at 1s) + try: + await asyncio.wait_for(first_stats_future, timeout=5.0) + except asyncio.TimeoutError: + pytest.fail("First 'Total stats' log not seen within 5 seconds") + + # Wait for second "Total stats" log (should happen at 2s) + try: + await asyncio.wait_for(second_stats_future, timeout=5.0) + except asyncio.TimeoutError: + pytest.fail( + f"Second 'Total stats' log not seen. Total seen: {stats_count}" + ) + + # Verify we got at least 2 stats logs + assert stats_count >= 2, ( + f"Expected at least 2 'Total stats' logs, got {stats_count}" + ) + + # Verify we found stats for our components + assert "sensor" in component_stats_found, ( + f"Expected sensor stats, found: {component_stats_found}" + ) + assert "switch" in component_stats_found, ( + f"Expected switch stats, found: {component_stats_found}" + )