Skip to content

Commit 0b17c69

Browse files
Fix resampler behaviour when resampling is to late
Previous solution used Timer, that was fired after the specific time. Also it didn't take time difference spent on resampling into account when sleeping. In result the time difference between resampling period was constantly increasing. And there was no option to catch up with resampling. Signed-off-by: ela-kotulska-frequenz <[email protected]>
1 parent 6f4e5ec commit 0b17c69

File tree

2 files changed

+34
-25
lines changed

2 files changed

+34
-25
lines changed

src/frequenz/sdk/timeseries/_resampling.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,30 @@ def remove_timeseries(self, source: Source) -> bool:
429429
return False
430430
return True
431431

432+
async def _wait_for_next_resampling_period(self) -> None:
433+
"""Wait for next resampling period.
434+
435+
If resampling period already started, then return without sleeping.
436+
That would allow us to catch up with resampling.
437+
Print warning if function woke up to late.
438+
"""
439+
now = datetime.now(tz=timezone.utc)
440+
if self._window_end > now:
441+
sleep_for = self._window_end - now
442+
await asyncio.sleep(sleep_for.total_seconds())
443+
444+
timer_error_s = (now - self._window_end).total_seconds()
445+
if timer_error_s > (self._config.resampling_period_s / 10.0):
446+
_logger.warning(
447+
"The resampling task woke up too late. Resampling should have started "
448+
"at %s, but it started at %s (%s seconds difference; resampling "
449+
"period is %s seconds)",
450+
self._window_end,
451+
now,
452+
timer_error_s,
453+
self._config.resampling_period_s,
454+
)
455+
432456
async def resample(self, *, one_shot: bool = False) -> None:
433457
"""Start resampling all known timeseries.
434458
@@ -447,12 +471,17 @@ async def resample(self, *, one_shot: bool = False) -> None:
447471
timeseries from the resampler before calling this method
448472
again).
449473
"""
450-
async for timer_timestamp in self._timer:
474+
while True:
475+
await self._wait_for_next_resampling_period()
476+
451477
results = await asyncio.gather(
452478
*[r.resample(self._window_end) for r in self._resamplers.values()],
453479
return_exceptions=True,
454480
)
455-
self._update_window_end(timer_timestamp)
481+
482+
self._window_end = self._window_end + timedelta(
483+
seconds=self._config.resampling_period_s
484+
)
456485
exceptions = {
457486
source: results[i]
458487
for i, source in enumerate(self._resamplers)
@@ -465,27 +494,6 @@ async def resample(self, *, one_shot: bool = False) -> None:
465494
if one_shot:
466495
break
467496

468-
def _update_window_end(self, timer_timestamp: datetime) -> None:
469-
# We use abs() here to account for errors in the timer where the timer
470-
# fires before its time even when in theory it shouldn't be possible,
471-
# but we want to be resilient to timer implementation changes that
472-
# could end up in this case, either because there were some time jump
473-
# or some rounding error.
474-
timer_error_s = abs((timer_timestamp - self._window_end).total_seconds())
475-
if timer_error_s > (self._config.resampling_period_s / 10.0):
476-
_logger.warning(
477-
"The resampling timer fired too late. It should have fired at "
478-
"%s, but it fired at %s (%s seconds difference; resampling "
479-
"period is %s seconds)",
480-
self._window_end,
481-
timer_timestamp,
482-
timer_error_s,
483-
self._config.resampling_period_s,
484-
)
485-
self._window_end = self._window_end + timedelta(
486-
seconds=self._config.resampling_period_s
487-
)
488-
489497

490498
class _ResamplingHelper:
491499
"""Keeps track of *relevant* samples to pass them to the resampling function.

tests/timeseries/test_resampling.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,9 @@ async def test_timer_errors_are_logged(
341341
assert (
342342
"frequenz.sdk.timeseries._resampling",
343343
logging.WARNING,
344-
"The resampling timer fired too late. It should have fired at 1970-01-01 00:00:04+00:00, "
345-
"but it fired at 1970-01-01 00:00:04.399800+00:00 (0.3998 seconds difference; resampling "
344+
"The resampling task woke up too late. Resampling should have started at "
345+
"1970-01-01 00:00:04+00:00, but it started at "
346+
"1970-01-01 00:00:04.399800+00:00 (0.3998 seconds difference; resampling "
346347
"period is 2 seconds)",
347348
) in _filter_logs(caplog.record_tuples, logger_level=logging.WARNING)
348349
sink_mock.reset_mock()

0 commit comments

Comments
 (0)