1use 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 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 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}