scx_rusty/
tuner.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.
5use std::collections::BTreeMap;
6use std::sync::Arc;
7
8use ::fb_procfs as procfs;
9use anyhow::Result;
10use anyhow::anyhow;
11use anyhow::bail;
12use scx_utils::Cpumask;
13
14use crate::BpfSkel;
15use crate::DomainGroup;
16use crate::sub_or_zero;
17
18fn calc_util(curr: &procfs::CpuStat, prev: &procfs::CpuStat) -> Result<f64> {
19    match (curr, prev) {
20        (
21            procfs::CpuStat {
22                user_usec: Some(curr_user),
23                nice_usec: Some(curr_nice),
24                system_usec: Some(curr_system),
25                idle_usec: Some(curr_idle),
26                iowait_usec: Some(curr_iowait),
27                irq_usec: Some(curr_irq),
28                softirq_usec: Some(curr_softirq),
29                stolen_usec: Some(curr_stolen),
30                ..
31            },
32            procfs::CpuStat {
33                user_usec: Some(prev_user),
34                nice_usec: Some(prev_nice),
35                system_usec: Some(prev_system),
36                idle_usec: Some(prev_idle),
37                iowait_usec: Some(prev_iowait),
38                irq_usec: Some(prev_irq),
39                softirq_usec: Some(prev_softirq),
40                stolen_usec: Some(prev_stolen),
41                ..
42            },
43        ) => {
44            let idle_usec = sub_or_zero(curr_idle, prev_idle);
45            let iowait_usec = sub_or_zero(curr_iowait, prev_iowait);
46            let user_usec = sub_or_zero(curr_user, prev_user);
47            let system_usec = sub_or_zero(curr_system, prev_system);
48            let nice_usec = sub_or_zero(curr_nice, prev_nice);
49            let irq_usec = sub_or_zero(curr_irq, prev_irq);
50            let softirq_usec = sub_or_zero(curr_softirq, prev_softirq);
51            let stolen_usec = sub_or_zero(curr_stolen, prev_stolen);
52
53            let busy_usec =
54                user_usec + system_usec + nice_usec + irq_usec + softirq_usec + stolen_usec;
55            let total_usec = idle_usec + busy_usec + iowait_usec;
56            if total_usec > 0 {
57                Ok(((busy_usec as f64) / (total_usec as f64)).clamp(0.0, 1.0))
58            } else {
59                Ok(1.0)
60            }
61        }
62        _ => {
63            bail!("Missing stats in cpustat");
64        }
65    }
66}
67
68pub struct Tuner {
69    pub direct_greedy_mask: Cpumask,
70    pub kick_greedy_mask: Cpumask,
71    pub fully_utilized: bool,
72    pub slice_ns: u64,
73    underutil_slice_ns: u64,
74    overutil_slice_ns: u64,
75    dom_group: Arc<DomainGroup>,
76    direct_greedy_under: f64,
77    kick_greedy_under: f64,
78    proc_reader: procfs::ProcReader,
79    prev_cpu_stats: BTreeMap<u32, procfs::CpuStat>,
80}
81
82impl Tuner {
83    pub fn new(
84        dom_group: Arc<DomainGroup>,
85        direct_greedy_under: f64,
86        kick_greedy_under: f64,
87        underutil_slice_ns: u64,
88        overutil_slice_ns: u64,
89    ) -> Result<Self> {
90        let proc_reader = procfs::ProcReader::new();
91        let prev_cpu_stats = proc_reader
92            .read_stat()?
93            .cpus_map
94            .ok_or_else(|| anyhow!("Expected cpus_map to exist"))?;
95
96        Ok(Self {
97            direct_greedy_mask: Cpumask::new(),
98            kick_greedy_mask: Cpumask::new(),
99            fully_utilized: false,
100            direct_greedy_under: direct_greedy_under / 100.0,
101            kick_greedy_under: kick_greedy_under / 100.0,
102            proc_reader,
103            prev_cpu_stats,
104            slice_ns: underutil_slice_ns,
105            underutil_slice_ns,
106            overutil_slice_ns,
107            dom_group,
108        })
109    }
110
111    /// Apply a step in the Tuner by:
112    ///
113    /// 1. Recording CPU stats from procfs
114    /// 2. Calculating current per-domain and host-wide utilization
115    /// 3. Updating direct_greedy_under and kick_greedy_under cpumasks according
116    ///    to the observed utilization
117    pub fn step(&mut self, skel: &mut BpfSkel) -> Result<()> {
118        let curr_cpu_stats = self
119            .proc_reader
120            .read_stat()?
121            .cpus_map
122            .ok_or_else(|| anyhow!("Expected cpus_map to exist"))?;
123        let mut dom_util_sum = vec![0.0f64; self.dom_group.nr_doms()];
124
125        let mut avg_util = 0.0f64;
126        for (dom_id, dom) in self.dom_group.doms().iter() {
127            for cpu in dom.mask().iter() {
128                let cpu32 = cpu as u32;
129                if let (Some(curr), Some(prev)) =
130                    (curr_cpu_stats.get(&cpu32), self.prev_cpu_stats.get(&cpu32))
131                {
132                    let util = calc_util(curr, prev)?;
133                    dom_util_sum[*dom_id] += util;
134                    avg_util += util;
135                }
136            }
137        }
138        avg_util /= self.dom_group.weight() as f64;
139        self.fully_utilized = avg_util >= 0.99999;
140
141        self.direct_greedy_mask.clear_all();
142        self.kick_greedy_mask.clear_all();
143        for (dom_id, dom) in self.dom_group.doms().iter() {
144            // Calculate the domain avg util. If there are no active CPUs,
145            // it doesn't really matter. Go with 0.0 as that's less likely
146            // to confuse users.
147            let util = match dom.weight() {
148                0 => 0.0,
149                nr => dom_util_sum[*dom_id] / nr as f64,
150            };
151
152            let enable_direct =
153                self.direct_greedy_under > 0.99999 || util < self.direct_greedy_under;
154            let enable_kick = self.kick_greedy_under > 0.99999 || util < self.kick_greedy_under;
155
156            if enable_direct {
157                self.direct_greedy_mask |= &dom.mask();
158            }
159            if enable_kick {
160                self.kick_greedy_mask |= &dom.mask();
161            }
162        }
163
164        let ti = &mut skel.maps.bss_data.tune_input;
165        let write_to_bpf = |target: &mut [u64; 8], mask: &Cpumask| {
166            let raw_slice = mask.as_raw_slice();
167            let (left, _) = target.split_at_mut(raw_slice.len());
168            left.clone_from_slice(raw_slice);
169        };
170
171        write_to_bpf(&mut ti.direct_greedy_cpumask, &self.direct_greedy_mask);
172        write_to_bpf(&mut ti.kick_greedy_cpumask, &self.kick_greedy_mask);
173        if self.fully_utilized {
174            self.slice_ns = self.overutil_slice_ns;
175        } else {
176            self.slice_ns = self.underutil_slice_ns;
177        }
178        ti.slice_ns = self.slice_ns;
179
180        ti.genn += 1;
181
182        self.prev_cpu_stats = curr_cpu_stats;
183
184        Ok(())
185    }
186}