scx_utils/
ravg.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This software may be used and distributed according to the terms of the
4// GNU General Public License version 2.
5
6//! # Running Average Utilities
7//!
8//! Rust userland utilities to access running averages tracked by BPF
9//! ravg_data. See
10//! [ravg.bpf.h](https://github.com/sched-ext/scx/blob/main/scheds/include/common/ravg.bpf.h)
11//! and
12//! [ravg_impl.bpf.h](https://github.com/sched-ext/scx/blob/main/scheds/include/common/ravg_impl.bpf.h)
13//! for details.
14
15/// Read the current running average
16///
17/// Read the running average value at `@now` of ravg_data (`@val`,
18/// `@val_at`, `@old`, `@cur`) given `@half_life` and `@frac_bits`. This is
19/// equivalent to C `ravg_read()`.
20///
21/// There currently is no way to make `bindgen` and `libbpf_cargo` generated
22/// code to use a pre-existing type, so this function takes each field of
23/// struct ravg_data as a separate argument. This works but is ugly and
24/// error-prone. Something to improve in the future.
25pub fn ravg_read(
26    val: u64,
27    val_at: u64,
28    old: u64,
29    cur: u64,
30    now: u64,
31    half_life: u32,
32    frac_bits: u32,
33) -> f64 {
34    let ravg_1: f64 = (1 << frac_bits) as f64;
35    let half_life = half_life as u64;
36    let val = val as f64;
37    let mut old = old as f64 / ravg_1;
38    let mut cur = cur as f64 / ravg_1;
39
40    let now = now.max(val_at);
41    let normalized_dur = |dur| dur as f64 / half_life as f64;
42
43    //
44    // The following is f64 implementation of BPF ravg_accumulate().
45    //
46    let cur_seq = (now / half_life) as i64;
47    let val_seq = (val_at / half_life) as i64;
48    let seq_delta = (cur_seq - val_seq) as i32;
49
50    if seq_delta > 0 {
51        let full_decay = 2f64.powi(seq_delta);
52
53        // Decay $old and fold $cur into it.
54        old /= full_decay;
55        old += cur / full_decay;
56        cur = 0.0;
57
58        // Fold the oldest period whicy may be partial.
59        old += val * normalized_dur(half_life - val_at % half_life) / full_decay;
60
61        // Pre-computed decayed full-period values.
62        const FULL_SUMS: [f64; 20] = [
63            0.5,
64            0.75,
65            0.875,
66            0.9375,
67            0.96875,
68            0.984375,
69            0.9921875,
70            0.99609375,
71            0.998046875,
72            0.9990234375,
73            0.99951171875,
74            0.999755859375,
75            0.9998779296875,
76            0.99993896484375,
77            0.999969482421875,
78            0.9999847412109375,
79            0.9999923706054688,
80            0.9999961853027344,
81            0.9999980926513672,
82            0.9999990463256836,
83            // Use the same value beyond this point.
84        ];
85
86        // Fold the full periods in the middle.
87        if seq_delta >= 2 {
88            let idx = ((seq_delta - 2) as usize).min(FULL_SUMS.len() - 1);
89            old += val * FULL_SUMS[idx];
90        }
91
92        // Accumulate the current period duration into @cur.
93        cur += val * normalized_dur(now % half_life);
94    } else {
95        cur += val * normalized_dur(now - val_at);
96    }
97
98    //
99    // The following is the blending part of BPF ravg_read().
100    //
101    old * (1.0 - normalized_dur(now % half_life) / 2.0) + cur / 2.0
102}