7676
7777A resampling function produces a new sample based on a list of pre-existing
7878samples. It can do "upsampling" when there data rate of the `input_samples`
79- period is smaller than the `resampling_period_s `, or "downsampling" if it is
79+ period is smaller than the `resampling_period `, or "downsampling" if it is
8080bigger.
8181
82- In general a resampling window is the same as the `resampling_period_s `, and
82+ In general a resampling window is the same as the `resampling_period `, and
8383this function might receive input samples from multiple windows in the past to
8484enable extrapolation, but no samples from the future (so the timestamp of the
8585new sample that is going to be produced will always be bigger than the biggest
@@ -123,31 +123,31 @@ def average(
123123class ResamplerConfig :
124124 """Resampler configuration."""
125125
126- resampling_period_s : float
127- """The resampling period in seconds .
126+ resampling_period : timedelta
127+ """The resampling period.
128128
129129 This is the time it passes between resampled data should be calculated.
130130
131- It must be a positive number .
131+ It must be a positive time span .
132132 """
133133
134134 max_data_age_in_periods : float = 3.0
135135 """The maximum age a sample can have to be considered *relevant* for resampling.
136136
137- Expressed in number of periods, where period is the `resampling_period_s `
137+ Expressed in number of periods, where period is the `resampling_period `
138138 if we are downsampling (resampling period bigger than the input period) or
139139 the input sampling period if we are upsampling (input period bigger than
140140 the resampling period).
141141
142142 It must be bigger than 1.0.
143143
144144 Example:
145- If `resampling_period_s ` is 3, the input sampling period is
145+ If `resampling_period ` is 3 seconds , the input sampling period is
146146 1 and `max_data_age_in_periods` is 2, then data older than 3*2
147147 = 6 seconds will be discarded when creating a new sample and never
148148 passed to the resampling function.
149149
150- If `resampling_period_s ` is 3, the input sampling period is
150+ If `resampling_period ` is 3 seconds , the input sampling period is
151151 5 and `max_data_age_in_periods` is 2, then data older than 5*2
152152 = 10 seconds will be discarded when creating a new sample and never
153153 passed to the resampling function.
@@ -197,9 +197,9 @@ def __post_init__(self) -> None:
197197 Raises:
198198 ValueError: If any value is out of range.
199199 """
200- if self .resampling_period_s < 0.0 :
200+ if self .resampling_period . total_seconds () < 0.0 :
201201 raise ValueError (
202- f"resampling_period_s ({ self .resampling_period_s } ) must be positive"
202+ f"resampling_period ({ self .resampling_period } ) must be positive"
203203 )
204204 if self .max_data_age_in_periods < 1.0 :
205205 raise ValueError (
@@ -348,8 +348,8 @@ def __init__(self, config: ResamplerConfig) -> None:
348348 self ._resamplers : dict [Source , _StreamingHelper ] = {}
349349 """A mapping between sources and the streaming helper handling that source."""
350350
351- self ._window_end : datetime = datetime . now ( timezone . utc ) + timedelta (
352- seconds = self ._config .resampling_period_s
351+ self ._window_end : datetime = (
352+ datetime . now ( timezone . utc ) + self ._config .resampling_period
353353 )
354354 """The time in which the current window ends.
355355
@@ -449,9 +449,7 @@ async def resample(self, *, one_shot: bool = False) -> None:
449449 return_exceptions = True ,
450450 )
451451
452- self ._window_end = self ._window_end + timedelta (
453- seconds = self ._config .resampling_period_s
454- )
452+ self ._window_end = self ._window_end + self ._config .resampling_period
455453 exceptions = {
456454 source : results [i ]
457455 for i , source in enumerate (self ._resamplers )
@@ -476,25 +474,30 @@ async def _wait_for_next_resampling_period(self) -> None:
476474 sleep_for = self ._window_end - now
477475 await asyncio .sleep (sleep_for .total_seconds ())
478476
479- timer_error_s = (now - self ._window_end ).total_seconds ()
480- if timer_error_s > (self ._config .resampling_period_s / 10.0 ):
477+ timer_error = now - self ._window_end
478+ # We use a tolerance of 10% of the resampling period
479+ tolerance = timedelta (
480+ seconds = self ._config .resampling_period .total_seconds () / 10.0
481+ )
482+ if timer_error > tolerance :
481483 _logger .warning (
482- "The resampling task woke up too late. Resampling should have started "
483- "at %s, but it started at %s (%s seconds difference; resampling "
484- "period is %s seconds )" ,
484+ "The resampling task woke up too late. Resampling should have "
485+ "started at %s, but it started at %s (tolerance: %s, "
486+ "difference: %s; resampling period: %s )" ,
485487 self ._window_end ,
486488 now ,
487- timer_error_s ,
488- self ._config .resampling_period_s ,
489+ tolerance ,
490+ timer_error ,
491+ self ._config .resampling_period ,
489492 )
490493
491494
492495class _ResamplingHelper :
493496 """Keeps track of *relevant* samples to pass them to the resampling function.
494497
495498 Samples are stored in an internal ring buffer. All collected samples that
496- are newer than `max(resampling_period_s , input_period_s)
497- * max_data_age_in_periods` seconds are considered *relevant* and are passed
499+ are newer than `max(resampling_period , input_period_s)
500+ * max_data_age_in_periods` are considered *relevant* and are passed
498501 to the provided `resampling_function` when calling the `resample()` method.
499502 All older samples are discarded.
500503 """
@@ -552,7 +555,7 @@ def _update_source_sample_period(self, now: datetime) -> bool:
552555 props .sampling_period_s is not None
553556 or props .sampling_start is None
554557 or props .received_samples
555- < config .resampling_period_s * config .max_data_age_in_periods
558+ < config .resampling_period . total_seconds () * config .max_data_age_in_periods
556559 or len (self ._buffer ) < self ._buffer .maxlen
557560 # There might be a race between the first sample being received and
558561 # this function being called
@@ -589,14 +592,14 @@ def _update_buffer_len(self) -> bool:
589592 # If we are upsampling, one sample could be enough for back-filling, but
590593 # we store max_data_age_in_periods for input periods, so resampling
591594 # functions can do more complex inter/extrapolation if they need to.
592- if input_sampling_period_s > config .resampling_period_s :
595+ if input_sampling_period_s > config .resampling_period . total_seconds () :
593596 new_buffer_len = input_sampling_period_s * config .max_data_age_in_periods
594597 # If we are upsampling, we want a buffer that can hold
595- # max_data_age_in_periods * resampling_period_s seconds of data, and we
598+ # max_data_age_in_periods * resampling_period of data, and we
596599 # one sample every input_sampling_period_s.
597600 else :
598601 new_buffer_len = (
599- config .resampling_period_s
602+ config .resampling_period . total_seconds ()
600603 / input_sampling_period_s
601604 * config .max_data_age_in_periods
602605 )
@@ -652,9 +655,9 @@ def resample(self, timestamp: datetime) -> Sample:
652655 # To see which samples are relevant we need to consider if we are down
653656 # or upsampling.
654657 period = (
655- max (conf .resampling_period_s , props .sampling_period_s )
658+ max (conf .resampling_period . total_seconds () , props .sampling_period_s )
656659 if props .sampling_period_s is not None
657- else conf .resampling_period_s
660+ else conf .resampling_period . total_seconds ()
658661 )
659662 minimum_relevant_timestamp = timestamp - timedelta (
660663 seconds = period * conf .max_data_age_in_periods
0 commit comments