Skip to content

Commit 153221f

Browse files
authored
Allow configuring resampler's max_age_in_intervals in LogicalMeter (#30)
This PR makes the logical meter’s resampler “max sample age” window configurable via `LogicalMeterConfig`, replacing the previously hard-coded value and adding coverage + release notes for the new configuration knob. **Changes:** - Add `max_age_in_intervals` to `LogicalMeterConfig` (defaulting to 3) plus a builder method `with_max_age_in_intervals`. - Wire the configured value into `LogicalMeterActor` when constructing `frequenz_resampling::Resampler`. - Add a new async test for `max_age_in_intervals` behavior and update release notes.
2 parents 65c5e4a + f03f841 commit 153221f

4 files changed

Lines changed: 77 additions & 1 deletion

File tree

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
- It is now possible to change the default resampling function, and to override the resampling function for specific metrics.
1414

15+
- The resampler's `max_age_in_intervals` has also become configurable, through `LogicalMeterConfig`.
16+
1517
## Bug Fixes
1618

1719
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->

src/logical_meter/config.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ pub struct LogicalMeterConfig {
1616
pub(crate) resampling_function: Option<ResamplingFunction<f32, Sample<f32>>>,
1717
/// Resampler overrides.
1818
pub(crate) resampling_overrides: HashMap<Metric, ResamplingFunction<f32, Sample<f32>>>,
19+
/// The maximum age of samples to be considered for resampling, in number of
20+
/// intervals.
21+
pub(crate) max_age_in_intervals: u32,
1922
}
2023

2124
impl LogicalMeterConfig {
@@ -25,6 +28,7 @@ impl LogicalMeterConfig {
2528
resampling_interval,
2629
resampling_function: None,
2730
resampling_overrides: HashMap::new(),
31+
max_age_in_intervals: 3,
2832
}
2933
}
3034

@@ -55,4 +59,17 @@ impl LogicalMeterConfig {
5559

5660
self
5761
}
62+
63+
/// Sets the maximum age of samples to be considered for resampling, in
64+
/// number of intervals.
65+
///
66+
/// Must be at least 1. If a smaller value is provided, it will be clamped
67+
/// to 1.
68+
///
69+
/// If not set, the default value is 3.
70+
pub fn with_max_age_in_intervals(mut self, max_age_in_intervals: u32) -> Self {
71+
// Ensure that the maximum age is at least 1 interval.
72+
self.max_age_in_intervals = max_age_in_intervals.max(1);
73+
self
74+
}
5875
}

src/logical_meter/logical_meter_actor.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,9 @@ impl LogicalMeterActor {
383383
// Finally, default to average if no default is
384384
// configured
385385
.unwrap_or(ResamplingFunction::Average),
386-
3,
386+
// The resampler expects max age to be i32, so we need to
387+
// cap it if the user provided a higher value.
388+
self.config.max_age_in_intervals.min(i32::MAX as u32) as i32,
387389
self.resampler_ts,
388390
false,
389391
),

src/logical_meter/logical_meter_handle.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,61 @@ mod tests {
473473
);
474474
}
475475

476+
#[tokio::test(start_paused = true)]
477+
async fn test_max_age_in_intervals() {
478+
let lm_config = Some(
479+
LogicalMeterConfig::new(TimeDelta::try_milliseconds(200).unwrap())
480+
.with_max_age_in_intervals(1)
481+
.with_default_resampling_function(ResamplingFunction::Count),
482+
);
483+
let mut lm = new_logical_meter_handle(lm_config).await;
484+
let formula = lm.consumer(crate::metric::AcPowerActive).unwrap();
485+
486+
let samples = fetch_samples(formula, 8).await;
487+
check_samples(
488+
samples,
489+
|q| q.as_watts(),
490+
TimeDelta::try_milliseconds(200).unwrap(),
491+
vec![
492+
Some(1.0),
493+
Some(1.0),
494+
Some(1.0),
495+
Some(1.0),
496+
Some(1.0),
497+
Some(1.0),
498+
Some(0.0),
499+
Some(0.0),
500+
],
501+
);
502+
503+
let lm_config = Some(
504+
LogicalMeterConfig::new(TimeDelta::try_milliseconds(200).unwrap())
505+
.with_max_age_in_intervals(3)
506+
.with_default_resampling_function(ResamplingFunction::Count),
507+
);
508+
let mut lm = new_logical_meter_handle(lm_config).await;
509+
let formula = lm.consumer(crate::metric::AcPowerActive).unwrap();
510+
511+
let samples = fetch_samples(formula, 10).await;
512+
check_samples(
513+
samples,
514+
|q| q.as_watts(),
515+
TimeDelta::try_milliseconds(200).unwrap(),
516+
vec![
517+
Some(1.0),
518+
Some(2.0),
519+
Some(3.0),
520+
Some(3.0),
521+
Some(3.0),
522+
Some(3.0),
523+
Some(2.0),
524+
Some(1.0),
525+
Some(0.0),
526+
Some(0.0),
527+
],
528+
)
529+
}
530+
476531
#[tokio::test(start_paused = true)]
477532
async fn test_consumer_current_formula() {
478533
let formula = new_logical_meter_handle(None)

0 commit comments

Comments
 (0)