Add slots to the StateMachine class (#95849)

This commit is contained in:
J. Nick Koston 2023-07-05 07:00:37 -05:00 committed by GitHub
parent 39dcb5a2b5
commit b2e708834f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 32 deletions

View file

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

View file

@ -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] = {}

View file

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

View file

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

View file

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