1use alloc::sync::Arc;
4use core::sync::atomic::{AtomicU64, Ordering};
5
6use super::ThreadId;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum SchedulerTickMode {
15 User,
17 System,
19}
20
21#[derive(Debug)]
26pub struct SchedulerTickCpuTime {
27 user_ns: AtomicU64,
28 system_ns: AtomicU64,
29 realtime: Option<RealtimeTickAccounting>,
30}
31
32#[derive(Debug)]
33struct RealtimeTickAccounting {
34 gate: Arc<SchedulerTickGate>,
35 period_ns: AtomicU64,
36 last_period: AtomicU64,
37 ticks: AtomicU64,
38}
39
40impl SchedulerTickCpuTime {
41 pub const fn new() -> Self {
43 Self {
44 user_ns: AtomicU64::new(0),
45 system_ns: AtomicU64::new(0),
46 realtime: None,
47 }
48 }
49
50 pub fn with_realtime_gate(gate: Arc<SchedulerTickGate>) -> Self {
58 Self {
59 realtime: Some(RealtimeTickAccounting {
60 gate,
61 period_ns: AtomicU64::new(0),
62 last_period: AtomicU64::new(u64::MAX),
63 ticks: AtomicU64::new(0),
64 }),
65 ..Self::new()
66 }
67 }
68
69 pub fn realtime_ticks(&self) -> Option<(u64, core::num::NonZeroU64)> {
75 let realtime = self.realtime.as_ref()?;
76 let period = core::num::NonZeroU64::new(realtime.period_ns.load(Ordering::Acquire))?;
77 Some((realtime.ticks.load(Ordering::Acquire), period))
78 }
79
80 pub(crate) fn sample_realtime(&self, wall_ns: u64, tick_ns: u64) {
81 let Some(realtime) = &self.realtime else {
82 return;
83 };
84 if realtime.gate.enabled_generation().is_none() {
85 return;
86 }
87 assert_ne!(tick_ns, 0, "real-time tick period must be nonzero");
88 let period = realtime.period_ns.load(Ordering::Relaxed);
89 if period == 0 {
90 realtime.period_ns.store(tick_ns, Ordering::Release);
91 } else {
92 assert_eq!(period, tick_ns, "real-time tick period changed");
93 }
94 let current_period = wall_ns / tick_ns;
98 if realtime.last_period.load(Ordering::Relaxed) == current_period {
99 return;
100 }
101 realtime
102 .last_period
103 .store(current_period, Ordering::Relaxed);
104 let ticks = realtime.ticks.load(Ordering::Relaxed);
105 realtime
106 .ticks
107 .store(ticks.saturating_add(1), Ordering::Release);
108 }
109
110 pub(crate) fn reset_realtime(&self) {
111 if let Some(realtime) = &self.realtime {
112 realtime.ticks.store(0, Ordering::Release);
114 }
115 }
116
117 pub fn snapshot(&self) -> SchedulerTickCpuTimeSnapshot {
119 SchedulerTickCpuTimeSnapshot {
120 user_ns: self.user_ns.load(Ordering::Acquire),
121 system_ns: self.system_ns.load(Ordering::Acquire),
122 }
123 }
124
125 pub(crate) fn sample(&self, mode: SchedulerTickMode, tick_ns: u64) {
126 let total = match mode {
127 SchedulerTickMode::User => &self.user_ns,
128 SchedulerTickMode::System => &self.system_ns,
129 };
130 total
131 .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
132 Some(current.saturating_add(tick_ns))
133 })
134 .expect("infallible scheduler-tick CPU-time update failed");
135 }
136}
137
138impl Default for SchedulerTickCpuTime {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
146pub struct SchedulerTickCpuTimeSnapshot {
147 user_ns: u64,
148 system_ns: u64,
149}
150
151impl SchedulerTickCpuTimeSnapshot {
152 pub const fn user_ns(self) -> u64 {
154 self.user_ns
155 }
156
157 pub const fn system_ns(self) -> u64 {
159 self.system_ns
160 }
161}
162
163#[derive(Debug)]
169pub struct SchedulerTickGate {
170 state: AtomicU64,
171}
172
173impl SchedulerTickGate {
174 const ENABLED: u64 = 1;
175 const GENERATION_STEP: u64 = 2;
176
177 pub const fn new() -> Self {
179 Self {
180 state: AtomicU64::new(0),
181 }
182 }
183
184 pub fn set_enabled(&self, enabled: bool) {
190 let mut observed = self.state.load(Ordering::Acquire);
191 loop {
192 if (observed & Self::ENABLED != 0) == enabled {
193 return;
194 }
195 let generation = observed
196 .checked_add(Self::GENERATION_STEP)
197 .expect("scheduler tick gate generation overflow");
198 let updated = (generation & !Self::ENABLED) | u64::from(enabled);
199 match self.state.compare_exchange_weak(
200 observed,
201 updated,
202 Ordering::AcqRel,
203 Ordering::Acquire,
204 ) {
205 Ok(_) => return,
206 Err(current) => observed = current,
207 }
208 }
209 }
210
211 fn enabled_generation(&self) -> Option<u64> {
212 let state = self.state.load(Ordering::Acquire);
213 (state & Self::ENABLED != 0).then_some(state)
214 }
215
216 fn generation_is_enabled(&self, generation: u64) -> bool {
217 self.state.load(Ordering::Acquire) == generation
218 }
219}
220
221impl Default for SchedulerTickGate {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
238pub enum SchedulerTickWorkDisposition {
239 Complete,
241 Retry,
243}
244
245pub type SchedulerTickTaskWork = unsafe extern "Rust" fn(
247 data: usize,
248 thread: ThreadId,
249 observed_ns: u64,
250) -> SchedulerTickWorkDisposition;
251
252#[derive(Clone, Debug)]
253pub(crate) struct SchedulerTickWork {
254 gate: Arc<SchedulerTickGate>,
255 callback: SchedulerTickTaskWork,
256}
257
258impl SchedulerTickWork {
259 pub(crate) const fn new(gate: Arc<SchedulerTickGate>, callback: SchedulerTickTaskWork) -> Self {
260 Self { gate, callback }
261 }
262
263 pub(crate) fn enabled_generation(&self) -> Option<u64> {
264 self.gate.enabled_generation()
265 }
266
267 pub(crate) fn generation_is_enabled(&self, generation: u64) -> bool {
268 self.gate.generation_is_enabled(generation)
269 }
270
271 pub(crate) unsafe fn invoke(
272 &self,
273 data: usize,
274 thread: ThreadId,
275 observed_ns: u64,
276 ) -> SchedulerTickWorkDisposition {
277 unsafe { (self.callback)(data, thread, observed_ns) }
278 }
279}
280
281#[derive(Debug)]
283pub(crate) struct SchedulerTickWorkClaim {
284 work: SchedulerTickWork,
285 generation: u64,
286 observed_ns: u64,
287}
288
289impl SchedulerTickWorkClaim {
290 pub(crate) const fn new(work: SchedulerTickWork, generation: u64, observed_ns: u64) -> Self {
291 Self {
292 work,
293 generation,
294 observed_ns,
295 }
296 }
297
298 pub(crate) const fn generation(&self) -> u64 {
299 self.generation
300 }
301
302 pub(crate) fn generation_is_enabled(&self) -> bool {
303 self.work.generation_is_enabled(self.generation)
304 }
305
306 pub(crate) unsafe fn invoke(
307 &self,
308 data: usize,
309 thread: ThreadId,
310 ) -> SchedulerTickWorkDisposition {
311 unsafe { self.work.invoke(data, thread, self.observed_ns) }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn periodic_tick_samples_only_the_published_execution_mode() {
321 let accounting = SchedulerTickCpuTime::new();
322
323 accounting.sample(SchedulerTickMode::User, 10);
324 accounting.sample(SchedulerTickMode::System, 10);
325
326 assert_eq!(
327 accounting.snapshot(),
328 SchedulerTickCpuTimeSnapshot {
329 user_ns: 10,
330 system_ns: 10,
331 }
332 );
333 }
334}