mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Avoid hashing attributes when they are already in the cache (#68395)
This commit is contained in:
parent
a91888a7f8
commit
0c0df07c52
@ -985,7 +985,7 @@ class Recorder(threading.Thread):
|
|||||||
if event.event_type == EVENT_STATE_CHANGED:
|
if event.event_type == EVENT_STATE_CHANGED:
|
||||||
try:
|
try:
|
||||||
dbstate = States.from_event(event)
|
dbstate = States.from_event(event)
|
||||||
dbstate_attributes = StateAttributes.from_event(event)
|
shared_attrs = StateAttributes.shared_attrs_from_event(event)
|
||||||
except (TypeError, ValueError) as ex:
|
except (TypeError, ValueError) as ex:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"State is not JSON serializable: %s: %s",
|
"State is not JSON serializable: %s: %s",
|
||||||
@ -995,27 +995,33 @@ class Recorder(threading.Thread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
dbstate.attributes = None
|
dbstate.attributes = None
|
||||||
shared_attrs = dbstate_attributes.shared_attrs
|
|
||||||
# Matching attributes found in the pending commit
|
# Matching attributes found in the pending commit
|
||||||
if pending_attributes := self._pending_state_attributes.get(shared_attrs):
|
if pending_attributes := self._pending_state_attributes.get(shared_attrs):
|
||||||
dbstate.state_attributes = pending_attributes
|
dbstate.state_attributes = pending_attributes
|
||||||
# Matching attributes id found in the cache
|
# Matching attributes id found in the cache
|
||||||
elif attributes_id := self._state_attributes_ids.get(shared_attrs):
|
elif attributes_id := self._state_attributes_ids.get(shared_attrs):
|
||||||
dbstate.attributes_id = attributes_id
|
dbstate.attributes_id = attributes_id
|
||||||
# Matching attributes found in the database
|
|
||||||
elif (
|
|
||||||
attributes := self.event_session.query(StateAttributes.attributes_id)
|
|
||||||
.filter(StateAttributes.hash == dbstate_attributes.hash)
|
|
||||||
.filter(StateAttributes.shared_attrs == shared_attrs)
|
|
||||||
.first()
|
|
||||||
):
|
|
||||||
dbstate.attributes_id = attributes[0]
|
|
||||||
self._state_attributes_ids[shared_attrs] = attributes[0]
|
|
||||||
# No matching attributes found, save them in the DB
|
|
||||||
else:
|
else:
|
||||||
dbstate.state_attributes = dbstate_attributes
|
attr_hash = StateAttributes.hash_shared_attrs(shared_attrs)
|
||||||
self._pending_state_attributes[shared_attrs] = dbstate_attributes
|
# Matching attributes found in the database
|
||||||
self.event_session.add(dbstate_attributes)
|
if (
|
||||||
|
attributes := self.event_session.query(
|
||||||
|
StateAttributes.attributes_id
|
||||||
|
)
|
||||||
|
.filter(StateAttributes.hash == attr_hash)
|
||||||
|
.filter(StateAttributes.shared_attrs == shared_attrs)
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
dbstate.attributes_id = attributes[0]
|
||||||
|
self._state_attributes_ids[shared_attrs] = attributes[0]
|
||||||
|
# No matching attributes found, save them in the DB
|
||||||
|
else:
|
||||||
|
dbstate_attributes = StateAttributes(
|
||||||
|
shared_attrs=shared_attrs, hash=attr_hash
|
||||||
|
)
|
||||||
|
dbstate.state_attributes = dbstate_attributes
|
||||||
|
self._pending_state_attributes[shared_attrs] = dbstate_attributes
|
||||||
|
self.event_session.add(dbstate_attributes)
|
||||||
|
|
||||||
if old_state := self._old_states.pop(dbstate.entity_id, None):
|
if old_state := self._old_states.pop(dbstate.entity_id, None):
|
||||||
if old_state.state_id:
|
if old_state.state_id:
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
"""Recorder constants."""
|
"""Recorder constants."""
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
import json
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
|
|
||||||
DATA_INSTANCE = "recorder_instance"
|
DATA_INSTANCE = "recorder_instance"
|
||||||
SQLITE_URL_PREFIX = "sqlite://"
|
SQLITE_URL_PREFIX = "sqlite://"
|
||||||
DOMAIN = "recorder"
|
DOMAIN = "recorder"
|
||||||
@ -17,3 +23,5 @@ MAX_QUEUE_BACKLOG = 30000
|
|||||||
MAX_ROWS_TO_PURGE = 998
|
MAX_ROWS_TO_PURGE = 998
|
||||||
|
|
||||||
DB_WORKER_PREFIX = "DbWorker"
|
DB_WORKER_PREFIX = "DbWorker"
|
||||||
|
|
||||||
|
JSON_DUMP: Final = partial(json.dumps, cls=JSONEncoder, separators=(",", ":"))
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import TypedDict, overload
|
from typing import Any, TypedDict, overload
|
||||||
|
|
||||||
from fnvhash import fnv1a_32
|
from fnvhash import fnv1a_32
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
@ -35,9 +35,10 @@ from homeassistant.const import (
|
|||||||
MAX_LENGTH_STATE_STATE,
|
MAX_LENGTH_STATE_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
||||||
from homeassistant.helpers.json import JSONEncoder
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from .const import JSON_DUMP
|
||||||
|
|
||||||
# SQLAlchemy Schema
|
# SQLAlchemy Schema
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
@ -116,8 +117,7 @@ class Events(Base): # type: ignore[misc,valid-type]
|
|||||||
"""Create an event database object from a native event."""
|
"""Create an event database object from a native event."""
|
||||||
return Events(
|
return Events(
|
||||||
event_type=event.event_type,
|
event_type=event.event_type,
|
||||||
event_data=event_data
|
event_data=event_data or JSON_DUMP(event.data),
|
||||||
or json.dumps(event.data, cls=JSONEncoder, separators=(",", ":")),
|
|
||||||
origin=str(event.origin.value),
|
origin=str(event.origin.value),
|
||||||
time_fired=event.time_fired,
|
time_fired=event.time_fired,
|
||||||
context_id=event.context.id,
|
context_id=event.context.id,
|
||||||
@ -186,15 +186,13 @@ class States(Base): # type: ignore[misc,valid-type]
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_event(event):
|
def from_event(event) -> States:
|
||||||
"""Create object from a state_changed event."""
|
"""Create object from a state_changed event."""
|
||||||
entity_id = event.data["entity_id"]
|
entity_id = event.data["entity_id"]
|
||||||
state = event.data.get("new_state")
|
state: State | None = event.data.get("new_state")
|
||||||
|
dbstate = States(entity_id=entity_id, attributes=None)
|
||||||
|
|
||||||
dbstate = States(entity_id=entity_id)
|
# None state means the state was removed from the state machine
|
||||||
dbstate.attributes = None
|
|
||||||
|
|
||||||
# State got deleted
|
|
||||||
if state is None:
|
if state is None:
|
||||||
dbstate.state = ""
|
dbstate.state = ""
|
||||||
dbstate.domain = split_entity_id(entity_id)[0]
|
dbstate.domain = split_entity_id(entity_id)[0]
|
||||||
@ -208,7 +206,7 @@ class States(Base): # type: ignore[misc,valid-type]
|
|||||||
|
|
||||||
return dbstate
|
return dbstate
|
||||||
|
|
||||||
def to_native(self, validate_entity_id=True):
|
def to_native(self, validate_entity_id: bool = True) -> State | None:
|
||||||
"""Convert to an HA state object."""
|
"""Convert to an HA state object."""
|
||||||
try:
|
try:
|
||||||
return State(
|
return State(
|
||||||
@ -221,7 +219,7 @@ class States(Base): # type: ignore[misc,valid-type]
|
|||||||
process_timestamp(self.last_updated),
|
process_timestamp(self.last_updated),
|
||||||
# Join the events table on event_id to get the context instead
|
# Join the events table on event_id to get the context instead
|
||||||
# as it will always be there for state_changed events
|
# as it will always be there for state_changed events
|
||||||
context=Context(id=None),
|
context=Context(id=None), # type: ignore[arg-type]
|
||||||
validate_entity_id=validate_entity_id,
|
validate_entity_id=validate_entity_id,
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -251,23 +249,29 @@ class StateAttributes(Base): # type: ignore[misc,valid-type]
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_event(event):
|
def from_event(event: Event) -> StateAttributes:
|
||||||
"""Create object from a state_changed event."""
|
"""Create object from a state_changed event."""
|
||||||
state = event.data.get("new_state")
|
state: State | None = event.data.get("new_state")
|
||||||
dbstate = StateAttributes()
|
# None state means the state was removed from the state machine
|
||||||
# State got deleted
|
dbstate = StateAttributes(
|
||||||
if state is None:
|
shared_attrs="{}" if state is None else JSON_DUMP(state.attributes)
|
||||||
dbstate.shared_attrs = "{}"
|
)
|
||||||
else:
|
dbstate.hash = StateAttributes.hash_shared_attrs(dbstate.shared_attrs)
|
||||||
dbstate.shared_attrs = json.dumps(
|
|
||||||
dict(state.attributes),
|
|
||||||
cls=JSONEncoder,
|
|
||||||
separators=(",", ":"),
|
|
||||||
)
|
|
||||||
dbstate.hash = fnv1a_32(dbstate.shared_attrs.encode("utf-8"))
|
|
||||||
return dbstate
|
return dbstate
|
||||||
|
|
||||||
def to_native(self):
|
@staticmethod
|
||||||
|
def shared_attrs_from_event(event: Event) -> str:
|
||||||
|
"""Create shared_attrs from a state_changed event."""
|
||||||
|
state: State | None = event.data.get("new_state")
|
||||||
|
# None state means the state was removed from the state machine
|
||||||
|
return "{}" if state is None else JSON_DUMP(state.attributes)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hash_shared_attrs(shared_attrs: str) -> int:
|
||||||
|
"""Return the hash of json encoded shared attributes."""
|
||||||
|
return fnv1a_32(shared_attrs.encode("utf-8"))
|
||||||
|
|
||||||
|
def to_native(self) -> dict[str, Any]:
|
||||||
"""Convert to an HA state object."""
|
"""Convert to an HA state object."""
|
||||||
try:
|
try:
|
||||||
return json.loads(self.shared_attrs)
|
return json.loads(self.shared_attrs)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user