Refactor integration startup time to show wall clock time (#113707)

* Refactor setup time tracking to exclude time waiting on other operations

We now exclude the import time and th time waiting on
base platforms to setup from the setup times

* tweak

* tweak

* tweak

* tweak

* adjust

* fixes

* fixes

* preen

* preen

* tweak

* tweak

* adjust

* tweak

* reduce

* do not count integrtion platforms against their parent integration

* handle legacy tts platforms

* stt as well

* one more wait

* use the same pattern in all the legacy

* fix tts and stt legacy

* fix

* fix

* reduce

* preen

* entity comp does not wait for platforms

* scene blocks as well

* fix test

* test fixes

* coverage

* coverage

* coverage

* fix test

* Update tests/test_setup.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/test_setup.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/setup.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* strip

* strip WAIT_PLATFORM_INTEGRATION

* strip WAIT_PLATFORM_INTEGRATION

* strip WAIT_PLATFORM_INTEGRATION

* strip WAIT_PLATFORM_INTEGRATION

* remove complexity

* Apply suggestions from code review

* no longer works that way

* fixes

* fixes

* fixes

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
J. Nick Koston 2024-03-18 15:45:34 -10:00 committed by GitHub
parent 9be5f3531f
commit c615b52840
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 598 additions and 176 deletions

View file

@ -2,14 +2,14 @@
import asyncio
import threading
from unittest.mock import AsyncMock, Mock, patch
from unittest.mock import ANY, AsyncMock, Mock, patch
import pytest
import voluptuous as vol
from homeassistant import config_entries, loader, setup
from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import CoreState, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import discovery, translation
from homeassistant.helpers.config_validation import (
@ -727,26 +727,244 @@ async def test_integration_only_setup_entry(hass: HomeAssistant) -> None:
assert await setup.async_setup_component(hass, "test_integration_only_entry", {})
async def test_async_start_setup(hass: HomeAssistant) -> None:
"""Test setup started context manager keeps track of setup times."""
with setup.async_start_setup(hass, ["august"]):
assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august"], float)
with setup.async_start_setup(hass, ["august"]):
assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august_2"], float)
async def test_async_start_setup_running(hass: HomeAssistant) -> None:
"""Test setup started context manager does nothing when running."""
assert hass.state is CoreState.running
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
assert "august" not in hass.data[setup.DATA_SETUP_STARTED]
assert isinstance(hass.data[setup.DATA_SETUP_TIME]["august"], float)
assert "august_2" not in hass.data[setup.DATA_SETUP_TIME]
with setup.async_start_setup(
hass, integration="august", phase=setup.SetupPhases.SETUP
):
assert not setup_started
async def test_async_start_setup_platforms(hass: HomeAssistant) -> None:
"""Test setup started context manager keeps track of setup times for platforms."""
with setup.async_start_setup(hass, ["august.sensor"]):
assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august.sensor"], float)
async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None:
"""Test setup started keeps track of setup times with a config entry."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
assert "august" not in hass.data[setup.DATA_SETUP_STARTED]
assert isinstance(hass.data[setup.DATA_SETUP_TIME]["august"], float)
assert "sensor" not in hass.data[setup.DATA_SETUP_TIME]
with setup.async_start_setup(
hass, integration="august", phase=setup.SetupPhases.SETUP
):
assert isinstance(setup_started[("august", None)], float)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
# CONFIG_ENTRY_PLATFORM_SETUP inside of CONFIG_ENTRY_SETUP should not be tracked
assert setup_time["august"] == {
None: {setup.SetupPhases.SETUP: ANY},
"entry_id": {setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY},
}
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
# Platforms outside of CONFIG_ENTRY_SETUP should be tracked
# This simulates a late platform forward
assert setup_time["august"] == {
None: {setup.SetupPhases.SETUP: ANY},
"entry_id": {
setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY,
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: ANY,
},
}
with setup.async_start_setup(
hass,
integration="august",
group="entry_id2",
phase=setup.SetupPhases.CONFIG_ENTRY_SETUP,
):
assert isinstance(setup_started[("august", "entry_id2")], float)
# We wrap places where we wait for other components
# or the import of a module with async_freeze_setup
# so we can subtract the time waited from the total setup time
with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP):
await asyncio.sleep(0)
# Wait time should be added if freeze_setup is used
assert setup_time["august"] == {
None: {setup.SetupPhases.SETUP: ANY},
"entry_id": {
setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY,
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: ANY,
},
"entry_id2": {
setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY,
setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP: ANY,
},
}
async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None:
"""Test setup started context manager keeps track of setup times with modern yaml."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
hass, integration="command_line", phase=setup.SetupPhases.SETUP
):
assert isinstance(setup_started[("command_line", None)], float)
assert setup_time["command_line"] == {
None: {setup.SetupPhases.SETUP: ANY},
}
async def test_async_start_setup_platform_integration(hass: HomeAssistant) -> None:
"""Test setup started keeps track of setup times a platform integration."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
hass, integration="sensor", phase=setup.SetupPhases.SETUP
):
assert isinstance(setup_started[("sensor", None)], float)
# Platform integration setups happen in another task
with setup.async_start_setup(
hass,
integration="filter",
group="123456",
phase=setup.SetupPhases.PLATFORM_SETUP,
):
assert isinstance(setup_started[("filter", "123456")], float)
assert setup_time["sensor"] == {
None: {
setup.SetupPhases.SETUP: ANY,
},
}
assert setup_time["filter"] == {
"123456": {
setup.SetupPhases.PLATFORM_SETUP: ANY,
},
}
async def test_async_start_setup_legacy_platform_integration(
hass: HomeAssistant,
) -> None:
"""Test setup started keeps track of setup times for a legacy platform integration."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
hass, integration="notify", phase=setup.SetupPhases.SETUP
):
assert isinstance(setup_started[("notify", None)], float)
with setup.async_start_setup(
hass,
integration="legacy_notify_integration",
group="123456",
phase=setup.SetupPhases.PLATFORM_SETUP,
):
assert isinstance(setup_started[("legacy_notify_integration", "123456")], float)
assert setup_time["notify"] == {
None: {
setup.SetupPhases.SETUP: ANY,
},
}
assert setup_time["legacy_notify_integration"] == {
"123456": {
setup.SetupPhases.PLATFORM_SETUP: ANY,
},
}
async def test_async_start_setup_simple_integration_end_to_end(
hass: HomeAssistant,
) -> None:
"""Test end to end timings for a simple integration with no platforms."""
hass.set_state(CoreState.not_running)
mock_integration(
hass,
MockModule(
"test_integration_no_platforms",
setup=False,
async_setup_entry=AsyncMock(return_value=True),
),
)
assert await setup.async_setup_component(hass, "test_integration_no_platforms", {})
await hass.async_block_till_done()
assert setup.async_get_setup_timings(hass) == {
"test_integration_no_platforms": ANY,
}
async def test_async_get_setup_timings(hass) -> None:
"""Test we can get the setup timings from the setup time data."""
setup_time = setup._setup_times(hass)
# Mock setup time data
setup_time.update(
{
"august": {
None: {setup.SetupPhases.SETUP: 1},
"entry_id": {
setup.SetupPhases.CONFIG_ENTRY_SETUP: 1,
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: 4,
},
"entry_id2": {
setup.SetupPhases.CONFIG_ENTRY_SETUP: 7,
setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP: -5,
},
},
"notify": {
None: {
setup.SetupPhases.SETUP: 2,
},
},
"legacy_notify_integration": {
"123456": {
setup.SetupPhases.PLATFORM_SETUP: 3,
},
},
"sensor": {
None: {
setup.SetupPhases.SETUP: 1,
},
},
"filter": {
"123456": {
setup.SetupPhases.PLATFORM_SETUP: 2,
},
},
}
)
assert setup.async_get_setup_timings(hass) == {
"august": 6,
"notify": 2,
"legacy_notify_integration": 3,
"sensor": 1,
"filter": 2,
}
async def test_setup_config_entry_from_yaml(