1use std::{
17 ffi::OsStr,
18 sync::{
19 Arc, Mutex,
20 atomic::{AtomicBool, AtomicU64, Ordering},
21 },
22 thread,
23 time::{Duration, Instant},
24};
25
26use anyhow::{Context, Result, ensure};
27use libbpf_rs::{AsRawLibbpf, MapCore, MapFlags};
28
29use crate::{
30 Loaded, OpsProfiler,
31 edq::{self, View},
32};
33
34#[derive(Clone)]
39pub struct ArenaView {
40 base: u64,
41 bytes: u64,
42 _object: Loaded,
43}
44
45impl ArenaView {
46 pub fn new(object: &Loaded) -> Result<Self> {
48 let map = object
49 .maps()
50 .find(|map| map.name() == OsStr::new("arena"))
51 .context("find arena map")?;
52 let mut size = 0;
53 let base = unsafe {
55 libbpf_rs::libbpf_sys::bpf_map__initial_value(
56 map.as_libbpf_object().as_ptr(),
57 &mut size,
58 )
59 };
60 ensure!(!base.is_null(), "the arena is not mapped");
61 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }.max(1) as u64;
63 Ok(Self {
64 base: base as u64,
65 bytes: u64::from(map.max_entries()) * page,
66 _object: object.clone(),
67 })
68 }
69
70 fn contains(&self, address: u64, bytes: u64) -> bool {
71 address >= self.base && bytes <= self.bytes && address - self.base <= self.bytes - bytes
72 }
73
74 pub fn read(&self, address: u64) -> u64 {
78 if !address.is_multiple_of(8) || !self.contains(address, 8) {
79 return 0;
80 }
81 unsafe { (*(address as *const AtomicU64)).load(Ordering::Relaxed) }
84 }
85}
86
87impl View for ArenaView {
88 fn load(&self, address: u64) -> u64 {
89 self.read(address)
90 }
91}
92
93pub struct CidTableReader {
98 view: ArenaView,
99 table: u64,
100 count: u64,
101 words: u64,
102}
103
104impl CidTableReader {
105 pub fn new(object: &Loaded, section: &str, words: usize) -> Result<Self> {
107 let view = ArenaView::new(object)?;
108 let header = object
109 .maps()
110 .find(|map| map.name() == OsStr::new(section))
111 .with_context(|| format!("find the table {section}"))?;
112 let mut data = [0_u8; 16];
113 ensure!(
114 header.lookup_into(&0_u32.to_ne_bytes(), &mut data, MapFlags::ANY)?,
115 "read the header of {section}"
116 );
117 let table = u64::from_ne_bytes(data[..8].try_into()?);
118 let count = u64::from_ne_bytes(data[8..].try_into()?);
119 let words = words as u64;
120 ensure!(
121 count != 0 && count <= u64::from(u32::MAX) && view.contains(table, count * words * 8),
122 "the table {section} is not in the arena"
123 );
124 Ok(Self {
125 view,
126 table,
127 count,
128 words,
129 })
130 }
131
132 pub fn sum(&self) -> Vec<u64> {
134 let mut sums = vec![0_u64; self.words as usize];
135 for cid in 0..self.count {
136 for (word, sum) in sums.iter_mut().enumerate() {
137 let address = self.table + (cid * self.words + word as u64) * 8;
138 *sum = sum.wrapping_add(self.view.read(address));
139 }
140 }
141 sums
142 }
143}
144
145#[derive(Clone, Copy, Debug, Default)]
147pub struct QueueStats {
148 pub inserts: u64,
150 pub depth_sum: u64,
152 pub busy: u64,
154 pub locked: u64,
156}
157
158struct State {
159 last_read: Instant,
160 passes: u64,
161 queues: Vec<QueueStats>,
162}
163
164struct Shared {
165 view: ArenaView,
166 table: u64,
167 stop: AtomicBool,
168 state: Mutex<State>,
169}
170
171impl Shared {
172 fn queue(&self, cid: usize) -> u64 {
173 self.table + cid as u64 * edq::QUEUE_BYTES
174 }
175
176 fn pass(&self) {
178 let mut state = self.state.lock().unwrap();
179 for (cid, queue) in state.queues.iter_mut().enumerate() {
180 let address = self.queue(cid);
181 let depth = edq::nr_queued(&self.view, address);
182 queue.depth_sum += depth;
183 queue.busy += u64::from(depth != 0);
184 queue.locked += u64::from(edq::locked(&self.view, address));
185 }
186 state.passes += 1;
187 }
188
189 fn run(&self) {
190 let mut random = self.table | 1;
192 while !self.stop.load(Ordering::Relaxed) {
193 let idle = self.state.lock().unwrap().last_read.elapsed() > OpsProfiler::IDLE;
194 if idle {
195 thread::park();
196 continue;
197 }
198 self.pass();
199 random ^= random << 13;
200 random ^= random >> 7;
201 random ^= random << 17;
202 thread::sleep(
203 QueueSampler::PERIOD_MIN
204 + QueueSampler::PERIOD_SPREAD
205 .mul_f64((random >> 11) as f64 / (1_u64 << 53) as f64),
206 );
207 }
208 }
209}
210
211pub struct QueueSampler {
215 shared: Arc<Shared>,
216 thread: Option<thread::JoinHandle<()>>,
217}
218
219impl QueueSampler {
220 const PERIOD_MIN: Duration = Duration::from_millis(5);
225 const PERIOD_SPREAD: Duration = Duration::from_millis(10);
226
227 pub fn new(object: &Loaded) -> Result<Self> {
229 let view = ArenaView::new(object)?;
230 let queues = object
231 .maps()
232 .find(|map| map.name() == OsStr::new(".data.queues"))
233 .context("find the queue table")?;
234 let mut data = [0_u8; 24];
235 ensure!(
236 queues.lookup_into(&0_u32.to_ne_bytes(), &mut data, MapFlags::ANY)?,
237 "read the queue table"
238 );
239 let table = u64::from_ne_bytes(data[..8].try_into()?);
240 let count = u64::from_ne_bytes(data[8..16].try_into()?);
241 ensure!(
242 count != 0
243 && count <= u64::from(u32::MAX)
244 && view.contains(table, count * edq::QUEUE_BYTES),
245 "the queue table is not in the arena"
246 );
247 let shared = Arc::new(Shared {
248 view,
249 table,
250 stop: AtomicBool::new(false),
251 state: Mutex::new(State {
252 last_read: Instant::now() - OpsProfiler::IDLE * 2,
254 passes: 0,
255 queues: vec![QueueStats::default(); count as usize],
256 }),
257 });
258 let sampler = shared.clone();
259 let thread = thread::Builder::new()
260 .name("edq-sampler".into())
261 .spawn(move || sampler.run())
262 .context("start the queue sampler")?;
263 Ok(Self {
264 shared,
265 thread: Some(thread),
266 })
267 }
268
269 pub fn sample(&self) -> (u64, Vec<QueueStats>) {
272 let (passes, mut queues) = {
273 let mut state = self.shared.state.lock().unwrap();
274 state.last_read = Instant::now();
275 (state.passes, state.queues.clone())
276 };
277 if let Some(thread) = &self.thread {
278 thread.thread().unpark();
279 }
280 for (cid, queue) in queues.iter_mut().enumerate() {
281 queue.inserts = edq::nr_inserted(&self.shared.view, self.shared.queue(cid));
282 }
283 (passes, queues)
284 }
285}
286
287impl Drop for QueueSampler {
288 fn drop(&mut self) {
289 self.shared.stop.store(true, Ordering::Relaxed);
290 if let Some(thread) = self.thread.take() {
291 thread.thread().unpark();
292 let _ = thread.join();
293 }
294 }
295}