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 .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)

View file

@ -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},
}

View file

@ -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
)
)