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}