From 790c1bc2519703b547ce6f16daa7a9f58857c00a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Aug 2023 19:37:45 -0500 Subject: [PATCH] Decrease event loop latency by binding time.monotonic to loop.time directly (#98288) * Decrease event loop latency by binding time.monotonic to loop.time directly This is a small improvment to decrease event loop latency. While the goal is is to reduce Bluetooth connection time latency, everything using asyncio is a bit more responsive as a result. * relo per comments * fix too fast by adding resolution, ensure monotonic time is patchable by freezegun * fix test that freezes time too late and has a race loop --- homeassistant/runner.py | 5 +++++ tests/common.py | 5 ++++- tests/components/statistics/test_sensor.py | 7 +++++-- tests/patch_time.py | 9 ++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 4bbf1a7dada0..ed49db37f971 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -8,6 +8,7 @@ import logging import os import subprocess import threading +from time import monotonic import traceback from typing import Any @@ -114,6 +115,10 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): loop.set_default_executor = warn_use( # type: ignore[method-assign] loop.set_default_executor, "sets default executor on the event loop" ) + # bind the built-in time.monotonic directly as loop.time to avoid the + # overhead of the additional method call since its the most called loop + # method and its roughly 10%+ of all the call time in base_events.py + loop.time = monotonic # type: ignore[method-assign] return loop diff --git a/tests/common.py b/tests/common.py index eb8c8417f16e..0431743cccf8 100644 --- a/tests/common.py +++ b/tests/common.py @@ -420,6 +420,9 @@ def async_fire_time_changed( _async_fire_time_changed(hass, utc_datetime, fire_all) +_MONOTONIC_RESOLUTION = time.get_clock_info("monotonic").resolution + + @callback def _async_fire_time_changed( hass: HomeAssistant, utc_datetime: datetime | None, fire_all: bool @@ -432,7 +435,7 @@ def _async_fire_time_changed( continue mock_seconds_into_future = timestamp - time.time() - future_seconds = task.when() - hass.loop.time() + future_seconds = task.when() - (hass.loop.time() + _MONOTONIC_RESOLUTION) if fire_all or mock_seconds_into_future >= future_seconds: with patch( diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index 4b77e2d07253..780e550f2242 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -8,6 +8,7 @@ from typing import Any from unittest.mock import patch from freezegun import freeze_time +import pytest from homeassistant import config as hass_config from homeassistant.components.recorder import Recorder @@ -1286,12 +1287,14 @@ async def test_initialize_from_database( assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS +@pytest.mark.freeze_time( + datetime(dt_util.utcnow().year + 1, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC) +) async def test_initialize_from_database_with_maxage( recorder_mock: Recorder, hass: HomeAssistant ) -> None: """Test initializing the statistics from the database.""" - now = dt_util.utcnow() - current_time = datetime(now.year + 1, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC) + current_time = dt_util.utcnow() # Testing correct retrieval from recorder, thus we do not # want purging to occur within the class itself. diff --git a/tests/patch_time.py b/tests/patch_time.py index 2a453053170f..5f5dc467c9d9 100644 --- a/tests/patch_time.py +++ b/tests/patch_time.py @@ -2,8 +2,9 @@ from __future__ import annotations import datetime +import time -from homeassistant import util +from homeassistant import runner, util from homeassistant.util import dt as dt_util @@ -12,5 +13,11 @@ def _utcnow() -> datetime.datetime: return datetime.datetime.now(datetime.UTC) +def _monotonic() -> float: + """Make monotonic patchable by freezegun.""" + return time.monotonic() + + dt_util.utcnow = _utcnow # type: ignore[assignment] util.utcnow = _utcnow # type: ignore[assignment] +runner.monotonic = _monotonic # type: ignore[assignment]