Reduce memory pressure from history_stats with large data sets (#73289)

pull/73345/head
J. Nick Koston 2022-06-09 17:49:37 -10:00 committed by GitHub
parent 211e5432ac
commit d3f01f7ea9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 36 additions and 13 deletions

View File

@ -23,6 +23,14 @@ class HistoryStatsState:
period: tuple[datetime.datetime, datetime.datetime]
@dataclass
class HistoryState:
"""A minimal state to avoid holding on to State objects."""
state: str
last_changed: float
class HistoryStats:
"""Manage history stats."""
@ -40,7 +48,7 @@ class HistoryStats:
self.entity_id = entity_id
self._period = (MIN_TIME_UTC, MIN_TIME_UTC)
self._state: HistoryStatsState = HistoryStatsState(None, None, self._period)
self._history_current_period: list[State] = []
self._history_current_period: list[HistoryState] = []
self._previous_run_before_start = False
self._entity_states = set(entity_states)
self._duration = duration
@ -103,20 +111,18 @@ class HistoryStats:
<= floored_timestamp(new_state.last_changed)
<= current_period_end_timestamp
):
self._history_current_period.append(new_state)
self._history_current_period.append(
HistoryState(
new_state.state, new_state.last_changed.timestamp()
)
)
new_data = True
if not new_data and current_period_end_timestamp < now_timestamp:
# If period has not changed and current time after the period end...
# Don't compute anything as the value cannot have changed
return self._state
else:
self._history_current_period = await get_instance(
self.hass
).async_add_executor_job(
self._update_from_database,
current_period_start,
current_period_end,
)
await self._async_history_from_db(current_period_start, current_period_end)
self._previous_run_before_start = False
hours_matched, match_count = self._async_compute_hours_and_changes(
@ -127,7 +133,24 @@ class HistoryStats:
self._state = HistoryStatsState(hours_matched, match_count, self._period)
return self._state
def _update_from_database(
async def _async_history_from_db(
self,
current_period_start: datetime.datetime,
current_period_end: datetime.datetime,
) -> None:
"""Update history data for the current period from the database."""
instance = get_instance(self.hass)
states = await instance.async_add_executor_job(
self._state_changes_during_period,
current_period_start,
current_period_end,
)
self._history_current_period = [
HistoryState(state.state, state.last_changed.timestamp())
for state in states
]
def _state_changes_during_period(
self, start: datetime.datetime, end: datetime.datetime
) -> list[State]:
return history.state_changes_during_period(
@ -155,9 +178,9 @@ class HistoryStats:
match_count = 1 if previous_state_matches else 0
# Make calculations
for item in self._history_current_period:
current_state_matches = item.state in self._entity_states
state_change_timestamp = item.last_changed.timestamp()
for history_state in self._history_current_period:
current_state_matches = history_state.state in self._entity_states
state_change_timestamp = history_state.last_changed
if previous_state_matches:
elapsed += state_change_timestamp - last_state_change_timestamp