Improve template ratelimit performance (#41741)

This commit is contained in:
J. Nick Koston 2020-10-15 05:02:05 -05:00 committed by GitHub
parent 1f07a4eba0
commit 21cc23244d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 16 deletions

View File

@ -758,15 +758,15 @@ class _TrackTemplateResultInfo:
for track_template_ in self._track_templates:
template = track_template_.template
variables = track_template_.variables
self._info[template] = info = template.async_render_to_info(variables)
self._info[template] = template.async_render_to_info(variables)
if self._info[template].exception:
if info.exception:
if raise_on_template_error:
raise self._info[template].exception
raise info.exception
_LOGGER.error(
"Error while processing template: %s",
track_template_.template,
exc_info=self._info[template].exception,
exc_info=info.exception,
)
self._track_state_changes = async_track_state_change_filtered(
@ -817,9 +817,7 @@ class _TrackTemplateResultInfo:
if event:
info = self._info[template]
if not self._rate_limit.async_has_timer(
template
) and not _event_triggers_rerender(event, info):
if not _event_triggers_rerender(event, info):
return False
if self._rate_limit.async_schedule_action(
@ -828,6 +826,8 @@ class _TrackTemplateResultInfo:
now,
self._refresh,
event,
(track_template_,),
True,
):
return False
@ -838,10 +838,12 @@ class _TrackTemplateResultInfo:
)
self._rate_limit.async_triggered(template, now)
self._info[template] = template.async_render_to_info(track_template_.variables)
self._info[template] = info = template.async_render_to_info(
track_template_.variables
)
try:
result: Union[str, TemplateError] = self._info[template].result()
result: Union[str, TemplateError] = info.result()
except TemplateError as ex:
result = ex
@ -857,12 +859,29 @@ class _TrackTemplateResultInfo:
return TrackTemplateResult(template, last_result, result)
@callback
def _refresh(self, event: Optional[Event]) -> None:
def _refresh(
self,
event: Optional[Event],
track_templates: Optional[Iterable[TrackTemplate]] = None,
replayed: Optional[bool] = False,
) -> None:
"""Refresh the template.
The event is the state_changed event that caused the refresh
to be considered.
track_templates is an optional list of TrackTemplate objects
to refresh. If not provided, all tracked templates will be
considered.
replayed is True if the event is being replayed because the
rate limit was hit.
"""
updates = []
info_changed = False
now = dt_util.utcnow()
now = event.time_fired if not replayed and event else dt_util.utcnow()
for track_template_ in self._track_templates:
for track_template_ in track_templates or self._track_templates:
update = self._render_template_if_ready(track_template_, now, event)
if not update:
continue

View File

@ -26,6 +26,8 @@ class KeyedRateLimit:
@callback
def async_has_timer(self, key: Hashable) -> bool:
"""Check if a rate limit timer is running."""
if not self._rate_limit_timers:
return False
return key in self._rate_limit_timers
@callback
@ -37,7 +39,7 @@ class KeyedRateLimit:
@callback
def async_cancel_timer(self, key: Hashable) -> None:
"""Cancel a rate limit time that will call the action."""
if not self.async_has_timer(key):
if not self._rate_limit_timers or not self.async_has_timer(key):
return
self._rate_limit_timers.pop(key).cancel()
@ -71,10 +73,14 @@ class KeyedRateLimit:
Return None
"""
if rate_limit is None or key not in self._last_triggered:
if rate_limit is None:
return None
next_call_time = self._last_triggered[key] + rate_limit
last_triggered = self._last_triggered.get(key)
if not last_triggered:
return None
next_call_time = last_triggered + rate_limit
if next_call_time <= now:
self.async_cancel_timer(key)

View File

@ -213,6 +213,10 @@ class RenderInfo:
split_entity_id(entity_id)[0] in self.domains or entity_id in self.entities
)
def _filter_entities(self, entity_id: str) -> bool:
"""Template should re-render if the entity state changes when we match specific entities."""
return entity_id in self.entities
def _filter_lifecycle_domains(self, entity_id: str) -> bool:
"""Template should re-render if the entity is added or removed with domains watched."""
return split_entity_id(entity_id)[0] in self.domains_lifecycle
@ -255,8 +259,10 @@ class RenderInfo:
if self.all_states:
return
if self.entities or self.domains:
if self.domains:
self.filter = self._filter_domains_and_entities
elif self.entities:
self.filter = self._filter_entities
else:
self.filter = _false
@ -264,6 +270,15 @@ class RenderInfo:
class Template:
"""Class to hold a template and manage caching and rendering."""
__slots__ = (
"__weakref__",
"template",
"hass",
"is_static",
"_compiled_code",
"_compiled",
)
def __init__(self, template, hass=None):
"""Instantiate a template."""
if not isinstance(template, str):