Avoid duplicate timestamp conversions for websocket api and recorder (#108144)
* Avoid duplicate timestamp conversions for websocket api and recorder We convert the time from datetime to timestamps one per open websocket connection and the recorder for every state update. Only do the conversion once since its ~30% of the cost of building the state diff * more * two more * two more in live history
This commit is contained in:
parent
26058bf922
commit
3d595fff13
6 changed files with 54 additions and 19 deletions
|
@ -302,13 +302,9 @@ def _history_compressed_state(state: State, no_attributes: bool) -> dict[str, An
|
||||||
comp_state: dict[str, Any] = {COMPRESSED_STATE_STATE: state.state}
|
comp_state: dict[str, Any] = {COMPRESSED_STATE_STATE: state.state}
|
||||||
if not no_attributes or state.domain in history.NEED_ATTRIBUTE_DOMAINS:
|
if not no_attributes or state.domain in history.NEED_ATTRIBUTE_DOMAINS:
|
||||||
comp_state[COMPRESSED_STATE_ATTRIBUTES] = state.attributes
|
comp_state[COMPRESSED_STATE_ATTRIBUTES] = state.attributes
|
||||||
comp_state[COMPRESSED_STATE_LAST_UPDATED] = dt_util.utc_to_timestamp(
|
comp_state[COMPRESSED_STATE_LAST_UPDATED] = state.last_updated_timestamp
|
||||||
state.last_updated
|
|
||||||
)
|
|
||||||
if state.last_changed != state.last_updated:
|
if state.last_changed != state.last_updated:
|
||||||
comp_state[COMPRESSED_STATE_LAST_CHANGED] = dt_util.utc_to_timestamp(
|
comp_state[COMPRESSED_STATE_LAST_CHANGED] = state.last_changed_timestamp
|
||||||
state.last_changed
|
|
||||||
)
|
|
||||||
return comp_state
|
return comp_state
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ from homeassistant.components.recorder.models import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ICON, EVENT_STATE_CHANGED
|
from homeassistant.const import ATTR_ICON, EVENT_STATE_CHANGED
|
||||||
from homeassistant.core import Context, Event, State, callback
|
from homeassistant.core import Context, Event, State, callback
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.util.json import json_loads
|
from homeassistant.util.json import json_loads
|
||||||
from homeassistant.util.ulid import ulid_to_bytes
|
from homeassistant.util.ulid import ulid_to_bytes
|
||||||
|
|
||||||
|
@ -131,7 +130,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
|
||||||
context_id_bin=ulid_to_bytes(context.id),
|
context_id_bin=ulid_to_bytes(context.id),
|
||||||
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
|
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
|
||||||
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
|
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
|
||||||
time_fired_ts=dt_util.utc_to_timestamp(event.time_fired),
|
time_fired_ts=event.time_fired_timestamp,
|
||||||
row_id=hash(event),
|
row_id=hash(event),
|
||||||
)
|
)
|
||||||
# States are prefiltered so we never get states
|
# States are prefiltered so we never get states
|
||||||
|
@ -147,7 +146,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
|
||||||
context_id_bin=ulid_to_bytes(context.id),
|
context_id_bin=ulid_to_bytes(context.id),
|
||||||
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
|
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
|
||||||
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
|
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
|
||||||
time_fired_ts=dt_util.utc_to_timestamp(new_state.last_updated),
|
time_fired_ts=new_state.last_updated_timestamp,
|
||||||
row_id=hash(event),
|
row_id=hash(event),
|
||||||
icon=new_state.attributes.get(ATTR_ICON),
|
icon=new_state.attributes.get(ATTR_ICON),
|
||||||
)
|
)
|
||||||
|
|
|
@ -296,7 +296,7 @@ class Events(Base):
|
||||||
event_data=None,
|
event_data=None,
|
||||||
origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin),
|
origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin),
|
||||||
time_fired=None,
|
time_fired=None,
|
||||||
time_fired_ts=dt_util.utc_to_timestamp(event.time_fired),
|
time_fired_ts=event.time_fired_timestamp,
|
||||||
context_id=None,
|
context_id=None,
|
||||||
context_id_bin=ulid_to_bytes_or_none(event.context.id),
|
context_id_bin=ulid_to_bytes_or_none(event.context.id),
|
||||||
context_user_id=None,
|
context_user_id=None,
|
||||||
|
@ -495,16 +495,16 @@ class States(Base):
|
||||||
# None state means the state was removed from the state machine
|
# None state means the state was removed from the state machine
|
||||||
if state is None:
|
if state is None:
|
||||||
dbstate.state = ""
|
dbstate.state = ""
|
||||||
dbstate.last_updated_ts = dt_util.utc_to_timestamp(event.time_fired)
|
dbstate.last_updated_ts = event.time_fired_timestamp
|
||||||
dbstate.last_changed_ts = None
|
dbstate.last_changed_ts = None
|
||||||
return dbstate
|
return dbstate
|
||||||
|
|
||||||
dbstate.state = state.state
|
dbstate.state = state.state
|
||||||
dbstate.last_updated_ts = dt_util.utc_to_timestamp(state.last_updated)
|
dbstate.last_updated_ts = state.last_updated_timestamp
|
||||||
if state.last_updated == state.last_changed:
|
if state.last_updated == state.last_changed:
|
||||||
dbstate.last_changed_ts = None
|
dbstate.last_changed_ts = None
|
||||||
else:
|
else:
|
||||||
dbstate.last_changed_ts = dt_util.utc_to_timestamp(state.last_changed)
|
dbstate.last_changed_ts = state.last_changed_timestamp
|
||||||
|
|
||||||
return dbstate
|
return dbstate
|
||||||
|
|
||||||
|
|
|
@ -183,9 +183,9 @@ def _state_diff(
|
||||||
if old_state.state != new_state.state:
|
if old_state.state != new_state.state:
|
||||||
additions[COMPRESSED_STATE_STATE] = new_state.state
|
additions[COMPRESSED_STATE_STATE] = new_state.state
|
||||||
if old_state.last_changed != new_state.last_changed:
|
if old_state.last_changed != new_state.last_changed:
|
||||||
additions[COMPRESSED_STATE_LAST_CHANGED] = new_state.last_changed.timestamp()
|
additions[COMPRESSED_STATE_LAST_CHANGED] = new_state.last_changed_timestamp
|
||||||
elif old_state.last_updated != new_state.last_updated:
|
elif old_state.last_updated != new_state.last_updated:
|
||||||
additions[COMPRESSED_STATE_LAST_UPDATED] = new_state.last_updated.timestamp()
|
additions[COMPRESSED_STATE_LAST_UPDATED] = new_state.last_updated_timestamp
|
||||||
if old_state_context.parent_id != new_state_context.parent_id:
|
if old_state_context.parent_id != new_state_context.parent_id:
|
||||||
additions[COMPRESSED_STATE_CONTEXT] = {"parent_id": new_state_context.parent_id}
|
additions[COMPRESSED_STATE_CONTEXT] = {"parent_id": new_state_context.parent_id}
|
||||||
if old_state_context.user_id != new_state_context.user_id:
|
if old_state_context.user_id != new_state_context.user_id:
|
||||||
|
|
|
@ -1077,6 +1077,11 @@ class Event:
|
||||||
if not context.origin_event:
|
if not context.origin_event:
|
||||||
context.origin_event = self
|
context.origin_event = self
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def time_fired_timestamp(self) -> float:
|
||||||
|
"""Return time fired as a timestamp."""
|
||||||
|
return self.time_fired.timestamp()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _as_dict(self) -> dict[str, Any]:
|
def _as_dict(self) -> dict[str, Any]:
|
||||||
"""Create a dict representation of this Event.
|
"""Create a dict representation of this Event.
|
||||||
|
@ -1445,6 +1450,16 @@ class State:
|
||||||
"_", " "
|
"_", " "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def last_updated_timestamp(self) -> float:
|
||||||
|
"""Timestamp of last update."""
|
||||||
|
return self.last_updated.timestamp()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def last_changed_timestamp(self) -> float:
|
||||||
|
"""Timestamp of last change."""
|
||||||
|
return self.last_changed.timestamp()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _as_dict(self) -> dict[str, Any]:
|
def _as_dict(self) -> dict[str, Any]:
|
||||||
"""Return a dict representation of the State.
|
"""Return a dict representation of the State.
|
||||||
|
@ -1526,12 +1541,12 @@ class State:
|
||||||
COMPRESSED_STATE_STATE: self.state,
|
COMPRESSED_STATE_STATE: self.state,
|
||||||
COMPRESSED_STATE_ATTRIBUTES: self.attributes,
|
COMPRESSED_STATE_ATTRIBUTES: self.attributes,
|
||||||
COMPRESSED_STATE_CONTEXT: context,
|
COMPRESSED_STATE_CONTEXT: context,
|
||||||
COMPRESSED_STATE_LAST_CHANGED: dt_util.utc_to_timestamp(self.last_changed),
|
COMPRESSED_STATE_LAST_CHANGED: self.last_changed_timestamp,
|
||||||
}
|
}
|
||||||
if self.last_changed != self.last_updated:
|
if self.last_changed != self.last_updated:
|
||||||
compressed_state[COMPRESSED_STATE_LAST_UPDATED] = dt_util.utc_to_timestamp(
|
compressed_state[
|
||||||
self.last_updated
|
COMPRESSED_STATE_LAST_UPDATED
|
||||||
)
|
] = self.last_updated_timestamp
|
||||||
return compressed_state
|
return compressed_state
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
|
@ -625,6 +625,14 @@ def test_event_eq() -> None:
|
||||||
assert event1.as_dict() == event2.as_dict()
|
assert event1.as_dict() == event2.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def test_event_time_fired_timestamp() -> None:
|
||||||
|
"""Test time_fired_timestamp."""
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
event = ha.Event("some_type", {"some": "attr"}, time_fired=now)
|
||||||
|
assert event.time_fired_timestamp == now.timestamp()
|
||||||
|
assert event.time_fired_timestamp == now.timestamp()
|
||||||
|
|
||||||
|
|
||||||
def test_event_json_fragment() -> None:
|
def test_event_json_fragment() -> None:
|
||||||
"""Test event JSON fragments."""
|
"""Test event JSON fragments."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
@ -2453,6 +2461,23 @@ async def test_state_change_events_context_id_match_state_time(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_state_timestamps() -> None:
|
||||||
|
"""Test timestamp functions for State."""
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
state = ha.State(
|
||||||
|
"light.bedroom",
|
||||||
|
"on",
|
||||||
|
{"brightness": 100},
|
||||||
|
last_changed=now,
|
||||||
|
last_updated=now,
|
||||||
|
context=ha.Context(id="1234"),
|
||||||
|
)
|
||||||
|
assert state.last_changed_timestamp == now.timestamp()
|
||||||
|
assert state.last_changed_timestamp == now.timestamp()
|
||||||
|
assert state.last_updated_timestamp == now.timestamp()
|
||||||
|
assert state.last_updated_timestamp == now.timestamp()
|
||||||
|
|
||||||
|
|
||||||
async def test_state_firing_event_matches_context_id_ulid_time(
|
async def test_state_firing_event_matches_context_id_ulid_time(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue