From 0a219081eac726416a28d0a11bce96b793122f04 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Mon, 15 Jun 2020 14:54:20 -0500
Subject: [PATCH] Only process logbook timestamps for events we will keep
 (#36792)

* Only process logbook timestamps for events we will keep

Since we group by minute we were previously processing
every timestamp. We can avoid this by making all the
minute checks use the unprocessed datetime since
the groupings will be the same regardless of timezone.

This reduces the number of datetime object recreations
by at least an order of magnitude.
---
 homeassistant/components/logbook/__init__.py | 19 +++---
 tests/components/alexa/test_init.py          |  9 +--
 tests/components/automation/test_init.py     |  7 ++-
 tests/components/homekit/test_init.py        |  6 +-
 tests/components/logbook/test_init.py        | 62 +++++++++++++++-----
 tests/components/script/test_init.py         |  7 ++-
 6 files changed, 76 insertions(+), 34 deletions(-)

diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py
index 1c75fe7021d..3af1650eae1 100644
--- a/homeassistant/components/logbook/__init__.py
+++ b/homeassistant/components/logbook/__init__.py
@@ -205,7 +205,7 @@ def humanify(hass, events, prev_states=None):
 
     # Group events in batches of GROUP_BY_MINUTES
     for _, g_events in groupby(
-        events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES
+        events, lambda event: event.time_fired_minute // GROUP_BY_MINUTES
     ):
 
         events_batch = list(g_events)
@@ -224,16 +224,16 @@ def humanify(hass, events, prev_states=None):
                     last_sensor_event[event.entity_id] = event
 
             elif event.event_type == EVENT_HOMEASSISTANT_STOP:
-                if event.time_fired.minute in start_stop_events:
+                if event.time_fired_minute in start_stop_events:
                     continue
 
-                start_stop_events[event.time_fired.minute] = 1
+                start_stop_events[event.time_fired_minute] = 1
 
             elif event.event_type == EVENT_HOMEASSISTANT_START:
-                if event.time_fired.minute not in start_stop_events:
+                if event.time_fired_minute not in start_stop_events:
                     continue
 
-                start_stop_events[event.time_fired.minute] = 2
+                start_stop_events[event.time_fired_minute] = 2
 
         # Yield entries
         external_events = hass.data.get(DOMAIN, {})
@@ -283,7 +283,7 @@ def humanify(hass, events, prev_states=None):
                 }
 
             elif event.event_type == EVENT_HOMEASSISTANT_START:
-                if start_stop_events.get(event.time_fired.minute) == 2:
+                if start_stop_events.get(event.time_fired_minute) == 2:
                     continue
 
                 yield {
@@ -296,7 +296,7 @@ def humanify(hass, events, prev_states=None):
                 }
 
             elif event.event_type == EVENT_HOMEASSISTANT_STOP:
-                if start_stop_events.get(event.time_fired.minute) == 2:
+                if start_stop_events.get(event.time_fired_minute) == 2:
                     action = "restarted"
                 else:
                     action = "stopped"
@@ -597,6 +597,11 @@ class LazyEventPartialState:
                 self._event_data = json.loads(self._row.event_data)
         return self._event_data
 
+    @property
+    def time_fired_minute(self):
+        """Minute the event was fired not converted."""
+        return self._row.time_fired.minute
+
     @property
     def time_fired(self):
         """Time event was fired in utc."""
diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py
index 212b48cb436..2a9e72aaef7 100644
--- a/tests/components/alexa/test_init.py
+++ b/tests/components/alexa/test_init.py
@@ -1,9 +1,10 @@
 """Tests for alexa."""
 from homeassistant.components import logbook
 from homeassistant.components.alexa.const import EVENT_ALEXA_SMART_HOME
-import homeassistant.core as ha
 from homeassistant.setup import async_setup_component
 
+from tests.components.logbook.test_init import MockLazyEventPartialState
+
 
 async def test_humanify_alexa_event(hass):
     """Test humanifying Alexa event."""
@@ -14,11 +15,11 @@ async def test_humanify_alexa_event(hass):
         logbook.humanify(
             hass,
             [
-                ha.Event(
+                MockLazyEventPartialState(
                     EVENT_ALEXA_SMART_HOME,
                     {"request": {"namespace": "Alexa.Discovery", "name": "Discover"}},
                 ),
-                ha.Event(
+                MockLazyEventPartialState(
                     EVENT_ALEXA_SMART_HOME,
                     {
                         "request": {
@@ -28,7 +29,7 @@ async def test_humanify_alexa_event(hass):
                         }
                     },
                 ),
-                ha.Event(
+                MockLazyEventPartialState(
                     EVENT_ALEXA_SMART_HOME,
                     {
                         "request": {
diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py
index d17e55691bc..2ac89b68a2d 100644
--- a/tests/components/automation/test_init.py
+++ b/tests/components/automation/test_init.py
@@ -17,7 +17,7 @@ from homeassistant.const import (
     STATE_OFF,
     STATE_ON,
 )
-from homeassistant.core import Context, CoreState, Event, State
+from homeassistant.core import Context, CoreState, State
 from homeassistant.exceptions import HomeAssistantError, Unauthorized
 from homeassistant.setup import async_setup_component
 import homeassistant.util.dt as dt_util
@@ -30,6 +30,7 @@ from tests.common import (
     mock_restore_cache,
 )
 from tests.components.automation import common
+from tests.components.logbook.test_init import MockLazyEventPartialState
 
 
 @pytest.fixture
@@ -1044,11 +1045,11 @@ async def test_logbook_humanify_automation_triggered_event(hass):
         logbook.humanify(
             hass,
             [
-                Event(
+                MockLazyEventPartialState(
                     EVENT_AUTOMATION_TRIGGERED,
                     {ATTR_ENTITY_ID: "automation.hello", ATTR_NAME: "Hello Automation"},
                 ),
-                Event(
+                MockLazyEventPartialState(
                     EVENT_AUTOMATION_TRIGGERED,
                     {ATTR_ENTITY_ID: "automation.bye", ATTR_NAME: "Bye Automation"},
                 ),
diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py
index 6d01413da8f..05667a5f2a0 100644
--- a/tests/components/homekit/test_init.py
+++ b/tests/components/homekit/test_init.py
@@ -1,5 +1,4 @@
 """Test HomeKit initialization."""
-from homeassistant import core as ha
 from homeassistant.components import logbook
 from homeassistant.components.homekit.const import (
     ATTR_DISPLAY_NAME,
@@ -11,6 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE
 from homeassistant.setup import async_setup_component
 
 from tests.async_mock import patch
+from tests.components.logbook.test_init import MockLazyEventPartialState
 
 
 async def test_humanify_homekit_changed_event(hass, hk_driver):
@@ -22,7 +22,7 @@ async def test_humanify_homekit_changed_event(hass, hk_driver):
         logbook.humanify(
             hass,
             [
-                ha.Event(
+                MockLazyEventPartialState(
                     EVENT_HOMEKIT_CHANGED,
                     {
                         ATTR_ENTITY_ID: "lock.front_door",
@@ -30,7 +30,7 @@ async def test_humanify_homekit_changed_event(hass, hk_driver):
                         ATTR_SERVICE: "lock",
                     },
                 ),
-                ha.Event(
+                MockLazyEventPartialState(
                     EVENT_HOMEKIT_CHANGED,
                     {
                         ATTR_ENTITY_ID: "cover.window",
diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py
index 5facb8b08e7..472b1c7056b 100644
--- a/tests/components/logbook/test_init.py
+++ b/tests/components/logbook/test_init.py
@@ -167,7 +167,11 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config({})
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -198,7 +202,11 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config({})
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -226,7 +234,11 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config({})
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -260,7 +272,11 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config(config[logbook.DOMAIN])
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -295,8 +311,8 @@ class TestComponentLogbook(unittest.TestCase):
         events = [
             e
             for e in (
-                ha.Event(EVENT_HOMEASSISTANT_START),
-                ha.Event(EVENT_ALEXA_SMART_HOME),
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
+                MockLazyEventPartialState(EVENT_ALEXA_SMART_HOME),
                 eventA,
                 eventB,
             )
@@ -333,7 +349,11 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config(config[logbook.DOMAIN])
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -354,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase):
         pointA = dt_util.utcnow()
         pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
 
-        event_alexa = ha.Event(
+        event_alexa = MockLazyEventPartialState(
             EVENT_ALEXA_SMART_HOME,
             {"request": {"namespace": "Alexa.Discovery", "name": "Discover"}},
         )
@@ -373,7 +393,12 @@ class TestComponentLogbook(unittest.TestCase):
         entities_filter = logbook._generate_filter_from_config(config[logbook.DOMAIN])
         events = [
             e
-            for e in (ha.Event(EVENT_HOMEASSISTANT_START), event_alexa, eventA, eventB,)
+            for e in (
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
+                event_alexa,
+                eventA,
+                eventB,
+            )
             if logbook._keep_event(self.hass, e, entities_filter)
         ]
         entries = list(logbook.humanify(self.hass, events))
@@ -420,7 +445,7 @@ class TestComponentLogbook(unittest.TestCase):
         events = [
             e
             for e in (
-                ha.Event(EVENT_HOMEASSISTANT_START),
+                MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
                 eventA1,
                 eventA2,
                 eventA3,
@@ -491,8 +516,8 @@ class TestComponentLogbook(unittest.TestCase):
             logbook.humanify(
                 self.hass,
                 (
-                    ha.Event(EVENT_HOMEASSISTANT_STOP),
-                    ha.Event(EVENT_HOMEASSISTANT_START),
+                    MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
+                    MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
                 ),
             )
         )
@@ -511,7 +536,7 @@ class TestComponentLogbook(unittest.TestCase):
             logbook.humanify(
                 self.hass,
                 (
-                    ha.Event(EVENT_HOMEASSISTANT_START),
+                    MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
                     self.create_state_changed_event(pointA, entity_id, 10),
                 ),
             )
@@ -1129,7 +1154,7 @@ class TestComponentLogbook(unittest.TestCase):
             logbook.humanify(
                 self.hass,
                 (
-                    ha.Event(
+                    MockLazyEventPartialState(
                         logbook.EVENT_LOGBOOK_ENTRY,
                         {
                             logbook.ATTR_NAME: name,
@@ -1465,3 +1490,12 @@ async def test_logbook_view_end_time_entity(hass, hass_client):
     json = await response.json()
     assert len(json) == 1
     assert json[0]["entity_id"] == entity_id_test
+
+
+class MockLazyEventPartialState(ha.Event):
+    """Minimal mock of a Lazy event."""
+
+    @property
+    def time_fired_minute(self):
+        """Minute the event was fired."""
+        return self.time_fired.minute
diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py
index 21f14ad9c88..6ec02d05575 100644
--- a/tests/components/script/test_init.py
+++ b/tests/components/script/test_init.py
@@ -14,7 +14,7 @@ from homeassistant.const import (
     SERVICE_TURN_OFF,
     SERVICE_TURN_ON,
 )
-from homeassistant.core import Context, Event, callback, split_entity_id
+from homeassistant.core import Context, callback, split_entity_id
 from homeassistant.exceptions import ServiceNotFound
 from homeassistant.helpers.service import async_get_all_descriptions
 from homeassistant.loader import bind_hass
@@ -22,6 +22,7 @@ from homeassistant.setup import async_setup_component, setup_component
 
 from tests.async_mock import Mock, patch
 from tests.common import get_test_home_assistant
+from tests.components.logbook.test_init import MockLazyEventPartialState
 
 ENTITY_ID = "script.test"
 
@@ -477,11 +478,11 @@ async def test_logbook_humanify_script_started_event(hass):
         logbook.humanify(
             hass,
             [
-                Event(
+                MockLazyEventPartialState(
                     EVENT_SCRIPT_STARTED,
                     {ATTR_ENTITY_ID: "script.hello", ATTR_NAME: "Hello Script"},
                 ),
-                Event(
+                MockLazyEventPartialState(
                     EVENT_SCRIPT_STARTED,
                     {ATTR_ENTITY_ID: "script.bye", ATTR_NAME: "Bye Script"},
                 ),
-- 
GitLab