Add slots to the StateMachine class (#95849)
This commit is contained in:
parent
39dcb5a2b5
commit
b2e708834f
5 changed files with 63 additions and 32 deletions
|
@ -553,10 +553,10 @@ class Recorder(threading.Thread):
|
||||||
If the number of entities has increased, increase the size of the LRU
|
If the number of entities has increased, increase the size of the LRU
|
||||||
cache to avoid thrashing.
|
cache to avoid thrashing.
|
||||||
"""
|
"""
|
||||||
new_size = self.hass.states.async_entity_ids_count() * 2
|
if new_size := self.hass.states.async_entity_ids_count() * 2:
|
||||||
self.state_attributes_manager.adjust_lru_size(new_size)
|
self.state_attributes_manager.adjust_lru_size(new_size)
|
||||||
self.states_meta_manager.adjust_lru_size(new_size)
|
self.states_meta_manager.adjust_lru_size(new_size)
|
||||||
self.statistics_meta_manager.adjust_lru_size(new_size)
|
self.statistics_meta_manager.adjust_lru_size(new_size)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_periodic_statistics(self) -> None:
|
def async_periodic_statistics(self) -> None:
|
||||||
|
|
|
@ -1410,6 +1410,8 @@ class State:
|
||||||
class StateMachine:
|
class StateMachine:
|
||||||
"""Helper class that tracks the state of different entities."""
|
"""Helper class that tracks the state of different entities."""
|
||||||
|
|
||||||
|
__slots__ = ("_states", "_reservations", "_bus", "_loop")
|
||||||
|
|
||||||
def __init__(self, bus: EventBus, loop: asyncio.events.AbstractEventLoop) -> None:
|
def __init__(self, bus: EventBus, loop: asyncio.events.AbstractEventLoop) -> None:
|
||||||
"""Initialize state machine."""
|
"""Initialize state machine."""
|
||||||
self._states: dict[str, State] = {}
|
self._states: dict[str, State] = {}
|
||||||
|
|
|
@ -56,6 +56,10 @@ from homeassistant.components.recorder.services import (
|
||||||
SERVICE_PURGE,
|
SERVICE_PURGE,
|
||||||
SERVICE_PURGE_ENTITIES,
|
SERVICE_PURGE_ENTITIES,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.recorder.table_managers import (
|
||||||
|
state_attributes as state_attributes_table_manager,
|
||||||
|
states_meta as states_meta_table_manager,
|
||||||
|
)
|
||||||
from homeassistant.components.recorder.util import session_scope
|
from homeassistant.components.recorder.util import session_scope
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_COMPONENT_LOADED,
|
EVENT_COMPONENT_LOADED,
|
||||||
|
@ -93,6 +97,15 @@ from tests.common import (
|
||||||
from tests.typing import RecorderInstanceGenerator
|
from tests.typing import RecorderInstanceGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def small_cache_size() -> None:
|
||||||
|
"""Patch the default cache size to 8."""
|
||||||
|
with patch.object(state_attributes_table_manager, "CACHE_SIZE", 8), patch.object(
|
||||||
|
states_meta_table_manager, "CACHE_SIZE", 8
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
def _default_recorder(hass):
|
def _default_recorder(hass):
|
||||||
"""Return a recorder with reasonable defaults."""
|
"""Return a recorder with reasonable defaults."""
|
||||||
return Recorder(
|
return Recorder(
|
||||||
|
@ -2022,13 +2035,10 @@ def test_deduplication_event_data_inside_commit_interval(
|
||||||
assert all(event.data_id == first_data_id for event in events)
|
assert all(event.data_id == first_data_id for event in events)
|
||||||
|
|
||||||
|
|
||||||
# Patch CACHE_SIZE since otherwise
|
|
||||||
# the CI can fail because the test takes too long to run
|
|
||||||
@patch(
|
|
||||||
"homeassistant.components.recorder.table_managers.state_attributes.CACHE_SIZE", 5
|
|
||||||
)
|
|
||||||
def test_deduplication_state_attributes_inside_commit_interval(
|
def test_deduplication_state_attributes_inside_commit_interval(
|
||||||
hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture
|
small_cache_size: None,
|
||||||
|
hass_recorder: Callable[..., HomeAssistant],
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test deduplication of state attributes inside the commit interval."""
|
"""Test deduplication of state attributes inside the commit interval."""
|
||||||
hass = hass_recorder()
|
hass = hass_recorder()
|
||||||
|
@ -2306,16 +2316,15 @@ async def test_excluding_attributes_by_integration(
|
||||||
|
|
||||||
|
|
||||||
async def test_lru_increases_with_many_entities(
|
async def test_lru_increases_with_many_entities(
|
||||||
recorder_mock: Recorder, hass: HomeAssistant
|
small_cache_size: None, recorder_mock: Recorder, hass: HomeAssistant
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that the recorder's internal LRU cache increases with many entities."""
|
"""Test that the recorder's internal LRU cache increases with many entities."""
|
||||||
# We do not actually want to record 4096 entities so we mock the entity count
|
mock_entity_count = 16
|
||||||
mock_entity_count = 4096
|
for idx in range(mock_entity_count):
|
||||||
with patch.object(
|
hass.states.async_set(f"test.entity{idx}", "on")
|
||||||
hass.states, "async_entity_ids_count", return_value=mock_entity_count
|
|
||||||
):
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
await async_wait_recording_done(hass)
|
||||||
await async_wait_recording_done(hass)
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
recorder_mock.state_attributes_manager._id_map.get_size()
|
recorder_mock.state_attributes_manager._id_map.get_size()
|
||||||
|
|
|
@ -232,17 +232,21 @@ async def test_hass_starting(hass: HomeAssistant) -> None:
|
||||||
entity.hass = hass
|
entity.hass = hass
|
||||||
entity.entity_id = "input_boolean.b1"
|
entity.entity_id = "input_boolean.b1"
|
||||||
|
|
||||||
|
all_states = hass.states.async_all()
|
||||||
|
assert len(all_states) == 0
|
||||||
|
hass.states.async_set("input_boolean.b1", "on")
|
||||||
|
|
||||||
# Mock that only b1 is present this run
|
# Mock that only b1 is present this run
|
||||||
states = [State("input_boolean.b1", "on")]
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.restore_state.Store.async_save"
|
"homeassistant.helpers.restore_state.Store.async_save"
|
||||||
) as mock_write_data, patch.object(hass.states, "async_all", return_value=states):
|
) as mock_write_data:
|
||||||
state = await entity.async_get_last_state()
|
state = await entity.async_get_last_state()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.entity_id == "input_boolean.b1"
|
assert state.entity_id == "input_boolean.b1"
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
hass.states.async_remove("input_boolean.b1")
|
||||||
|
|
||||||
# Assert that no data was written yet, since hass is still starting.
|
# Assert that no data was written yet, since hass is still starting.
|
||||||
assert not mock_write_data.called
|
assert not mock_write_data.called
|
||||||
|
@ -293,15 +297,20 @@ async def test_dump_data(hass: HomeAssistant) -> None:
|
||||||
"input_boolean.b5": StoredState(State("input_boolean.b5", "off"), None, now),
|
"input_boolean.b5": StoredState(State("input_boolean.b5", "off"), None, now),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
hass.states.async_set(state.entity_id, state.state, state.attributes)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.restore_state.Store.async_save"
|
"homeassistant.helpers.restore_state.Store.async_save"
|
||||||
) as mock_write_data, patch.object(hass.states, "async_all", return_value=states):
|
) as mock_write_data:
|
||||||
await data.async_dump_states()
|
await data.async_dump_states()
|
||||||
|
|
||||||
assert mock_write_data.called
|
assert mock_write_data.called
|
||||||
args = mock_write_data.mock_calls[0][1]
|
args = mock_write_data.mock_calls[0][1]
|
||||||
written_states = args[0]
|
written_states = args[0]
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
hass.states.async_remove(state.entity_id)
|
||||||
# b0 should not be written, since it didn't extend RestoreEntity
|
# b0 should not be written, since it didn't extend RestoreEntity
|
||||||
# b1 should be written, since it is present in the current run
|
# b1 should be written, since it is present in the current run
|
||||||
# b2 should not be written, since it is not registered with the helper
|
# b2 should not be written, since it is not registered with the helper
|
||||||
|
@ -319,9 +328,12 @@ async def test_dump_data(hass: HomeAssistant) -> None:
|
||||||
# Test that removed entities are not persisted
|
# Test that removed entities are not persisted
|
||||||
await entity.async_remove()
|
await entity.async_remove()
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
hass.states.async_set(state.entity_id, state.state, state.attributes)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.restore_state.Store.async_save"
|
"homeassistant.helpers.restore_state.Store.async_save"
|
||||||
) as mock_write_data, patch.object(hass.states, "async_all", return_value=states):
|
) as mock_write_data:
|
||||||
await data.async_dump_states()
|
await data.async_dump_states()
|
||||||
|
|
||||||
assert mock_write_data.called
|
assert mock_write_data.called
|
||||||
|
@ -355,10 +367,13 @@ async def test_dump_error(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
data = async_get(hass)
|
data = async_get(hass)
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
hass.states.async_set(state.entity_id, state.state, state.attributes)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.restore_state.Store.async_save",
|
"homeassistant.helpers.restore_state.Store.async_save",
|
||||||
side_effect=HomeAssistantError,
|
side_effect=HomeAssistantError,
|
||||||
) as mock_write_data, patch.object(hass.states, "async_all", return_value=states):
|
) as mock_write_data:
|
||||||
await data.async_dump_states()
|
await data.async_dump_states()
|
||||||
|
|
||||||
assert mock_write_data.called
|
assert mock_write_data.called
|
||||||
|
|
|
@ -4533,20 +4533,22 @@ async def test_render_to_info_with_exception(hass: HomeAssistant) -> None:
|
||||||
async def test_lru_increases_with_many_entities(hass: HomeAssistant) -> None:
|
async def test_lru_increases_with_many_entities(hass: HomeAssistant) -> None:
|
||||||
"""Test that the template internal LRU cache increases with many entities."""
|
"""Test that the template internal LRU cache increases with many entities."""
|
||||||
# We do not actually want to record 4096 entities so we mock the entity count
|
# We do not actually want to record 4096 entities so we mock the entity count
|
||||||
mock_entity_count = 4096
|
mock_entity_count = 16
|
||||||
|
|
||||||
assert template.CACHED_TEMPLATE_LRU.get_size() == template.CACHED_TEMPLATE_STATES
|
assert template.CACHED_TEMPLATE_LRU.get_size() == template.CACHED_TEMPLATE_STATES
|
||||||
assert (
|
assert (
|
||||||
template.CACHED_TEMPLATE_NO_COLLECT_LRU.get_size()
|
template.CACHED_TEMPLATE_NO_COLLECT_LRU.get_size()
|
||||||
== template.CACHED_TEMPLATE_STATES
|
== template.CACHED_TEMPLATE_STATES
|
||||||
)
|
)
|
||||||
|
template.CACHED_TEMPLATE_LRU.set_size(8)
|
||||||
|
template.CACHED_TEMPLATE_NO_COLLECT_LRU.set_size(8)
|
||||||
|
|
||||||
template.async_setup(hass)
|
template.async_setup(hass)
|
||||||
with patch.object(
|
for i in range(mock_entity_count):
|
||||||
hass.states, "async_entity_ids_count", return_value=mock_entity_count
|
hass.states.async_set(f"sensor.sensor{i}", "on")
|
||||||
):
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert template.CACHED_TEMPLATE_LRU.get_size() == int(
|
assert template.CACHED_TEMPLATE_LRU.get_size() == int(
|
||||||
round(mock_entity_count * template.ENTITY_COUNT_GROWTH_FACTOR)
|
round(mock_entity_count * template.ENTITY_COUNT_GROWTH_FACTOR)
|
||||||
|
@ -4556,9 +4558,12 @@ async def test_lru_increases_with_many_entities(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.async_stop()
|
await hass.async_stop()
|
||||||
with patch.object(hass.states, "async_entity_ids_count", return_value=8192):
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
|
for i in range(mock_entity_count):
|
||||||
await hass.async_block_till_done()
|
hass.states.async_set(f"sensor.sensor_add_{i}", "on")
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert template.CACHED_TEMPLATE_LRU.get_size() == int(
|
assert template.CACHED_TEMPLATE_LRU.get_size() == int(
|
||||||
round(mock_entity_count * template.ENTITY_COUNT_GROWTH_FACTOR)
|
round(mock_entity_count * template.ENTITY_COUNT_GROWTH_FACTOR)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue