Skip to main content

rivet/
report.rs

1//! `rivet::report()` — a single call that dumps kernel-wide state to the
2//! console: every live task's priority (base/effective), state, stack
3//! watermark, and `%busy` execution-time share, plus registry-wide
4//! timer/task slot usage.
5//!
6//! Scope note (plan.md Phase 8, extended by Phase 10): `%busy` is backed
7//! by [`crate::exec_time`], itself built on the Group A cycle counter.
8//! Periods/budgets (plan.md Phase 11) don't get their own report column —
9//! a budget overrun raises [`crate::fault::FaultKind::BudgetExceeded`]
10//! immediately through the normal fault policy rather than being tallied
11//! silently, so there's no "miss count" to display.
12
13use core::sync::atomic::Ordering;
14
15use crate::preempt::tcb::{self, TaskState};
16
17fn print_dec(mut n: usize) {
18    if n == 0 {
19        crate::console::write_str("0");
20        return;
21    }
22    let mut digits = [0u8; 20];
23    let mut i = 0;
24    while n > 0 {
25        digits[i] = b'0' + (n % 10) as u8;
26        n /= 10;
27        i += 1;
28    }
29    let mut buf = [0u8; 20];
30    for j in 0..i {
31        buf[j] = digits[i - 1 - j];
32    }
33    if let Ok(s) = core::str::from_utf8(&buf[..i]) {
34        crate::console::write_str(s);
35    }
36}
37
38/// A registered task's stack watermark: `(used, total)` bytes, or `None`
39/// if `id` isn't a live, registered task or has no stack pool entry.
40/// Factored out of [`report`] so callers that need just one task's number
41/// (e.g. a periodic trace-emitting task) don't have to re-derive the
42/// scratch-window dance below.
43///
44/// On Cortex-M, another task's pool-allocated stack is outside the
45/// *currently running* task's MPU region-7 window (region 6 denies the
46/// rest of the whole pool by design) — reading it needs the same
47/// scratch-window primitive `preempt::spawn`/`stack_pool::release_stack`
48/// already use for the same reason (a no-op on arches without a
49/// whole-pool deny region, e.g. RISC-V). Opening the window disables that
50/// deny for the *entire* pool, not just this stack, so it must run under
51/// a critical section: a context switch mid-window would leave every
52/// other task's stack briefly unguarded too.
53pub fn task_stack_usage(id: usize) -> Option<(usize, usize)> {
54    let t = tcb::get(id)?;
55    let base_addr = t.stack_base.load(Ordering::Acquire);
56    let size = t.stack_size.load(Ordering::Acquire);
57    if base_addr == 0 || size == 0 {
58        return None;
59    }
60    let used = crate::critical::enter(|| {
61        crate::port::arch::scratch_open(base_addr, size);
62        // SAFETY: reading a registered task's own stack range from
63        // outside that task is safe for watermarking purposes — the
64        // bytes below the high-water mark are never written again once
65        // touched, and this doesn't rely on the *current* contents above
66        // it, only on how far the 0xAA fill pattern has been
67        // overwritten. The scratch window (opened above, under this
68        // critical section) ensures this is also *permitted* by the MPU,
69        // not just logically sound.
70        let stack = unsafe { core::slice::from_raw_parts(base_addr as *const u8, size) };
71        let used = crate::preempt::stack_usage(stack);
72        crate::port::arch::scratch_close();
73        used
74    });
75    Some((used, size))
76}
77
78/// Print a full kernel state dump to the console. Safe to call from any
79/// task context (not ISR-safe — it does blocking console writes, same as
80/// [`crate::console::write_str`] in general; see [`crate::log`] for the
81/// ISR-safe alternative when you need to trace from interrupt context).
82pub fn report() {
83    crate::console::write_str("=== rivet::report() ===\n");
84
85    let mut used_count = 0usize;
86    for (id, t) in tcb::TASKS.iter().enumerate() {
87        if !t.used.load(Ordering::Acquire) {
88            continue;
89        }
90        used_count += 1;
91
92        crate::console::write_str("task ");
93        print_dec(id);
94
95        let base = t.base_priority.load(Ordering::Acquire);
96        let eff = t.effective_priority.load(Ordering::Acquire);
97        crate::console::write_str(" prio=");
98        print_dec(base as usize);
99        if eff != base {
100            crate::console::write_str("(eff=");
101            print_dec(eff as usize);
102            crate::console::write_str(")");
103        }
104
105        crate::console::write_str(" state=");
106        if t.exited.load(Ordering::Acquire) {
107            crate::console::write_str("exited");
108        } else {
109            crate::console::write_str(match t.state() {
110                TaskState::Ready => "ready",
111                TaskState::Running => "running",
112                TaskState::Blocked => "blocked",
113            });
114        }
115
116        if let Some((used, size)) = task_stack_usage(id) {
117            crate::console::write_str(" stack=");
118            print_dec(used);
119            crate::console::write_str("/");
120            print_dec(size);
121        }
122
123        let held = t.held_count.load(Ordering::Acquire);
124        if held != 0 {
125            crate::console::write_str(" held_mutexes=");
126            print_dec(held as usize);
127        }
128
129        crate::console::write_str(" busy=");
130        print_dec(crate::exec_time::busy_percent(id) as usize);
131        crate::console::write_str("%");
132
133        crate::console::write_str("\n");
134    }
135
136    crate::console::write_str("ptask slots: ");
137    print_dec(used_count);
138    crate::console::write_str("/");
139    print_dec(tcb::MAX_PTASKS);
140    crate::console::write_str("\ntimer slots: ");
141    print_dec(crate::timer::slots_in_use());
142    crate::console::write_str("/");
143    print_dec(crate::timer::MAX_TIMERS);
144    crate::console::write_str("\n");
145
146    crate::console::write_str("log: ");
147    print_dec(crate::log::dropped_frames());
148    crate::console::write_str(" dropped frame(s)\n");
149
150    #[cfg(feature = "latency-histograms")]
151    print_latency_histograms();
152
153    crate::console::write_str("=== end report ===\n");
154}
155
156/// Print each latency histogram's non-empty buckets as `2^b:count` pairs
157/// (plan.md Phase 12). Only compiled with `latency-histograms`.
158#[cfg(feature = "latency-histograms")]
159fn print_latency_histograms() {
160    crate::console::write_str("latency (cycles, log2 buckets):\n");
161    for kind in crate::latency::ALL_KINDS {
162        crate::console::write_str("  ");
163        crate::console::write_str(crate::latency::name(kind));
164        crate::console::write_str(": ");
165        let snap = crate::latency::snapshot(kind);
166        let mut any = false;
167        for (b, count) in snap.iter().enumerate() {
168            if *count == 0 {
169                continue;
170            }
171            any = true;
172            crate::console::write_str("2^");
173            print_dec(b);
174            crate::console::write_str(":");
175            print_dec(*count as usize);
176            crate::console::write_str(" ");
177        }
178        if !any {
179            crate::console::write_str("(no samples)");
180        } else {
181            crate::console::write_str("max=");
182            print_dec(crate::latency::max_cycles(kind) as usize);
183        }
184        crate::console::write_str("\n");
185    }
186}