diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index c25402597cc..4bb51a9975f 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -48,8 +48,8 @@ def async_track_state_change(hass, entity_ids, action, from_state=None, Must be run within the event loop. """ - from_state = _process_state_match(from_state) - to_state = _process_state_match(to_state) + match_from_state = _process_state_match(from_state) + match_to_state = _process_state_match(to_state) # Ensure it is a lowercase list with entity ids we want to match on if entity_ids == MATCH_ALL: @@ -66,17 +66,15 @@ def async_track_state_change(hass, entity_ids, action, from_state=None, event.data.get('entity_id') not in entity_ids: return - if event.data.get('old_state') is not None: - old_state = event.data['old_state'].state - else: - old_state = None + old_state = event.data.get('old_state') + if old_state is not None: + old_state = old_state.state - if event.data.get('new_state') is not None: - new_state = event.data['new_state'].state - else: - new_state = None + new_state = event.data.get('new_state') + if new_state is not None: + new_state = new_state.state - if _matcher(old_state, from_state) and _matcher(new_state, to_state): + if match_from_state(old_state) and match_to_state(new_state): hass.async_run_job(action, event.data.get('entity_id'), event.data.get('old_state'), event.data.get('new_state')) @@ -336,15 +334,10 @@ def async_track_utc_time_change(hass, action, year=None, month=None, day=None, if local: now = dt_util.as_local(now) - mat = _matcher # pylint: disable=too-many-boolean-expressions - if mat(now.year, year) and \ - mat(now.month, month) and \ - mat(now.day, day) and \ - mat(now.hour, hour) and \ - mat(now.minute, minute) and \ - mat(now.second, second): + if second(now.second) and minute(now.minute) and hour(now.hour) and \ + day(now.day) and month(now.month) and year(now.year): hass.async_run_job(action, now) @@ -368,34 +361,28 @@ track_time_change = threaded_listener_factory(async_track_time_change) def _process_state_match(parameter): - """Wrap parameter in a tuple if it is not one and returns it.""" + """Convert parameter to function that matches input against parameter.""" if parameter is None or parameter == MATCH_ALL: - return MATCH_ALL + return lambda _: True + elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): - return (parameter,) - return tuple(parameter) + return lambda state: state == parameter + + parameter = tuple(parameter) + return lambda state: state in parameter def _process_time_match(parameter): """Wrap parameter in a tuple if it is not one and returns it.""" if parameter is None or parameter == MATCH_ALL: - return MATCH_ALL + return lambda _: True + elif isinstance(parameter, str) and parameter.startswith('/'): - return parameter + parameter = float(parameter[1:]) + return lambda time: time % parameter == 0 + elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): - return (parameter,) - return tuple(parameter) + return lambda time: time == parameter - -def _matcher(subject, pattern): - """Return True if subject matches the pattern. - - Pattern is either a tuple of allowed subjects or a `MATCH_ALL`. - """ - if isinstance(pattern, str) and pattern.startswith('/'): - try: - return subject % float(pattern.lstrip('/')) == 0 - except ValueError: - return False - - return MATCH_ALL == pattern or subject in pattern + parameter = tuple(parameter) + return lambda time: time in parameter diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 73ad8bc0cd2..100d3aa3508 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -2,10 +2,14 @@ import asyncio import argparse from contextlib import suppress +from datetime import datetime import logging from timeit import default_timer as timer +from homeassistant.const import ( + EVENT_TIME_CHANGED, ATTR_NOW, EVENT_STATE_CHANGED) from homeassistant import core +from homeassistant.util import dt as dt_util BENCHMARKS = {} @@ -64,11 +68,79 @@ def async_million_events(hass): hass.bus.async_listen(event_name, listener) - start = timer() - for _ in range(10**6): hass.bus.async_fire(event_name) + start = timer() + + yield from event.wait() + + return timer() - start + + +@benchmark +@asyncio.coroutine +# pylint: disable=invalid-name +def async_million_time_changed_helper(hass): + """Run a million events through time changed helper.""" + count = 0 + event = asyncio.Event(loop=hass.loop) + + @core.callback + def listener(_): + """Handle event.""" + nonlocal count + count += 1 + + if count == 10**6: + event.set() + + hass.helpers.event.async_track_time_change(listener, minute=0, second=0) + event_data = { + ATTR_NOW: datetime(2017, 10, 10, 15, 0, 0, tzinfo=dt_util.UTC) + } + + for _ in range(10**6): + hass.bus.async_fire(EVENT_TIME_CHANGED, event_data) + + start = timer() + + yield from event.wait() + + return timer() - start + + +@benchmark +@asyncio.coroutine +# pylint: disable=invalid-name +def async_million_state_changed_helper(hass): + """Run a million events through state changed helper.""" + count = 0 + entity_id = 'light.kitchen' + event = asyncio.Event(loop=hass.loop) + + @core.callback + def listener(*args): + """Handle event.""" + nonlocal count + count += 1 + + if count == 10**6: + event.set() + + hass.helpers.event.async_track_state_change( + entity_id, listener, 'off', 'on') + event_data = { + 'entity_id': entity_id, + 'old_state': core.State(entity_id, 'off'), + 'new_state': core.State(entity_id, 'on'), + } + + for _ in range(10**6): + hass.bus.async_fire(EVENT_STATE_CHANGED, event_data) + + start = timer() + yield from event.wait() return timer() - start diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 222bb46f8b6..7d601c7a78d 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -5,6 +5,7 @@ import unittest from datetime import datetime, timedelta from astral import Astral +import pytest from homeassistant.setup import setup_component import homeassistant.core as ha @@ -634,8 +635,9 @@ class TestEventHelpers(unittest.TestCase): """Test periodic tasks with wrong input.""" specific_runs = [] - track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), year='/two') + with pytest.raises(ValueError): + track_utc_time_change( + self.hass, lambda x: specific_runs.append(1), year='/two') self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) self.hass.block_till_done()