Avoid core/supervisor stats API calls when no entities need them (#102362)

This commit is contained in:
J. Nick Koston 2023-10-22 06:40:48 -10:00 committed by GitHub
parent af0b53cc79
commit 82c0610050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 41 deletions

View File

@ -57,9 +57,6 @@ from .addon_manager import AddonError, AddonInfo, AddonManager, AddonState # no
from .addon_panel import async_setup_addon_panel from .addon_panel import async_setup_addon_panel
from .auth import async_setup_auth_view from .auth import async_setup_auth_view
from .const import ( from .const import (
ADDON_UPDATE_CHANGELOG,
ADDON_UPDATE_INFO,
ADDON_UPDATE_STATS,
ATTR_ADDON, ATTR_ADDON,
ATTR_ADDONS, ATTR_ADDONS,
ATTR_AUTO_UPDATE, ATTR_AUTO_UPDATE,
@ -76,6 +73,10 @@ from .const import (
ATTR_STATE, ATTR_STATE,
ATTR_URL, ATTR_URL,
ATTR_VERSION, ATTR_VERSION,
CONTAINER_CHANGELOG,
CONTAINER_INFO,
CONTAINER_STATS,
CORE_CONTAINER,
DATA_KEY_ADDONS, DATA_KEY_ADDONS,
DATA_KEY_CORE, DATA_KEY_CORE,
DATA_KEY_HOST, DATA_KEY_HOST,
@ -83,6 +84,7 @@ from .const import (
DATA_KEY_SUPERVISOR, DATA_KEY_SUPERVISOR,
DATA_KEY_SUPERVISOR_ISSUES, DATA_KEY_SUPERVISOR_ISSUES,
DOMAIN, DOMAIN,
SUPERVISOR_CONTAINER,
SupervisorEntityModel, SupervisorEntityModel,
) )
from .discovery import HassioServiceInfo, async_setup_discovery_view # noqa: F401 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.entry_id = config_entry.entry_id
self.dev_reg = dev_reg self.dev_reg = dev_reg
self.is_hass_os = (get_info(self.hass) or {}).get("hassos") is not None self.is_hass_os = (get_info(self.hass) or {}).get("hassos") is not None
self._enabled_updates_by_addon: defaultdict[ self._container_updates: defaultdict[str, dict[str, set[str]]] = defaultdict(
str, dict[str, set[str]] lambda: defaultdict(set)
] = defaultdict(lambda: defaultdict(set)) )
async def _async_update_data(self) -> dict[str, Any]: async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library.""" """Update data via library."""
@ -910,23 +912,24 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
async def force_data_refresh(self, first_update: bool) -> None: async def force_data_refresh(self, first_update: bool) -> None:
"""Force update of the addon info.""" """Force update of the addon info."""
container_updates = self._container_updates
data = self.hass.data data = self.hass.data
hassio = self.hassio hassio = self.hassio
( updates = {
data[DATA_INFO], DATA_INFO: hassio.get_info(),
data[DATA_CORE_INFO], DATA_CORE_INFO: hassio.get_core_info(),
data[DATA_CORE_STATS], DATA_SUPERVISOR_INFO: hassio.get_supervisor_info(),
data[DATA_SUPERVISOR_INFO], DATA_OS_INFO: hassio.get_os_info(),
data[DATA_SUPERVISOR_STATS], }
data[DATA_OS_INFO], if first_update or CONTAINER_STATS in container_updates[CORE_CONTAINER]:
) = await asyncio.gather( updates[DATA_CORE_STATS] = hassio.get_core_stats()
hassio.get_info(), if first_update or CONTAINER_STATS in container_updates[SUPERVISOR_CONTAINER]:
hassio.get_core_info(), updates[DATA_SUPERVISOR_STATS] = hassio.get_supervisor_stats()
hassio.get_core_stats(),
hassio.get_supervisor_info(), results = await asyncio.gather(*updates.values())
hassio.get_supervisor_stats(), for key, result in zip(updates, results):
hassio.get_os_info(), data[key] = result
)
_addon_data = data[DATA_SUPERVISOR_INFO].get("addons", []) _addon_data = data[DATA_SUPERVISOR_INFO].get("addons", [])
all_addons: list[str] = [] all_addons: list[str] = []
@ -940,37 +943,36 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
# Update add-on info if its the first update or # Update add-on info if its the first update or
# there is at least one entity that needs the data. # 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 # to enable updates for the endpoints they need via
# async_added_to_hass. This ensures that we only update # async_added_to_hass. This ensures that we only update
# the data for the endpoints that are needed to avoid unnecessary # 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. # and throw them away.
# #
enabled_updates_by_addon = self._enabled_updates_by_addon
for data_key, update_func, enabled_key, wanted_addons in ( for data_key, update_func, enabled_key, wanted_addons in (
( (
DATA_ADDONS_STATS, DATA_ADDONS_STATS,
self._update_addon_stats, self._update_addon_stats,
ADDON_UPDATE_STATS, CONTAINER_STATS,
started_addons, started_addons,
), ),
( (
DATA_ADDONS_CHANGELOGS, DATA_ADDONS_CHANGELOGS,
self._update_addon_changelog, self._update_addon_changelog,
ADDON_UPDATE_CHANGELOG, CONTAINER_CHANGELOG,
all_addons, 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( dict(
await asyncio.gather( await asyncio.gather(
*[ *[
update_func(slug) update_func(slug)
for slug in wanted_addons for slug in wanted_addons
if first_update if first_update or enabled_key in container_updates[slug]
or enabled_key in enabled_updates_by_addon[slug]
] ]
) )
) )
@ -1004,11 +1006,11 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
return (slug, None) return (slug, None)
@callback @callback
def async_enable_addon_updates( def async_enable_container_updates(
self, slug: str, entity_id: str, types: set[str] self, slug: str, entity_id: str, types: set[str]
) -> CALLBACK_TYPE: ) -> CALLBACK_TYPE:
"""Enable updates for an add-on.""" """Enable updates for an add-on."""
enabled_updates = self._enabled_updates_by_addon[slug] enabled_updates = self._container_updates[slug]
for key in types: for key in types:
enabled_updates[key].add(entity_id) enabled_updates[key].add(entity_id)

View File

@ -82,19 +82,22 @@ PLACEHOLDER_KEY_COMPONENTS = "components"
ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config" ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config"
ADDON_UPDATE_STATS = "stats" CORE_CONTAINER = "homeassistant"
ADDON_UPDATE_CHANGELOG = "changelog" SUPERVISOR_CONTAINER = "hassio_supervisor"
ADDON_UPDATE_INFO = "info"
CONTAINER_STATS = "stats"
CONTAINER_CHANGELOG = "changelog"
CONTAINER_INFO = "info"
# This is a mapping of which endpoint the key in the addon data # 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 # is obtained from so we know which endpoint to update when the
# coordinator polls for updates. # coordinator polls for updates.
KEY_TO_UPDATE_TYPES: dict[str, set[str]] = { KEY_TO_UPDATE_TYPES: dict[str, set[str]] = {
ATTR_VERSION_LATEST: {ADDON_UPDATE_INFO, ADDON_UPDATE_CHANGELOG}, ATTR_VERSION_LATEST: {CONTAINER_INFO, CONTAINER_CHANGELOG},
ATTR_MEMORY_PERCENT: {ADDON_UPDATE_STATS}, ATTR_MEMORY_PERCENT: {CONTAINER_STATS},
ATTR_CPU_PERCENT: {ADDON_UPDATE_STATS}, ATTR_CPU_PERCENT: {CONTAINER_STATS},
ATTR_VERSION: {ADDON_UPDATE_INFO}, ATTR_VERSION: {CONTAINER_INFO},
ATTR_STATE: {ADDON_UPDATE_INFO}, ATTR_STATE: {CONTAINER_INFO},
} }

View File

@ -10,12 +10,14 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import DOMAIN, HassioDataUpdateCoordinator from . import DOMAIN, HassioDataUpdateCoordinator
from .const import ( from .const import (
ATTR_SLUG, ATTR_SLUG,
CORE_CONTAINER,
DATA_KEY_ADDONS, DATA_KEY_ADDONS,
DATA_KEY_CORE, DATA_KEY_CORE,
DATA_KEY_HOST, DATA_KEY_HOST,
DATA_KEY_OS, DATA_KEY_OS,
DATA_KEY_SUPERVISOR, DATA_KEY_SUPERVISOR,
KEY_TO_UPDATE_TYPES, KEY_TO_UPDATE_TYPES,
SUPERVISOR_CONTAINER,
) )
@ -52,7 +54,7 @@ class HassioAddonEntity(CoordinatorEntity[HassioDataUpdateCoordinator]):
await super().async_added_to_hass() await super().async_added_to_hass()
update_types = KEY_TO_UPDATE_TYPES[self.entity_description.key] update_types = KEY_TO_UPDATE_TYPES[self.entity_description.key]
self.async_on_remove( self.async_on_remove(
self.coordinator.async_enable_addon_updates( self.coordinator.async_enable_container_updates(
self._addon_slug, self.entity_id, update_types self._addon_slug, self.entity_id, update_types
) )
) )
@ -136,6 +138,16 @@ class HassioSupervisorEntity(CoordinatorEntity[HassioDataUpdateCoordinator]):
in self.coordinator.data[DATA_KEY_SUPERVISOR] 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]): class HassioCoreEntity(CoordinatorEntity[HassioDataUpdateCoordinator]):
"""Base Entity for Core.""" """Base Entity for Core."""
@ -161,3 +173,13 @@ class HassioCoreEntity(CoordinatorEntity[HassioDataUpdateCoordinator]):
and DATA_KEY_CORE in self.coordinator.data and DATA_KEY_CORE in self.coordinator.data
and self.entity_description.key in self.coordinator.data[DATA_KEY_CORE] 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
)
)