1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 12:59:34 +00:00

Fix setup timings when config entry platform loads are not awaited (#113959)

* Move setup time logging into the context manager

We were fetching the time twice but since the context
manager already has the timing, move it there

* remove log setup assertions from integration test

* tweak logging to give us better data for tracking issues

* redundant

* adjust

* preen

* fixes

* adjust

* make api change internal so nobody uses it

* coverage

* fix test

* fix more tests

* coverage

* more tests assuming internal calls

* fix more

* adjust

* adjust

* fix axis tests

* fix broadlink -- it does not call async_forward_entry_setup

* missed some

* remove useless patch

* rename, detect it both ways

* clear

* debug

* try to fix

* handle phase finishing out while paused

* where its set does not need to know its late as that is an implemenation detail of setup

* where its set does not need to know its late as that is an implemenation detail of setup

* tweak

* simplify

* reduce complexity

* revert order change as it makes review harder

* revert naming changes as it makes review harder

* improve comment

* improve debug

* late dispatch test

* test the other way as well

* Update setup.py

* Update setup.py

* Update setup.py

* simplify

* reduce
This commit is contained in:
J. Nick Koston
2024-03-23 09:26:38 -10:00
committed by GitHub
parent a4f52cc622
commit 4f18f0d902
16 changed files with 305 additions and 116 deletions

View File

@@ -10,7 +10,6 @@ import contextvars
from enum import StrEnum
import logging.handlers
import time
from timeit import default_timer as timer
from types import ModuleType
from typing import Any, Final, TypedDict
@@ -351,7 +350,6 @@ async def _async_setup_component( # noqa: C901
},
)
start = timer()
_LOGGER.info("Setting up %s", domain)
integration_set = {domain}
@@ -412,11 +410,8 @@ async def _async_setup_component( # noqa: C901
async_notify_setup_error(hass, domain, integration.documentation)
return False
finally:
end = timer()
if warn_task:
warn_task.cancel()
_LOGGER.info("Setup of domain %s took %.1f seconds", domain, end - start)
if result is False:
log_error("Integration failed to initialize.")
return False
@@ -663,6 +658,15 @@ class SetupPhases(StrEnum):
"""Wait time for the packages to import."""
def _setup_started(
hass: core.HomeAssistant,
) -> dict[tuple[str, str | None], float]:
"""Return the setup started dict."""
if DATA_SETUP_STARTED not in hass.data:
hass.data[DATA_SETUP_STARTED] = {}
return hass.data[DATA_SETUP_STARTED] # type: ignore[no-any-return]
@contextlib.contextmanager
def async_pause_setup(
hass: core.HomeAssistant, phase: SetupPhases
@@ -673,7 +677,9 @@ def async_pause_setup(
setting up the base components so we can subtract it
from the total setup time.
"""
if not (running := current_setup_group.get()):
if not (running := current_setup_group.get()) or running not in _setup_started(
hass
):
# This means we are likely in a late platform setup
# that is running in a task so we do not want
# to subtract out the time later as nothing is waiting
@@ -689,6 +695,13 @@ def async_pause_setup(
integration, group = running
# Add negative time for the time we waited
_setup_times(hass)[integration][group][phase] = -time_taken
_LOGGER.debug(
"Adding wait for %s for %s (%s) of %.2f",
phase,
integration,
group,
time_taken,
)
def _setup_times(
@@ -726,8 +739,7 @@ def async_start_setup(
yield
return
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {})
setup_started = _setup_started(hass)
current = (integration, group)
if current in setup_started:
# We are already inside another async_start_setup, this like means we
@@ -745,7 +757,26 @@ def async_start_setup(
finally:
time_taken = time.monotonic() - started
del setup_started[current]
_setup_times(hass)[integration][group][phase] = time_taken
group_setup_times = _setup_times(hass)[integration][group]
# We may see the phase multiple times if there are multiple
# platforms, but we only care about the longest time.
group_setup_times[phase] = max(group_setup_times[phase], time_taken)
if group is None:
_LOGGER.info(
"Setup of domain %s took %.2f seconds", integration, time_taken
)
elif _LOGGER.isEnabledFor(logging.DEBUG):
wait_time = -sum(value for value in group_setup_times.values() if value < 0)
calculated_time = time_taken - wait_time
_LOGGER.debug(
"Phase %s for %s (%s) took %.2fs (elapsed=%.2fs) (wait_time=%.2fs)",
phase,
integration,
group,
calculated_time,
time_taken,
wait_time,
)
@callback