Skip to content

Commit

Permalink
Significantly reduce clock_gettime syscalls on platforms with broken …
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Oct 31, 2022
1 parent 8416cc1 commit 1589c06
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

from collections.abc import Callable, Coroutine
import logging
import time
from typing import Any, Generic, TypeVar

from bleak import BleakError

from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.debounce import Debouncer
from homeassistant.util.dt import monotonic_time_coarse

from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
from .passive_update_processor import PassiveBluetoothProcessorCoordinator
Expand Down Expand Up @@ -94,7 +94,7 @@ def needs_poll(self, service_info: BluetoothServiceInfoBleak) -> bool:
"""Return true if time to try and poll."""
poll_age: float | None = None
if self._last_poll:
poll_age = time.monotonic() - self._last_poll
poll_age = monotonic_time_coarse() - self._last_poll
return self._needs_poll_method(service_info, poll_age)

async def _async_poll_data(
Expand Down Expand Up @@ -124,7 +124,7 @@ async def _async_poll(self) -> None:
self.last_poll_successful = False
return
finally:
self._last_poll = time.monotonic()
self._last_poll = monotonic_time_coarse()

if not self.last_poll_successful:
self.logger.debug("%s: Polling recovered")
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/bluetooth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from datetime import datetime, timedelta
import itertools
import logging
import time
from typing import TYPE_CHECKING, Any, Final

from bleak.backends.scanner import AdvertisementDataCallback
Expand All @@ -22,6 +21,7 @@
)
from homeassistant.helpers import discovery_flow
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.dt import monotonic_time_coarse

from .advertisement_tracker import AdvertisementTracker
from .const import (
Expand Down Expand Up @@ -69,7 +69,7 @@
APPLE_DEVICE_ID_START_BYTE,
}

MONOTONIC_TIME: Final = time.monotonic
MONOTONIC_TIME: Final = monotonic_time_coarse

_LOGGER = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/bluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from datetime import datetime
import logging
import platform
import time
from typing import Any

import async_timeout
Expand All @@ -22,6 +21,7 @@
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.dt import monotonic_time_coarse
from homeassistant.util.package import is_docker_env

from .const import (
Expand All @@ -35,7 +35,7 @@
from .util import adapter_human_name, async_reset_adapter

OriginalBleakScanner = bleak.BleakScanner
MONOTONIC_TIME = time.monotonic
MONOTONIC_TIME = monotonic_time_coarse

# or_patterns is a workaround for the fact that passive scanning
# needs at least one matcher to be set. The below matcher
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/bluetooth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
from __future__ import annotations

import platform
import time

from bluetooth_auto_recovery import recover_adapter

from homeassistant.core import callback
from homeassistant.util.dt import monotonic_time_coarse

from .const import (
DEFAULT_ADAPTER_BY_PLATFORM,
Expand All @@ -29,7 +29,7 @@ async def async_load_history_from_system() -> dict[str, BluetoothServiceInfoBlea

bluez_dbus = BlueZDBusObjects()
await bluez_dbus.load()
now = time.monotonic()
now = monotonic_time_coarse()
return {
address: BluetoothServiceInfoBleak(
name=history.advertisement_data.local_name
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/esphome/bluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.dt import monotonic_time_coarse

TWO_CHAR = re.compile("..")

Expand Down Expand Up @@ -84,7 +85,7 @@ def discovered_devices_and_advertisement_data(
@callback
def async_on_advertisement(self, adv: BluetoothLEAdvertisement) -> None:
"""Call the registered callback."""
now = time.monotonic()
now = monotonic_time_coarse()
address = ":".join(TWO_CHAR.findall("%012X" % adv.address)) # must be upper
name = adv.name
if prev_discovery := self._discovered_device_advertisement_datas.get(address):
Expand Down
26 changes: 26 additions & 0 deletions homeassistant/util/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import bisect
from contextlib import suppress
import datetime as dt
import platform
import re
import time
from typing import Any
import zoneinfo

Expand All @@ -13,6 +15,7 @@
DATE_STR_FORMAT = "%Y-%m-%d"
UTC = dt.timezone.utc
DEFAULT_TIME_ZONE: dt.tzinfo = dt.timezone.utc
CLOCK_MONOTONIC_COARSE = 6

# EPOCHORDINAL is not exposed as a constant
# https://github.com/python/cpython/blob/3.10/Lib/zoneinfo/_zoneinfo.py#L12
Expand Down Expand Up @@ -461,3 +464,26 @@ def _datetime_ambiguous(dattim: dt.datetime) -> bool:
assert dattim.tzinfo is not None
opposite_fold = dattim.replace(fold=not dattim.fold)
return _datetime_exists(dattim) and dattim.utcoffset() != opposite_fold.utcoffset()


def __monotonic_time_coarse() -> float:
"""Return a monotonic time in seconds.
This is the coarse version of time_monotonic, which is faster but less accurate.
Since many arm64 and 32-bit platforms don't support VDSO with time.monotonic
because of errata, we can't rely on the kernel to provide a fast
monotonic time.
https://lore.kernel.org/lkml/20170404171826.25030-1-marc.zyngier@arm.com/
"""
return time.clock_gettime(CLOCK_MONOTONIC_COARSE)


monotonic_time_coarse = time.monotonic
with suppress(Exception):
if (
platform.system() == "Linux"
and abs(time.monotonic() - __monotonic_time_coarse()) < 1
):
monotonic_time_coarse = __monotonic_time_coarse
6 changes: 6 additions & 0 deletions tests/util/test_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from datetime import datetime, timedelta
import time

import pytest

Expand Down Expand Up @@ -719,3 +720,8 @@ def test_find_next_time_expression_tenth_second_pattern_does_not_drift_entering_
assert (next_target - prev_target).total_seconds() == 60
assert next_target.second == 10
prev_target = next_target


def test_monotonic_time_coarse():
"""Test monotonic time coarse."""
assert abs(time.monotonic() - dt_util.monotonic_time_coarse()) < 1

0 comments on commit 1589c06

Please sign in to comment.