mysten_common/decay_moving_average.rs
1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/// A moving average that decays over time so that the average value
5/// skews towards the newer values over time.
6///
7/// The decay factor is a value between 0 and 1 that determines how much of the previous value
8/// is kept when updating the average.
9///
10/// A decay factor of 0 means that the average is completely replaced with the new value every time,
11/// and a decay factor of 1 means that the average never changes (keeps the old value).
12///
13/// ## Choosing Decay Factors for Different Behaviors
14///
15/// **Lower decay factor (closer to 0.0):**
16/// - Adapts quickly to new values, making the average more responsive to recent changes
17/// - Outliers are "forgotten" faster, providing better tolerance to temporary spikes or anomalies
18/// - Useful for tracking recent trends where you want to react quickly to changes
19/// - Example: `0.1` - heavily weights recent values, good for responsive latency tracking
20///
21/// **Higher decay factor (closer to 1.0):**
22/// - Changes slowly and retains more historical information
23/// - Outliers have a longer-lasting impact on the average, making them more visible
24/// - Provides more stable tracking that's less sensitive to temporary fluctuations
25/// - Example: `0.9` - heavily weights historical values, good for stable baseline tracking
26///
27/// When using this to track moving average of latency, it is important that
28/// there should be a cap on the maximum value that can be stored.
29#[derive(Debug, Clone)]
30pub struct DecayMovingAverage {
31 value: f64,
32 decay_factor: f64,
33}
34
35impl DecayMovingAverage {
36 /// Create a new DecayMovingAverage with an initial value and decay factor.
37 ///
38 /// # Arguments
39 /// * `init_value` - The initial value for the moving average
40 /// * `decay_factor` - A value between 0.0 and 1.0 that controls how much historical data is retained.
41 /// Lower values (e.g., 0.1) make the average more responsive to recent values and better at
42 /// forgetting outliers. Higher values (e.g., 0.9) make the average more stable but outliers
43 /// will have a longer-lasting impact.
44 pub fn new(init_value: f64, decay_factor: f64) -> Self {
45 assert!(
46 decay_factor > 0.0 && decay_factor < 1.0,
47 "Decay factor must be between 0 and 1"
48 );
49 Self {
50 value: init_value,
51 decay_factor,
52 }
53 }
54
55 /// Update the moving average with a new value.
56 ///
57 /// The new value is weighted by (1 - decay_factor), and the previous value
58 /// is weighted by decay_factor, so that the average value skews towards
59 /// the newer values over time.
60 pub fn update_moving_average(&mut self, value: f64) {
61 self.value = self.value * self.decay_factor + value * (1.0 - self.decay_factor);
62 }
63
64 /// Override the moving average with a new value, bypassing the decay calculation.
65 ///
66 /// Unlike `update_moving_average()`, this method immediately sets the average to the new value
67 /// rather than blending it with the previous value using the decay factor.
68 ///
69 /// This is particularly useful for implementing patterns like "decay moving max":
70 /// - Track the maximum value seen recently, but let it decay over time if no new maxima occur
71 /// - When a new maximum is encountered, immediately jump to that value using `override_moving_average()`
72 /// - For regular updates below the maximum, use `update_moving_average()` to let the value decay naturally
73 ///
74 /// # Example: Decay Moving Max
75 /// ```
76 /// fn update_moving_max(new_value: f64) {
77 /// use mysten_common::decay_moving_average::DecayMovingAverage;
78 /// let mut decay_max = DecayMovingAverage::new(0.0, 0.9);
79 ///
80 /// // New maximum encountered - immediately jump to it
81 /// if new_value > decay_max.get() {
82 /// decay_max.override_moving_average(new_value);
83 /// } else {
84 /// // Let the maximum decay naturally toward the current value
85 /// decay_max.update_moving_average(new_value);
86 /// }
87 /// }
88 /// ```
89 pub fn override_moving_average(&mut self, value: f64) {
90 self.value = value;
91 }
92
93 /// Get the current value of the moving average.
94 pub fn get(&self) -> f64 {
95 self.value
96 }
97}