From 82c0610050b70ebb921f5debde5e92ff97cee3e0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Oct 2023 06:40:48 -1000 Subject: [PATCH] Avoid core/supervisor stats API calls when no entities need them (#102362) --- homeassistant/components/hassio/__init__.py | 66 +++++++++++---------- homeassistant/components/hassio/const.py | 19 +++--- homeassistant/components/hassio/entity.py | 24 +++++++- 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index a471bf820c8..392671a5471 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -57,9 +57,6 @@ from .addon_manager import AddonError, AddonInfo, AddonManager, AddonState # no from .addon_panel import async_setup_addon_panel from .auth import async_setup_auth_view from .const import ( - ADDON_UPDATE_CHANGELOG, - ADDON_UPDATE_INFO, - ADDON_UPDATE_STATS, ATTR_ADDON, ATTR_ADDONS, ATTR_AUTO_UPDATE, @@ -76,6 +73,10 @@ from .const import ( ATTR_STATE, ATTR_URL, ATTR_VERSION, + CONTAINER_CHANGELOG, + CONTAINER_INFO, + CONTAINER_STATS, + CORE_CONTAINER, DATA_KEY_ADDONS, DATA_KEY_CORE, DATA_KEY_HOST, @@ -83,6 +84,7 @@ from .const import ( DATA_KEY_SUPERVISOR, DATA_KEY_SUPERVISOR_ISSUES, DOMAIN, + SUPERVISOR_CONTAINER, SupervisorEntityModel, ) from .discovery import HassioServiceInfo, async_setup_discovery_view # noqa: F401 @@ -805,9 +807,9 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): self.entry_id = config_entry.entry_id self.dev_reg = dev_reg self.is_hass_os = (get_info(self.hass) or {}).get("hassos") is not None - self._enabled_updates_by_addon: defaultdict[ - str, dict[str, set[str]] - ] = defaultdict(lambda: defaultdict(set)) + self._container_updates: defaultdict[str, dict[str, set[str]]] = defaultdict( + lambda: defaultdict(set) + ) async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" @@ -910,23 +912,24 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): async def force_data_refresh(self, first_update: bool) -> None: """Force update of the addon info.""" + container_updates = self._container_updates + data = self.hass.data hassio = self.hassio - ( - data[DATA_INFO], - data[DATA_CORE_INFO], - data[DATA_CORE_STATS], - data[DATA_SUPERVISOR_INFO], - data[DATA_SUPERVISOR_STATS], - data[DATA_OS_INFO], - ) = await asyncio.gather( - hassio.get_info(), - hassio.get_core_info(), - hassio.get_core_stats(), - hassio.get_supervisor_info(), - hassio.get_supervisor_stats(), - hassio.get_os_info(), - ) + updates = { + DATA_INFO: hassio.get_info(), + DATA_CORE_INFO: hassio.get_core_info(), + DATA_SUPERVISOR_INFO: hassio.get_supervisor_info(), + DATA_OS_INFO: hassio.get_os_info(), + } + if first_update or CONTAINER_STATS in container_updates[CORE_CONTAINER]: + updates[DATA_CORE_STATS] = hassio.get_core_stats() + if first_update or CONTAINER_STATS in container_updates[SUPERVISOR_CONTAINER]: + updates[DATA_SUPERVISOR_STATS] = hassio.get_supervisor_stats() + + results = await asyncio.gather(*updates.values()) + for key, result in zip(updates, results): + data[key] = result _addon_data = data[DATA_SUPERVISOR_INFO].get("addons", []) all_addons: list[str] = [] @@ -940,37 +943,36 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): # Update add-on info if its the first update or # there is at least one entity that needs the data. # - # When entities are added they call async_enable_addon_updates + # When entities are added they call async_enable_container_updates # to enable updates for the endpoints they need via # async_added_to_hass. This ensures that we only update # the data for the endpoints that are needed to avoid unnecessary - # API calls since otherwise we would fetch stats for all add-ons + # API calls since otherwise we would fetch stats for all containers # and throw them away. # - enabled_updates_by_addon = self._enabled_updates_by_addon for data_key, update_func, enabled_key, wanted_addons in ( ( DATA_ADDONS_STATS, self._update_addon_stats, - ADDON_UPDATE_STATS, + CONTAINER_STATS, started_addons, ), ( DATA_ADDONS_CHANGELOGS, self._update_addon_changelog, - ADDON_UPDATE_CHANGELOG, + CONTAINER_CHANGELOG, all_addons, ), - (DATA_ADDONS_INFO, self._update_addon_info, ADDON_UPDATE_INFO, all_addons), + (DATA_ADDONS_INFO, self._update_addon_info, CONTAINER_INFO, all_addons), ): - data.setdefault(data_key, {}).update( + container_data: dict[str, Any] = data.setdefault(data_key, {}) + container_data.update( dict( await asyncio.gather( *[ update_func(slug) for slug in wanted_addons - if first_update - or enabled_key in enabled_updates_by_addon[slug] + if first_update or enabled_key in container_updates[slug] ] ) ) @@ -1004,11 +1006,11 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): return (slug, None) @callback - def async_enable_addon_updates( + def async_enable_container_updates( self, slug: str, entity_id: str, types: set[str] ) -> CALLBACK_TYPE: """Enable updates for an add-on.""" - enabled_updates = self._enabled_updates_by_addon[slug] + enabled_updates = self._container_updates[slug] for key in types: enabled_updates[key].add(entity_id) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 3d2ff7b0cff..9b52057a914 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -82,19 +82,22 @@ PLACEHOLDER_KEY_COMPONENTS = "components" ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config" -ADDON_UPDATE_STATS = "stats" -ADDON_UPDATE_CHANGELOG = "changelog" -ADDON_UPDATE_INFO = "info" +CORE_CONTAINER = "homeassistant" +SUPERVISOR_CONTAINER = "hassio_supervisor" + +CONTAINER_STATS = "stats" +CONTAINER_CHANGELOG = "changelog" +CONTAINER_INFO = "info" # This is a mapping of which endpoint the key in the addon data # is obtained from so we know which endpoint to update when the # coordinator polls for updates. KEY_TO_UPDATE_TYPES: dict[str, set[str]] = { - ATTR_VERSION_LATEST: {ADDON_UPDATE_INFO, ADDON_UPDATE_CHANGELOG}, - ATTR_MEMORY_PERCENT: {ADDON_UPDATE_STATS}, - ATTR_CPU_PERCENT: {ADDON_UPDATE_STATS}, - ATTR_VERSION: {ADDON_UPDATE_INFO}, - ATTR_STATE: {ADDON_UPDATE_INFO}, + ATTR_VERSION_LATEST: {CONTAINER_INFO, CONTAINER_CHANGELOG}, + ATTR_MEMORY_PERCENT: {CONTAINER_STATS}, + ATTR_CPU_PERCENT: {CONTAINER_STATS}, + ATTR_VERSION: {CONTAINER_INFO}, + ATTR_STATE: {CONTAINER_INFO}, } diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py index 5421a3ea953..16e418d91d5 100644 --- a/homeassistant/components/hassio/entity.py +++ b/homeassistant/components/hassio/entity.py @@ -10,12 +10,14 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import DOMAIN, HassioDataUpdateCoordinator from .const import ( ATTR_SLUG, + CORE_CONTAINER, DATA_KEY_ADDONS, DATA_KEY_CORE, DATA_KEY_HOST, DATA_KEY_OS, DATA_KEY_SUPERVISOR, KEY_TO_UPDATE_TYPES, + SUPERVISOR_CONTAINER, ) @@ -52,7 +54,7 @@ class HassioAddonEntity(CoordinatorEntity[HassioDataUpdateCoordinator]): await super().async_added_to_hass() update_types = KEY_TO_UPDATE_TYPES[self.entity_description.key] self.async_on_remove( - self.coordinator.async_enable_addon_updates( + self.coordinator.async_enable_container_updates( self._addon_slug, self.entity_id, update_types ) ) @@ -136,6 +138,16 @@ class HassioSupervisorEntity(CoordinatorEntity[HassioDataUpdateCoordinator]): in self.coordinator.data[DATA_KEY_SUPERVISOR] ) + async def async_added_to_hass(self) -> None: + """Subscribe to updates.""" + await super().async_added_to_hass() + update_types = KEY_TO_UPDATE_TYPES[self.entity_description.key] + self.async_on_remove( + self.coordinator.async_enable_container_updates( + SUPERVISOR_CONTAINER, self.entity_id, update_types + ) + ) + class HassioCoreEntity(CoordinatorEntity[HassioDataUpdateCoordinator]): """Base Entity for Core.""" @@ -161,3 +173,13 @@ class HassioCoreEntity(CoordinatorEntity[HassioDataUpdateCoordinator]): and DATA_KEY_CORE in self.coordinator.data and self.entity_description.key in self.coordinator.data[DATA_KEY_CORE] ) + + async def async_added_to_hass(self) -> None: + """Subscribe to updates.""" + await super().async_added_to_hass() + update_types = KEY_TO_UPDATE_TYPES[self.entity_description.key] + self.async_on_remove( + self.coordinator.async_enable_container_updates( + CORE_CONTAINER, self.entity_id, update_types + ) + )