1use std::collections::BTreeMap;
6use std::sync::Arc;
7
8use ::fb_procfs as procfs;
9use anyhow::anyhow;
10use anyhow::bail;
11use anyhow::Result;
12use scx_utils::Cpumask;
13
14use crate::sub_or_zero;
15use crate::update_bpf_mask;
16use crate::BpfSkel;
17use crate::DomainGroup;
18
19fn calc_util(curr: &procfs::CpuStat, prev: &procfs::CpuStat) -> Result<f64> {
20 match (curr, prev) {
21 (
22 procfs::CpuStat {
23 user_usec: Some(curr_user),
24 nice_usec: Some(curr_nice),
25 system_usec: Some(curr_system),
26 idle_usec: Some(curr_idle),
27 iowait_usec: Some(curr_iowait),
28 irq_usec: Some(curr_irq),
29 softirq_usec: Some(curr_softirq),
30 stolen_usec: Some(curr_stolen),
31 ..
32 },
33 procfs::CpuStat {
34 user_usec: Some(prev_user),
35 nice_usec: Some(prev_nice),
36 system_usec: Some(prev_system),
37 idle_usec: Some(prev_idle),
38 iowait_usec: Some(prev_iowait),
39 irq_usec: Some(prev_irq),
40 softirq_usec: Some(prev_softirq),
41 stolen_usec: Some(prev_stolen),
42 ..
43 },
44 ) => {
45 let idle_usec = sub_or_zero(curr_idle, prev_idle);
46 let iowait_usec = sub_or_zero(curr_iowait, prev_iowait);
47 let user_usec = sub_or_zero(curr_user, prev_user);
48 let system_usec = sub_or_zero(curr_system, prev_system);
49 let nice_usec = sub_or_zero(curr_nice, prev_nice);
50 let irq_usec = sub_or_zero(curr_irq, prev_irq);
51 let softirq_usec = sub_or_zero(curr_softirq, prev_softirq);
52 let stolen_usec = sub_or_zero(curr_stolen, prev_stolen);
53
54 let busy_usec =
55 user_usec + system_usec + nice_usec + irq_usec + softirq_usec + stolen_usec;
56 let total_usec = idle_usec + busy_usec + iowait_usec;
57 if total_usec > 0 {
58 Ok(((busy_usec as f64) / (total_usec as f64)).clamp(0.0, 1.0))
59 } else {
60 Ok(1.0)
61 }
62 }
63 _ => {
64 bail!("Missing stats in cpustat");
65 }
66 }
67}
68
69pub struct Tuner {
70 pub direct_greedy_mask: Cpumask,
71 pub kick_greedy_mask: Cpumask,
72 pub fully_utilized: bool,
73 pub slice_ns: u64,
74 underutil_slice_ns: u64,
75 overutil_slice_ns: u64,
76 dom_group: Arc<DomainGroup>,
77 direct_greedy_under: f64,
78 kick_greedy_under: f64,
79 proc_reader: procfs::ProcReader,
80 prev_cpu_stats: BTreeMap<u32, procfs::CpuStat>,
81}
82
83impl Tuner {
84 pub fn new(
85 dom_group: Arc<DomainGroup>,
86 direct_greedy_under: f64,
87 kick_greedy_under: f64,
88 underutil_slice_ns: u64,
89 overutil_slice_ns: u64,
90 ) -> Result<Self> {
91 let proc_reader = procfs::ProcReader::new();
92 let prev_cpu_stats = proc_reader
93 .read_stat()?
94 .cpus_map
95 .ok_or_else(|| anyhow!("Expected cpus_map to exist"))?;
96
97 Ok(Self {
98 direct_greedy_mask: Cpumask::new(),
99 kick_greedy_mask: Cpumask::new(),
100 fully_utilized: false,
101 direct_greedy_under: direct_greedy_under / 100.0,
102 kick_greedy_under: kick_greedy_under / 100.0,
103 proc_reader,
104 prev_cpu_stats,
105 slice_ns: underutil_slice_ns,
106 underutil_slice_ns,
107 overutil_slice_ns,
108 dom_group,
109 })
110 }
111
112 pub fn step(&mut self, skel: &mut BpfSkel) -> Result<()> {
119 let curr_cpu_stats = self
120 .proc_reader
121 .read_stat()?
122 .cpus_map
123 .ok_or_else(|| anyhow!("Expected cpus_map to exist"))?;
124 let mut dom_util_sum = vec![0.0f64; self.dom_group.nr_doms()];
125
126 let mut avg_util = 0.0f64;
127 for (dom_id, dom) in self.dom_group.doms().iter() {
128 for cpu in dom.mask().iter() {
129 let cpu32 = cpu as u32;
130 if let (Some(curr), Some(prev)) =
131 (curr_cpu_stats.get(&cpu32), self.prev_cpu_stats.get(&cpu32))
132 {
133 let util = calc_util(curr, prev)?;
134 dom_util_sum[*dom_id] += util;
135 avg_util += util;
136 }
137 }
138 }
139 avg_util /= self.dom_group.weight() as f64;
140 self.fully_utilized = avg_util >= 0.99999;
141
142 self.direct_greedy_mask.clear_all();
143 self.kick_greedy_mask.clear_all();
144
145 for (dom_id, dom) in self.dom_group.doms().iter() {
146 let util = match dom.weight() {
150 0 => 0.0,
151 nr => dom_util_sum[*dom_id] / nr as f64,
152 };
153
154 let enable_direct =
155 self.direct_greedy_under > 0.99999 || util < self.direct_greedy_under;
156 let enable_kick = self.kick_greedy_under > 0.99999 || util < self.kick_greedy_under;
157
158 if enable_direct {
159 self.direct_greedy_mask |= &dom.mask();
160
161 let domc = dom.ctx().unwrap();
162
163 update_bpf_mask(domc.direct_greedy_cpumask, &dom.mask())?;
164 }
165
166 if enable_kick {
167 self.kick_greedy_mask |= &dom.mask();
168 }
169 }
170
171 let bss_data = skel.maps.bss_data.as_mut().unwrap();
172 update_bpf_mask(bss_data.direct_greedy_cpumask, &self.direct_greedy_mask)?;
173 update_bpf_mask(bss_data.kick_greedy_cpumask, &self.kick_greedy_mask)?;
174
175 if self.fully_utilized {
176 self.slice_ns = self.overutil_slice_ns;
177 } else {
178 self.slice_ns = self.underutil_slice_ns;
179 }
180
181 bss_data.slice_ns = self.slice_ns;
182
183 self.prev_cpu_stats = curr_cpu_stats;
188
189 Ok(())
190 }
191}