Reduce memory pressure from history_stats with large data sets (#73289)
parent
211e5432ac
commit
d3f01f7ea9
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue