Skip to content

Commit 9ba4824

Browse files
committed
Make source properties' sampling period a timedelta
The `SourceProperties` of the resampler now uses a `timedelta` for the input sampling period. The attribute was renamed from `sampling_period_s` to `sampling_period` accordingly. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent c67a6aa commit 9ba4824

File tree

3 files changed

+48
-36
lines changed

3 files changed

+48
-36
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
to better distinguish it from the sampling period parameter in the resampler.
2020
* The serialization feature for the ringbuffer was made more flexible. The `dump` and `load` methods can now work directly with a ringbuffer instance.
2121
* The `ResamplerConfig` now takes the resampling period as a `timedelta`. The configuration was renamed from `resampling_period_s` to `resampling_period` accordingly.
22+
* The `SourceProperties` of the resampler now uses a `timedelta` for the input sampling period. The attribute was renamed from `sampling_period_s` to `sampling_period` accordingly.
2223

2324
## New Features
2425

src/frequenz/sdk/timeseries/_resampling.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,11 @@ class SourceProperties:
311311
received_samples: int = 0
312312
"""Total samples received by this source so far."""
313313

314-
sampling_period_s: Optional[float] = None
315-
"""The sampling period of this source (in seconds).
314+
sampling_period: Optional[timedelta] = None
315+
"""The sampling period of this source.
316316
317-
This is we receive (on average) one sample for this source every
318-
`sampling_period_s` seconds.
317+
This means we receive (on average) one sample for this source every
318+
`sampling_period` time.
319319
320320
`None` means it is unknown.
321321
"""
@@ -496,7 +496,7 @@ class _ResamplingHelper:
496496
"""Keeps track of *relevant* samples to pass them to the resampling function.
497497
498498
Samples are stored in an internal ring buffer. All collected samples that
499-
are newer than `max(resampling_period, input_period_s)
499+
are newer than `max(resampling_period, input_period)
500500
* max_data_age_in_periods` are considered *relevant* and are passed
501501
to the provided `resampling_function` when calling the `resample()` method.
502502
All older samples are discarded.
@@ -552,7 +552,7 @@ def _update_source_sample_period(self, now: datetime) -> bool:
552552

553553
# We only update it if we didn't before and we have enough data
554554
if (
555-
props.sampling_period_s is not None
555+
props.sampling_period is not None
556556
or props.sampling_start is None
557557
or props.received_samples
558558
< config.resampling_period.total_seconds() * config.max_data_age_in_periods
@@ -564,14 +564,14 @@ def _update_source_sample_period(self, now: datetime) -> bool:
564564
return False
565565

566566
samples_time_delta = now - props.sampling_start
567-
props.sampling_period_s = (
568-
samples_time_delta.total_seconds()
569-
) / props.received_samples
567+
props.sampling_period = timedelta(
568+
seconds=samples_time_delta.total_seconds() / props.received_samples
569+
)
570570

571571
_logger.debug(
572572
"New input sampling period calculated for %r: %ss",
573573
self._name,
574-
props.sampling_period_s,
574+
props.sampling_period,
575575
)
576576
return True
577577

@@ -581,26 +581,28 @@ def _update_buffer_len(self) -> bool:
581581
Returns:
582582
Whether the buffer length was changed (was really updated).
583583
"""
584-
input_sampling_period_s = self._source_properties.sampling_period_s
585-
586584
# To make type checking happy
587-
assert input_sampling_period_s is not None
588585
assert self._buffer.maxlen is not None
586+
assert self._source_properties.sampling_period is not None
587+
588+
input_sampling_period = self._source_properties.sampling_period
589589

590590
config = self._config
591591

592592
# If we are upsampling, one sample could be enough for back-filling, but
593593
# we store max_data_age_in_periods for input periods, so resampling
594594
# functions can do more complex inter/extrapolation if they need to.
595-
if input_sampling_period_s > config.resampling_period.total_seconds():
596-
new_buffer_len = input_sampling_period_s * config.max_data_age_in_periods
595+
if input_sampling_period > config.resampling_period:
596+
new_buffer_len = (
597+
input_sampling_period.total_seconds() * config.max_data_age_in_periods
598+
)
597599
# If we are upsampling, we want a buffer that can hold
598600
# max_data_age_in_periods * resampling_period of data, and we
599-
# one sample every input_sampling_period_s.
601+
# one sample every input_sampling_period.
600602
else:
601603
new_buffer_len = (
602604
config.resampling_period.total_seconds()
603-
/ input_sampling_period_s
605+
/ input_sampling_period.total_seconds()
604606
* config.max_data_age_in_periods
605607
)
606608

@@ -655,13 +657,14 @@ def resample(self, timestamp: datetime) -> Sample:
655657
# To see which samples are relevant we need to consider if we are down
656658
# or upsampling.
657659
period = (
658-
max(conf.resampling_period.total_seconds(), props.sampling_period_s)
659-
if props.sampling_period_s is not None
660-
else conf.resampling_period.total_seconds()
661-
)
662-
minimum_relevant_timestamp = timestamp - timedelta(
663-
seconds=period * conf.max_data_age_in_periods
660+
max(
661+
conf.resampling_period,
662+
props.sampling_period,
663+
)
664+
if props.sampling_period is not None
665+
else conf.resampling_period
664666
)
667+
minimum_relevant_timestamp = timestamp - period * conf.max_data_age_in_periods
665668

666669
# We need to pass a dummy Sample to bisect because it only support
667670
# specifying a key extraction function in Python 3.10, so we need to

tests/timeseries/test_resampling.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ async def test_future_samples_not_included(
407407
a_sequence(sample0s, sample1s), config, source_props # sample2_1s is not here
408408
)
409409
assert source_props == SourceProperties(
410-
sampling_start=timestamp, received_samples=3, sampling_period_s=None
410+
sampling_start=timestamp, received_samples=3, sampling_period=None
411411
)
412412
assert _get_buffer_len(resampler, source_receiver) == config.initial_buffer_len
413413
sink_mock.reset_mock()
@@ -489,7 +489,7 @@ async def test_resampling_with_one_window(
489489
a_sequence(sample1s), config, source_props
490490
)
491491
assert source_props == SourceProperties(
492-
sampling_start=timestamp, received_samples=2, sampling_period_s=None
492+
sampling_start=timestamp, received_samples=2, sampling_period=None
493493
)
494494
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
495495
sink_mock.reset_mock()
@@ -518,7 +518,9 @@ async def test_resampling_with_one_window(
518518
# By now we have a full buffer (5 samples and a buffer of length 4), which
519519
# we received in 4 seconds, so we have an input period of 0.8s.
520520
assert source_props == SourceProperties(
521-
sampling_start=timestamp, received_samples=5, sampling_period_s=0.8
521+
sampling_start=timestamp,
522+
received_samples=5,
523+
sampling_period=timedelta(seconds=0.8),
522524
)
523525
# The buffer should be able to hold 2 seconds of data, and data is coming
524526
# every 0.8 seconds, so we should be able to store 3 samples.
@@ -536,7 +538,9 @@ async def test_resampling_with_one_window(
536538
current_iteration=3,
537539
)
538540
assert source_props == SourceProperties(
539-
sampling_start=timestamp, received_samples=5, sampling_period_s=0.8
541+
sampling_start=timestamp,
542+
received_samples=5,
543+
sampling_period=timedelta(seconds=0.8),
540544
)
541545
assert _get_buffer_len(resampler, source_recvr) == 3
542546

@@ -598,7 +602,7 @@ async def test_resampling_with_one_and_a_half_windows( # pylint: disable=too-ma
598602
a_sequence(sample0s, sample1s), config, source_props
599603
)
600604
assert source_props == SourceProperties(
601-
sampling_start=timestamp, received_samples=2, sampling_period_s=None
605+
sampling_start=timestamp, received_samples=2, sampling_period=None
602606
)
603607
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
604608
sink_mock.reset_mock()
@@ -626,7 +630,7 @@ async def test_resampling_with_one_and_a_half_windows( # pylint: disable=too-ma
626630
a_sequence(sample2_5s, sample3s, sample4s), config, source_props
627631
)
628632
assert source_props == SourceProperties(
629-
sampling_start=timestamp, received_samples=5, sampling_period_s=None
633+
sampling_start=timestamp, received_samples=5, sampling_period=None
630634
)
631635
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
632636
sink_mock.reset_mock()
@@ -654,7 +658,9 @@ async def test_resampling_with_one_and_a_half_windows( # pylint: disable=too-ma
654658
# By now we have a full buffer (7 samples and a buffer of length 6), which
655659
# we received in 4 seconds, so we have an input period of 6/7s.
656660
assert source_props == SourceProperties(
657-
sampling_start=timestamp, received_samples=7, sampling_period_s=6 / 7
661+
sampling_start=timestamp,
662+
received_samples=7,
663+
sampling_period=timedelta(seconds=6 / 7),
658664
)
659665
# The buffer should be able to hold 2 * 1.5 (3) seconds of data, and data
660666
# is coming every 6/7 seconds (~0.857s), so we should be able to store
@@ -693,7 +699,9 @@ async def test_resampling_with_one_and_a_half_windows( # pylint: disable=too-ma
693699
current_iteration=5,
694700
)
695701
assert source_props == SourceProperties(
696-
sampling_start=timestamp, received_samples=7, sampling_period_s=6 / 7
702+
sampling_start=timestamp,
703+
received_samples=7,
704+
sampling_period=timedelta(seconds=6 / 7),
697705
)
698706
assert _get_buffer_len(resampler, source_recvr) == 4
699707

@@ -755,7 +763,7 @@ async def test_resampling_with_two_windows( # pylint: disable=too-many-statemen
755763
a_sequence(sample0s, sample1s), config, source_props
756764
)
757765
assert source_props == SourceProperties(
758-
sampling_start=timestamp, received_samples=2, sampling_period_s=None
766+
sampling_start=timestamp, received_samples=2, sampling_period=None
759767
)
760768
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
761769
sink_mock.reset_mock()
@@ -783,7 +791,7 @@ async def test_resampling_with_two_windows( # pylint: disable=too-many-statemen
783791
a_sequence(sample1s, sample2_5s, sample3s, sample4s), config, source_props
784792
)
785793
assert source_props == SourceProperties(
786-
sampling_start=timestamp, received_samples=5, sampling_period_s=None
794+
sampling_start=timestamp, received_samples=5, sampling_period=None
787795
)
788796
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
789797
sink_mock.reset_mock()
@@ -811,7 +819,7 @@ async def test_resampling_with_two_windows( # pylint: disable=too-many-statemen
811819
source_props,
812820
)
813821
assert source_props == SourceProperties(
814-
sampling_start=timestamp, received_samples=7, sampling_period_s=None
822+
sampling_start=timestamp, received_samples=7, sampling_period=None
815823
)
816824
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
817825
sink_mock.reset_mock()
@@ -833,7 +841,7 @@ async def test_resampling_with_two_windows( # pylint: disable=too-many-statemen
833841
a_sequence(sample5s, sample6s), config, source_props
834842
)
835843
assert source_props == SourceProperties(
836-
sampling_start=timestamp, received_samples=7, sampling_period_s=None
844+
sampling_start=timestamp, received_samples=7, sampling_period=None
837845
)
838846
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
839847
sink_mock.reset_mock()
@@ -849,7 +857,7 @@ async def test_resampling_with_two_windows( # pylint: disable=too-many-statemen
849857
current_iteration=5,
850858
)
851859
assert source_props == SourceProperties(
852-
sampling_start=timestamp, received_samples=7, sampling_period_s=None
860+
sampling_start=timestamp, received_samples=7, sampling_period=None
853861
)
854862
assert _get_buffer_len(resampler, source_recvr) == config.initial_buffer_len
855863

0 commit comments

Comments
 (0)