Restrict recorder query to include max age (#18231)

* Update query to include maxAge

Updated the query from recorded to include MaxAge if set; reducing the amount of records retrieved that would otherwise be purged anyway for the sensor.

* Added newline in docstring

Added newline in docstring.

* Add test + small fix

Added test to ensure query works correctly
Query should be greater then or equal instead of greater then. Fixed.

* Fixed lint issue

Fixed lint issue.
This commit is contained in:
ehendrix23 2018-11-08 16:08:36 -07:00 committed by Diogo Gomes
parent 05eac915d1
commit ae85baf396
2 changed files with 73 additions and 1 deletions

View File

@ -247,6 +247,9 @@ class StatisticsSensor(Entity):
The query will get the list of states in DESCENDING order so that we The query will get the list of states in DESCENDING order so that we
can limit the result to self._sample_size. Afterwards reverse the can limit the result to self._sample_size. Afterwards reverse the
list so that we get it in the right order again. list so that we get it in the right order again.
If MaxAge is provided then query will restrict to entries younger then
current datetime - MaxAge.
""" """
from homeassistant.components.recorder.models import States from homeassistant.components.recorder.models import States
_LOGGER.debug("%s: initializing values from the database", _LOGGER.debug("%s: initializing values from the database",
@ -254,7 +257,17 @@ class StatisticsSensor(Entity):
with session_scope(hass=self._hass) as session: with session_scope(hass=self._hass) as session:
query = session.query(States)\ query = session.query(States)\
.filter(States.entity_id == self._entity_id.lower())\ .filter(States.entity_id == self._entity_id.lower())
if self._max_age is not None:
records_older_then = dt_util.utcnow() - self._max_age
_LOGGER.debug("%s: retrieve records not older then %s",
self.entity_id, records_older_then)
query = query.filter(States.last_updated >= records_older_then)
else:
_LOGGER.debug("%s: retrieving all records.", self.entity_id)
query = query\
.order_by(States.last_updated.desc())\ .order_by(States.last_updated.desc())\
.limit(self._sampling_size) .limit(self._sampling_size)
states = execute(query) states = execute(query)

View File

@ -3,6 +3,7 @@ import unittest
import statistics import statistics
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
from homeassistant.components.sensor.statistics import StatisticsSensor
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN) ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN)
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -233,3 +234,61 @@ class TestStatisticsSensor(unittest.TestCase):
# check if the result is as in test_sensor_source() # check if the result is as in test_sensor_source()
state = self.hass.states.get('sensor.test_mean') state = self.hass.states.get('sensor.test_mean')
assert str(self.mean) == state.state assert str(self.mean) == state.state
def test_initialize_from_database_with_maxage(self):
"""Test initializing the statistics from the database."""
mock_data = {
'return_time': datetime(2017, 8, 2, 12, 23, 42,
tzinfo=dt_util.UTC),
}
def mock_now():
return mock_data['return_time']
# Testing correct retrieval from recorder, thus we do not
# want purging to occur within the class itself.
def mock_purge(self):
return
# Set maximum age to 3 hours.
max_age = 3
# Determine what our minimum age should be based on test values.
expected_min_age = mock_data['return_time'] + \
timedelta(hours=len(self.values) - max_age)
# enable the recorder
init_recorder_component(self.hass)
with patch('homeassistant.components.sensor.statistics.dt_util.utcnow',
new=mock_now), \
patch.object(StatisticsSensor, '_purge_old', mock_purge):
# store some values
for value in self.values:
self.hass.states.set('sensor.test_monitored', value,
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
# insert the next value 1 hour later
mock_data['return_time'] += timedelta(hours=1)
# wait for the recorder to really store the data
self.hass.data[recorder.DATA_INSTANCE].block_till_done()
# only now create the statistics component, so that it must read
# the data from the database
assert setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'statistics',
'name': 'test',
'entity_id': 'sensor.test_monitored',
'sampling_size': 100,
'max_age': {'hours': max_age}
}
})
# check if the result is as in test_sensor_source()
state = self.hass.states.get('sensor.test_mean')
assert expected_min_age == state.attributes.get('min_age')
# The max_age timestamp should be 1 hour before what we have right
# now in mock_data['return_time'].
assert mock_data['return_time'] == state.attributes.get('max_age') +\
timedelta(hours=1)