Move processing of recorder service call arguments into services.py (#71260)

pull/71271/head
J. Nick Koston 2022-05-03 15:56:22 -05:00 committed by GitHub
parent e9abfad361
commit e30940ef2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 108 deletions

View File

@ -179,4 +179,4 @@ async def _process_recorder_platform(
) -> None: ) -> None:
"""Process a recorder platform.""" """Process a recorder platform."""
instance: Recorder = hass.data[DATA_INSTANCE] instance: Recorder = hass.data[DATA_INSTANCE]
instance.queue.put(AddRecorderPlatformTask(domain, platform)) instance.queue_task(AddRecorderPlatformTask(domain, platform))

View File

@ -28,7 +28,6 @@ from homeassistant.const import (
MATCH_ALL, MATCH_ALL,
) )
from homeassistant.core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback
from homeassistant.helpers.entityfilter import generate_filter
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_time_change, async_track_time_change,
async_track_time_interval, async_track_time_interval,
@ -38,9 +37,6 @@ import homeassistant.util.dt as dt_util
from . import migration, statistics from . import migration, statistics
from .const import ( from .const import (
ATTR_APPLY_FILTER,
ATTR_KEEP_DAYS,
ATTR_REPACK,
DB_WORKER_PREFIX, DB_WORKER_PREFIX,
KEEPALIVE_TIME, KEEPALIVE_TIME,
MAX_QUEUE_BACKLOG, MAX_QUEUE_BACKLOG,
@ -70,7 +66,6 @@ from .tasks import (
ExternalStatisticsTask, ExternalStatisticsTask,
KeepAliveTask, KeepAliveTask,
PerodicCleanupTask, PerodicCleanupTask,
PurgeEntitiesTask,
PurgeTask, PurgeTask,
RecorderTask, RecorderTask,
StatisticsTask, StatisticsTask,
@ -112,6 +107,7 @@ SHUTDOWN_TASK = object()
COMMIT_TASK = CommitTask() COMMIT_TASK = CommitTask()
KEEP_ALIVE_TASK = KeepAliveTask() KEEP_ALIVE_TASK = KeepAliveTask()
WAIT_TASK = WaitTask()
DB_LOCK_TIMEOUT = 30 DB_LOCK_TIMEOUT = 30
DB_LOCK_QUEUE_CHECK_TIMEOUT = 1 DB_LOCK_QUEUE_CHECK_TIMEOUT = 1
@ -152,7 +148,7 @@ class Recorder(threading.Thread):
self.keep_days = keep_days self.keep_days = keep_days
self._hass_started: asyncio.Future[object] = asyncio.Future() self._hass_started: asyncio.Future[object] = asyncio.Future()
self.commit_interval = commit_interval self.commit_interval = commit_interval
self.queue: queue.SimpleQueue[RecorderTask] = queue.SimpleQueue() self._queue: queue.SimpleQueue[RecorderTask] = queue.SimpleQueue()
self.db_url = uri self.db_url = uri
self.db_max_retries = db_max_retries self.db_max_retries = db_max_retries
self.db_retry_wait = db_retry_wait self.db_retry_wait = db_retry_wait
@ -175,21 +171,42 @@ class Recorder(threading.Thread):
self.event_session: Session | None = None self.event_session: Session | None = None
self.get_session: Callable[[], Session] | None = None self.get_session: Callable[[], Session] | None = None
self._completed_first_database_setup: bool | None = None self._completed_first_database_setup: bool | None = None
self._event_listener: CALLBACK_TYPE | None = None
self.async_migration_event = asyncio.Event() self.async_migration_event = asyncio.Event()
self.migration_in_progress = False self.migration_in_progress = False
self._queue_watcher: CALLBACK_TYPE | None = None
self._db_supports_row_number = True self._db_supports_row_number = True
self._database_lock_task: DatabaseLockTask | None = None self._database_lock_task: DatabaseLockTask | None = None
self._db_executor: DBInterruptibleThreadPoolExecutor | None = None self._db_executor: DBInterruptibleThreadPoolExecutor | None = None
self._exclude_attributes_by_domain = exclude_attributes_by_domain self._exclude_attributes_by_domain = exclude_attributes_by_domain
self._event_listener: CALLBACK_TYPE | None = None
self._queue_watcher: CALLBACK_TYPE | None = None
self._keep_alive_listener: CALLBACK_TYPE | None = None self._keep_alive_listener: CALLBACK_TYPE | None = None
self._commit_listener: CALLBACK_TYPE | None = None self._commit_listener: CALLBACK_TYPE | None = None
self._periodic_listener: CALLBACK_TYPE | None = None self._periodic_listener: CALLBACK_TYPE | None = None
self._nightly_listener: CALLBACK_TYPE | None = None self._nightly_listener: CALLBACK_TYPE | None = None
self.enabled = True self.enabled = True
@property
def backlog(self) -> int:
"""Return the number of items in the recorder backlog."""
return self._queue.qsize()
@property
def _using_file_sqlite(self) -> bool:
"""Short version to check if we are using sqlite3 as a file."""
return self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith(
SQLITE_URL_PREFIX
)
@property
def recording(self) -> bool:
"""Return if the recorder is recording."""
return self._event_listener is not None
def queue_task(self, task: RecorderTask) -> None:
"""Add a task to the recorder queue."""
self._queue.put(task)
def set_enable(self, enable: bool) -> None: def set_enable(self, enable: bool) -> None:
"""Enable or disable recording events and states.""" """Enable or disable recording events and states."""
self.enabled = enable self.enabled = enable
@ -222,7 +239,7 @@ class Recorder(threading.Thread):
def _async_keep_alive(self, now: datetime) -> None: def _async_keep_alive(self, now: datetime) -> None:
"""Queue a keep alive.""" """Queue a keep alive."""
if self._event_listener: if self._event_listener:
self.queue.put(KEEP_ALIVE_TASK) self.queue_task(KEEP_ALIVE_TASK)
@callback @callback
def _async_commit(self, now: datetime) -> None: def _async_commit(self, now: datetime) -> None:
@ -232,7 +249,7 @@ class Recorder(threading.Thread):
and not self._database_lock_task and not self._database_lock_task
and self._event_session_has_pending_writes() and self._event_session_has_pending_writes()
): ):
self.queue.put(COMMIT_TASK) self.queue_task(COMMIT_TASK)
@callback @callback
def async_add_executor_job( def async_add_executor_job(
@ -253,7 +270,7 @@ class Recorder(threading.Thread):
The queue grows during migraton or if something really goes wrong. The queue grows during migraton or if something really goes wrong.
""" """
size = self.queue.qsize() size = self.backlog
_LOGGER.debug("Recorder queue size is: %s", size) _LOGGER.debug("Recorder queue size is: %s", size)
if size <= MAX_QUEUE_BACKLOG: if size <= MAX_QUEUE_BACKLOG:
return return
@ -314,73 +331,52 @@ class Recorder(threading.Thread):
# Unknown what it is. # Unknown what it is.
return True return True
def do_adhoc_purge(self, **kwargs: Any) -> None:
"""Trigger an adhoc purge retaining keep_days worth of data."""
keep_days = kwargs.get(ATTR_KEEP_DAYS, self.keep_days)
repack = cast(bool, kwargs[ATTR_REPACK])
apply_filter = cast(bool, kwargs[ATTR_APPLY_FILTER])
purge_before = dt_util.utcnow() - timedelta(days=keep_days)
self.queue.put(PurgeTask(purge_before, repack, apply_filter))
def do_adhoc_purge_entities(
self, entity_ids: set[str], domains: list[str], entity_globs: list[str]
) -> None:
"""Trigger an adhoc purge of requested entities."""
entity_filter = generate_filter(domains, list(entity_ids), [], [], entity_globs)
self.queue.put(PurgeEntitiesTask(entity_filter))
def do_adhoc_statistics(self, **kwargs: Any) -> None: def do_adhoc_statistics(self, **kwargs: Any) -> None:
"""Trigger an adhoc statistics run.""" """Trigger an adhoc statistics run."""
if not (start := kwargs.get("start")): if not (start := kwargs.get("start")):
start = statistics.get_start_time() start = statistics.get_start_time()
self.queue.put(StatisticsTask(start)) self.queue_task(StatisticsTask(start))
def _empty_queue(self, event: Event) -> None:
"""Empty the queue if its still present at final write."""
# If the queue is full of events to be processed because
# the database is so broken that every event results in a retry
# we will never be able to get though the events to shutdown in time.
#
# We drain all the events in the queue and then insert
# an empty one to ensure the next thing the recorder sees
# is a request to shutdown.
while True:
try:
self._queue.get_nowait()
except queue.Empty:
break
self.queue_task(StopTask())
async def _async_shutdown(self, event: Event) -> None:
"""Shut down the Recorder."""
if not self._hass_started.done():
self._hass_started.set_result(SHUTDOWN_TASK)
self.queue_task(StopTask())
self._async_stop_listeners()
await self.hass.async_add_executor_job(self.join)
@callback
def _async_hass_started(self, event: Event) -> None:
"""Notify that hass has started."""
self._hass_started.set_result(None)
@callback @callback
def async_register(self) -> None: def async_register(self) -> None:
"""Post connection initialize.""" """Post connection initialize."""
bus = self.hass.bus
def _empty_queue(event: Event) -> None: bus.async_listen_once(EVENT_HOMEASSISTANT_FINAL_WRITE, self._empty_queue)
"""Empty the queue if its still present at final write.""" bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_shutdown)
# If the queue is full of events to be processed because
# the database is so broken that every event results in a retry
# we will never be able to get though the events to shutdown in time.
#
# We drain all the events in the queue and then insert
# an empty one to ensure the next thing the recorder sees
# is a request to shutdown.
while True:
try:
self.queue.get_nowait()
except queue.Empty:
break
self.queue.put(StopTask())
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_FINAL_WRITE, _empty_queue)
async def _async_shutdown(event: Event) -> None:
"""Shut down the Recorder."""
if not self._hass_started.done():
self._hass_started.set_result(SHUTDOWN_TASK)
self.queue.put(StopTask())
self._async_stop_listeners()
await self.hass.async_add_executor_job(self.join)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown)
if self.hass.state == CoreState.running: if self.hass.state == CoreState.running:
self._hass_started.set_result(None) self._hass_started.set_result(None)
return return
bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, self._async_hass_started)
@callback
def _async_hass_started(event: Event) -> None:
"""Notify that hass has started."""
self._hass_started.set_result(None)
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED, _async_hass_started
)
@callback @callback
def async_connection_failed(self) -> None: def async_connection_failed(self) -> None:
@ -414,9 +410,9 @@ class Recorder(threading.Thread):
# until after the database is vacuumed # until after the database is vacuumed
repack = self.auto_repack and is_second_sunday(now) repack = self.auto_repack and is_second_sunday(now)
purge_before = dt_util.utcnow() - timedelta(days=self.keep_days) purge_before = dt_util.utcnow() - timedelta(days=self.keep_days)
self.queue.put(PurgeTask(purge_before, repack=repack, apply_filter=False)) self.queue_task(PurgeTask(purge_before, repack=repack, apply_filter=False))
else: else:
self.queue.put(PerodicCleanupTask()) self.queue_task(PerodicCleanupTask())
@callback @callback
def async_periodic_statistics(self, now: datetime) -> None: def async_periodic_statistics(self, now: datetime) -> None:
@ -425,33 +421,33 @@ class Recorder(threading.Thread):
Short term statistics run every 5 minutes Short term statistics run every 5 minutes
""" """
start = statistics.get_start_time() start = statistics.get_start_time()
self.queue.put(StatisticsTask(start)) self.queue_task(StatisticsTask(start))
@callback @callback
def async_adjust_statistics( def async_adjust_statistics(
self, statistic_id: str, start_time: datetime, sum_adjustment: float self, statistic_id: str, start_time: datetime, sum_adjustment: float
) -> None: ) -> None:
"""Adjust statistics.""" """Adjust statistics."""
self.queue.put(AdjustStatisticsTask(statistic_id, start_time, sum_adjustment)) self.queue_task(AdjustStatisticsTask(statistic_id, start_time, sum_adjustment))
@callback @callback
def async_clear_statistics(self, statistic_ids: list[str]) -> None: def async_clear_statistics(self, statistic_ids: list[str]) -> None:
"""Clear statistics for a list of statistic_ids.""" """Clear statistics for a list of statistic_ids."""
self.queue.put(ClearStatisticsTask(statistic_ids)) self.queue_task(ClearStatisticsTask(statistic_ids))
@callback @callback
def async_update_statistics_metadata( def async_update_statistics_metadata(
self, statistic_id: str, unit_of_measurement: str | None self, statistic_id: str, unit_of_measurement: str | None
) -> None: ) -> None:
"""Update statistics metadata for a statistic_id.""" """Update statistics metadata for a statistic_id."""
self.queue.put(UpdateStatisticsMetadataTask(statistic_id, unit_of_measurement)) self.queue_task(UpdateStatisticsMetadataTask(statistic_id, unit_of_measurement))
@callback @callback
def async_external_statistics( def async_external_statistics(
self, metadata: StatisticMetaData, stats: Iterable[StatisticData] self, metadata: StatisticMetaData, stats: Iterable[StatisticData]
) -> None: ) -> None:
"""Schedule external statistics.""" """Schedule external statistics."""
self.queue.put(ExternalStatisticsTask(metadata, stats)) self.queue_task(ExternalStatisticsTask(metadata, stats))
@callback @callback
def using_sqlite(self) -> bool: def using_sqlite(self) -> bool:
@ -553,7 +549,7 @@ class Recorder(threading.Thread):
# has changed. This reduces the disk io. # has changed. This reduces the disk io.
self.stop_requested = False self.stop_requested = False
while not self.stop_requested: while not self.stop_requested:
task = self.queue.get() task = self._queue.get()
_LOGGER.debug("Processing task: %s", task) _LOGGER.debug("Processing task: %s", task)
try: try:
self._process_one_task_or_recover(task) self._process_one_task_or_recover(task)
@ -643,7 +639,7 @@ class Recorder(threading.Thread):
# Notify that lock is being held, wait until database can be used again. # Notify that lock is being held, wait until database can be used again.
self.hass.add_job(_async_set_database_locked, task) self.hass.add_job(_async_set_database_locked, task)
while not task.database_unlock.wait(timeout=DB_LOCK_QUEUE_CHECK_TIMEOUT): while not task.database_unlock.wait(timeout=DB_LOCK_QUEUE_CHECK_TIMEOUT):
if self.queue.qsize() > MAX_QUEUE_BACKLOG * 0.9: if self.backlog > MAX_QUEUE_BACKLOG * 0.9:
_LOGGER.warning( _LOGGER.warning(
"Database queue backlog reached more than 90% of maximum queue " "Database queue backlog reached more than 90% of maximum queue "
"length while waiting for backup to finish; recorder will now " "length while waiting for backup to finish; recorder will now "
@ -654,7 +650,7 @@ class Recorder(threading.Thread):
break break
_LOGGER.info( _LOGGER.info(
"Database queue backlog reached %d entries during backup", "Database queue backlog reached %d entries during backup",
self.queue.qsize(), self.backlog,
) )
def _process_one_event(self, event: Event) -> None: def _process_one_event(self, event: Event) -> None:
@ -908,7 +904,7 @@ class Recorder(threading.Thread):
@callback @callback
def event_listener(self, event: Event) -> None: def event_listener(self, event: Event) -> None:
"""Listen for new events and put them in the process queue.""" """Listen for new events and put them in the process queue."""
self.queue.put(EventTask(event)) self.queue_task(EventTask(event))
def block_till_done(self) -> None: def block_till_done(self) -> None:
"""Block till all events processed. """Block till all events processed.
@ -923,7 +919,7 @@ class Recorder(threading.Thread):
is in the database. is in the database.
""" """
self._queue_watch.clear() self._queue_watch.clear()
self.queue.put(WaitTask()) self.queue_task(WAIT_TASK)
self._queue_watch.wait() self._queue_watch.wait()
async def lock_database(self) -> bool: async def lock_database(self) -> bool:
@ -940,7 +936,7 @@ class Recorder(threading.Thread):
database_locked = asyncio.Event() database_locked = asyncio.Event()
task = DatabaseLockTask(database_locked, threading.Event(), False) task = DatabaseLockTask(database_locked, threading.Event(), False)
self.queue.put(task) self.queue_task(task)
try: try:
await asyncio.wait_for(database_locked.wait(), timeout=DB_LOCK_TIMEOUT) await asyncio.wait_for(database_locked.wait(), timeout=DB_LOCK_TIMEOUT)
except asyncio.TimeoutError as err: except asyncio.TimeoutError as err:
@ -1013,13 +1009,6 @@ class Recorder(threading.Thread):
self.get_session = scoped_session(sessionmaker(bind=self.engine, future=True)) self.get_session = scoped_session(sessionmaker(bind=self.engine, future=True))
_LOGGER.debug("Connected to recorder database") _LOGGER.debug("Connected to recorder database")
@property
def _using_file_sqlite(self) -> bool:
"""Short version to check if we are using sqlite3 as a file."""
return self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith(
SQLITE_URL_PREFIX
)
def _close_connection(self) -> None: def _close_connection(self) -> None:
"""Close the connection.""" """Close the connection."""
assert self.engine is not None assert self.engine is not None
@ -1053,7 +1042,7 @@ class Recorder(threading.Thread):
while start < last_period: while start < last_period:
end = start + timedelta(minutes=5) end = start + timedelta(minutes=5)
_LOGGER.debug("Compiling missing statistics for %s-%s", start, end) _LOGGER.debug("Compiling missing statistics for %s-%s", start, end)
self.queue.put(StatisticsTask(start)) self.queue_task(StatisticsTask(start))
start = end start = end
def _end_session(self) -> None: def _end_session(self) -> None:
@ -1075,8 +1064,3 @@ class Recorder(threading.Thread):
self._stop_executor() self._stop_executor()
self._end_session() self._end_session()
self._close_connection() self._close_connection()
@property
def recording(self) -> bool:
"""Return if the recorder is recording."""
return self._event_listener is not None

View File

@ -1,21 +1,26 @@
"""Support for recorder services.""" """Support for recorder services."""
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
from typing import cast
import voluptuous as vol import voluptuous as vol
from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.core import HomeAssistant, ServiceCall, callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import generate_filter
from homeassistant.helpers.service import async_extract_entity_ids from homeassistant.helpers.service import async_extract_entity_ids
import homeassistant.util.dt as dt_util
from .const import ATTR_APPLY_FILTER, ATTR_KEEP_DAYS, ATTR_REPACK, DOMAIN from .const import ATTR_APPLY_FILTER, ATTR_KEEP_DAYS, ATTR_REPACK, DOMAIN
from .core import Recorder from .core import Recorder
from .tasks import PurgeEntitiesTask, PurgeTask
SERVICE_PURGE = "purge" SERVICE_PURGE = "purge"
SERVICE_PURGE_ENTITIES = "purge_entities" SERVICE_PURGE_ENTITIES = "purge_entities"
SERVICE_ENABLE = "enable" SERVICE_ENABLE = "enable"
SERVICE_DISABLE = "disable" SERVICE_DISABLE = "disable"
SERVICE_PURGE_SCHEMA = vol.Schema( SERVICE_PURGE_SCHEMA = vol.Schema(
{ {
vol.Optional(ATTR_KEEP_DAYS): cv.positive_int, vol.Optional(ATTR_KEEP_DAYS): cv.positive_int,
@ -44,7 +49,12 @@ SERVICE_DISABLE_SCHEMA = vol.Schema({})
def _async_register_purge_service(hass: HomeAssistant, instance: Recorder) -> None: def _async_register_purge_service(hass: HomeAssistant, instance: Recorder) -> None:
async def async_handle_purge_service(service: ServiceCall) -> None: async def async_handle_purge_service(service: ServiceCall) -> None:
"""Handle calls to the purge service.""" """Handle calls to the purge service."""
instance.do_adhoc_purge(**service.data) kwargs = service.data
keep_days = kwargs.get(ATTR_KEEP_DAYS, instance.keep_days)
repack = cast(bool, kwargs[ATTR_REPACK])
apply_filter = cast(bool, kwargs[ATTR_APPLY_FILTER])
purge_before = dt_util.utcnow() - timedelta(days=keep_days)
instance.queue_task(PurgeTask(purge_before, repack, apply_filter))
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_PURGE, async_handle_purge_service, schema=SERVICE_PURGE_SCHEMA DOMAIN, SERVICE_PURGE, async_handle_purge_service, schema=SERVICE_PURGE_SCHEMA
@ -60,8 +70,8 @@ def _async_register_purge_entities_service(
entity_ids = await async_extract_entity_ids(hass, service) entity_ids = await async_extract_entity_ids(hass, service)
domains = service.data.get(ATTR_DOMAINS, []) domains = service.data.get(ATTR_DOMAINS, [])
entity_globs = service.data.get(ATTR_ENTITY_GLOBS, []) entity_globs = service.data.get(ATTR_ENTITY_GLOBS, [])
entity_filter = generate_filter(domains, list(entity_ids), [], [], entity_globs)
instance.do_adhoc_purge_entities(entity_ids, domains, entity_globs) instance.queue_task(PurgeEntitiesTask(entity_filter))
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,

View File

@ -78,7 +78,9 @@ class PurgeTask(RecorderTask):
periodic_db_cleanups(instance) periodic_db_cleanups(instance)
return return
# Schedule a new purge task if this one didn't finish # Schedule a new purge task if this one didn't finish
instance.queue.put(PurgeTask(self.purge_before, self.repack, self.apply_filter)) instance.queue_task(
PurgeTask(self.purge_before, self.repack, self.apply_filter)
)
@dataclass @dataclass
@ -92,7 +94,7 @@ class PurgeEntitiesTask(RecorderTask):
if purge.purge_entity_data(instance, self.entity_filter): if purge.purge_entity_data(instance, self.entity_filter):
return return
# Schedule a new purge task if this one didn't finish # Schedule a new purge task if this one didn't finish
instance.queue.put(PurgeEntitiesTask(self.entity_filter)) instance.queue_task(PurgeEntitiesTask(self.entity_filter))
@dataclass @dataclass
@ -115,7 +117,7 @@ class StatisticsTask(RecorderTask):
if statistics.compile_statistics(instance, self.start): if statistics.compile_statistics(instance, self.start):
return return
# Schedule a new statistics task if this one didn't finish # Schedule a new statistics task if this one didn't finish
instance.queue.put(StatisticsTask(self.start)) instance.queue_task(StatisticsTask(self.start))
@dataclass @dataclass
@ -130,7 +132,7 @@ class ExternalStatisticsTask(RecorderTask):
if statistics.add_external_statistics(instance, self.metadata, self.statistics): if statistics.add_external_statistics(instance, self.metadata, self.statistics):
return return
# Schedule a new statistics task if this one didn't finish # Schedule a new statistics task if this one didn't finish
instance.queue.put(ExternalStatisticsTask(self.metadata, self.statistics)) instance.queue_task(ExternalStatisticsTask(self.metadata, self.statistics))
@dataclass @dataclass
@ -151,7 +153,7 @@ class AdjustStatisticsTask(RecorderTask):
): ):
return return
# Schedule a new adjust statistics task if this one didn't finish # Schedule a new adjust statistics task if this one didn't finish
instance.queue.put( instance.queue_task(
AdjustStatisticsTask( AdjustStatisticsTask(
self.statistic_id, self.start_time, self.sum_adjustment self.statistic_id, self.start_time, self.sum_adjustment
) )

View File

@ -148,7 +148,7 @@ def ws_info(
"""Return status of the recorder.""" """Return status of the recorder."""
instance: Recorder = hass.data[DATA_INSTANCE] instance: Recorder = hass.data[DATA_INSTANCE]
backlog = instance.queue.qsize() if instance and instance.queue else None backlog = instance.backlog if instance else None
migration_in_progress = async_migration_in_progress(hass) migration_in_progress = async_migration_in_progress(hass)
recording = instance.recording if instance else False recording = instance.recording if instance else False
thread_alive = instance.is_alive() if instance else False thread_alive = instance.is_alive() if instance else False

View File

@ -1395,7 +1395,7 @@ async def test_database_lock_timeout(hass, recorder_mock):
self.event.wait() self.event.wait()
block_task = BlockQueue() block_task = BlockQueue()
instance.queue.put(block_task) instance.queue_task(block_task)
with patch.object(recorder.core, "DB_LOCK_TIMEOUT", 0.1): with patch.object(recorder.core, "DB_LOCK_TIMEOUT", 0.1):
try: try:
with pytest.raises(TimeoutError): with pytest.raises(TimeoutError):

View File

@ -557,7 +557,7 @@ async def test_purge_cutoff_date(
assert events.filter(Events.event_type == "PURGE").count() == rows - 1 assert events.filter(Events.event_type == "PURGE").count() == rows - 1
assert events.filter(Events.event_type == "KEEP").count() == 1 assert events.filter(Events.event_type == "KEEP").count() == 1
instance.queue.put(PurgeTask(cutoff, repack=False, apply_filter=False)) instance.queue_task(PurgeTask(cutoff, repack=False, apply_filter=False))
await hass.async_block_till_done() await hass.async_block_till_done()
await async_recorder_block_till_done(hass) await async_recorder_block_till_done(hass)
await async_wait_purge_done(hass) await async_wait_purge_done(hass)
@ -588,7 +588,7 @@ async def test_purge_cutoff_date(
assert events.filter(Events.event_type == "KEEP").count() == 1 assert events.filter(Events.event_type == "KEEP").count() == 1
# Make sure we can purge everything # Make sure we can purge everything
instance.queue.put(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) instance.queue_task(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False))
await async_recorder_block_till_done(hass) await async_recorder_block_till_done(hass)
await async_wait_purge_done(hass) await async_wait_purge_done(hass)
@ -599,7 +599,7 @@ async def test_purge_cutoff_date(
assert state_attributes.count() == 0 assert state_attributes.count() == 0
# Make sure we can purge everything when the db is already empty # Make sure we can purge everything when the db is already empty
instance.queue.put(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) instance.queue_task(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False))
await async_recorder_block_till_done(hass) await async_recorder_block_till_done(hass)
await async_wait_purge_done(hass) await async_wait_purge_done(hass)