⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/perf/
trace.rs

1//! Lightweight static tracepoints for kernel performance analysis.
2//!
3//! Provides a per-CPU ring buffer of trace events that can be enabled/disabled
4//! at runtime. When disabled, the overhead is a single atomic load (branch on
5//! `TRACING_ENABLED`). When enabled, events are written to a fixed-size ring
6//! buffer per CPU, requiring no heap allocation.
7
8use core::{
9    cell::UnsafeCell,
10    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
11};
12
13/// Global tracing enable flag -- zero overhead when false (single atomic load).
14pub(crate) static TRACING_ENABLED: AtomicBool = AtomicBool::new(false);
15
16/// Trace event types
17#[repr(u8)]
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub(crate) enum TraceEventType {
20    /// System call entry
21    SyscallEntry = 0,
22    /// System call exit
23    SyscallExit = 1,
24    /// Context switch: task switched out
25    SchedSwitchOut = 2,
26    /// Context switch: task switched in
27    SchedSwitchIn = 3,
28    /// IPC fast path send
29    IpcFastSend = 4,
30    /// IPC fast path receive
31    IpcFastReceive = 5,
32    /// Frame allocator: allocate
33    FrameAlloc = 6,
34    /// Frame allocator: free
35    FrameFree = 7,
36    /// Page fault
37    PageFault = 8,
38    /// IPC slow path fallback
39    IpcSlowPath = 9,
40}
41
42/// A single trace event (32 bytes, cache-line friendly).
43#[repr(C)]
44#[derive(Clone, Copy)]
45pub(crate) struct TraceEvent {
46    /// Timestamp (architecture-specific cycle counter)
47    pub(crate) timestamp: u64,
48    /// Event type
49    pub(crate) event_type: u8,
50    /// CPU that generated this event
51    pub(crate) cpu: u8,
52    /// Padding for alignment
53    _pad: [u8; 6],
54    /// Event-specific data (e.g., PID, syscall number, frame number)
55    pub(crate) data: [u64; 2],
56}
57
58impl TraceEvent {
59    const fn empty() -> Self {
60        Self {
61            timestamp: 0,
62            event_type: 0,
63            cpu: 0,
64            _pad: [0; 6],
65            data: [0; 2],
66        }
67    }
68}
69
70/// Number of events per CPU ring buffer (4096 events = 128KB per CPU)
71const RING_SIZE: usize = 4096;
72
73/// Per-CPU trace ring buffer
74struct TraceRing {
75    events: [TraceEvent; RING_SIZE],
76    write_idx: AtomicUsize,
77}
78
79impl TraceRing {
80    const fn new() -> Self {
81        Self {
82            events: [TraceEvent::empty(); RING_SIZE],
83            write_idx: AtomicUsize::new(0),
84        }
85    }
86
87    /// Record an event into the ring buffer (overwrites oldest on wrap).
88    #[inline]
89    fn record(&mut self, event: TraceEvent) {
90        let idx = self.write_idx.load(Ordering::Relaxed) % RING_SIZE;
91        self.events[idx] = event;
92        self.write_idx.fetch_add(1, Ordering::Relaxed);
93    }
94
95    /// Get the total number of events recorded (may wrap)
96    fn total_events(&self) -> usize {
97        self.write_idx.load(Ordering::Relaxed)
98    }
99
100    /// Read events from the ring buffer (most recent first, up to `count`)
101    fn read_recent(&self, count: usize) -> impl Iterator<Item = &TraceEvent> {
102        let total = self.total_events();
103        let available = total.min(RING_SIZE);
104        let to_read = count.min(available);
105
106        let start_raw = if total >= RING_SIZE {
107            total - to_read
108        } else {
109            total.saturating_sub(to_read)
110        };
111
112        (start_raw..start_raw + to_read).map(move |i| &self.events[i % RING_SIZE])
113    }
114}
115
116/// Maximum CPUs for trace ring allocation
117const MAX_TRACE_CPUS: usize = 16;
118
119/// Per-CPU trace rings with interior mutability.
120///
121/// Each CPU writes only to its own ring via `current_cpu_id()`.
122/// Reading is done with tracing disabled or from the shell (single-threaded).
123struct PerCpuTraceRings([UnsafeCell<TraceRing>; MAX_TRACE_CPUS]);
124
125// SAFETY: Per-CPU access pattern ensures no data races -- each CPU only
126// writes to its own ring (indexed by cpu_id). Reading is done with tracing
127// disabled, preventing concurrent writes.
128unsafe impl Sync for PerCpuTraceRings {}
129
130static TRACE_RINGS: PerCpuTraceRings =
131    PerCpuTraceRings([const { UnsafeCell::new(TraceRing::new()) }; MAX_TRACE_CPUS]);
132
133/// Record a trace event (inline, minimal overhead).
134///
135/// When `TRACING_ENABLED` is false, this compiles down to a single atomic
136/// load and branch (typically predicted not-taken).
137#[inline(always)]
138pub(crate) fn trace_event(event_type: TraceEventType, data0: u64, data1: u64) {
139    if !TRACING_ENABLED.load(Ordering::Relaxed) {
140        return;
141    }
142
143    let cpu = crate::sched::smp::current_cpu_id() as usize;
144    let event = TraceEvent {
145        timestamp: crate::bench::read_timestamp(),
146        event_type: event_type as u8,
147        cpu: cpu as u8,
148        _pad: [0; 6],
149        data: [data0, data1],
150    };
151
152    // SAFETY: Each CPU writes only to its own ring. The cpu index is
153    // bounded by MAX_TRACE_CPUS via the min() call.
154    unsafe {
155        (*TRACE_RINGS.0[cpu.min(MAX_TRACE_CPUS - 1)].get()).record(event);
156    }
157}
158
159/// Enable tracing
160pub(crate) fn enable() {
161    TRACING_ENABLED.store(true, Ordering::Release);
162}
163
164/// Disable tracing
165pub(crate) fn disable() {
166    TRACING_ENABLED.store(false, Ordering::Release);
167}
168
169/// Check if tracing is enabled
170pub(crate) fn is_enabled() -> bool {
171    TRACING_ENABLED.load(Ordering::Relaxed)
172}
173
174/// Dump trace events from all CPUs to serial output.
175///
176/// Prints the most recent `count` events per CPU.
177pub(crate) fn dump_trace(count: usize) {
178    let was_enabled = is_enabled();
179    disable(); // Pause tracing during dump
180
181    crate::println!("=== Trace Dump (most recent {} per CPU) ===", count);
182    crate::println!(
183        "{:>12} {:>4} {:>18} {:>16} {:>16}",
184        "TIMESTAMP",
185        "CPU",
186        "EVENT",
187        "DATA0",
188        "DATA1"
189    );
190
191    #[allow(clippy::needless_range_loop)]
192    for cpu in 0..MAX_TRACE_CPUS {
193        // SAFETY: We disabled tracing, so no concurrent writes.
194        // We only read from the ring buffer.
195        let ring = unsafe { &*TRACE_RINGS.0[cpu].get() };
196        let total = ring.total_events();
197        if total == 0 {
198            continue;
199        }
200
201        for event in ring.read_recent(count) {
202            if event.timestamp == 0 {
203                continue;
204            }
205            let name = event_type_name(event.event_type);
206            crate::println!(
207                "{:>12} {:>4} {:>18} {:#016x} {:#016x}",
208                event.timestamp,
209                event.cpu,
210                name,
211                event.data[0],
212                event.data[1]
213            );
214        }
215    }
216
217    let total: usize = (0..MAX_TRACE_CPUS)
218        .map(|cpu| unsafe { (*TRACE_RINGS.0[cpu].get()).total_events() })
219        .sum();
220    crate::println!("=== Total events recorded: {} ===", total);
221
222    if was_enabled {
223        enable(); // Re-enable if it was on
224    }
225}
226
227/// Get total events across all CPUs
228pub(crate) fn total_events() -> usize {
229    (0..MAX_TRACE_CPUS)
230        .map(|cpu| unsafe { (*TRACE_RINGS.0[cpu].get()).total_events() })
231        .sum()
232}
233
234fn event_type_name(t: u8) -> &'static str {
235    match t {
236        0 => "syscall_entry",
237        1 => "syscall_exit",
238        2 => "sched_switch_out",
239        3 => "sched_switch_in",
240        4 => "ipc_fast_send",
241        5 => "ipc_fast_recv",
242        6 => "frame_alloc",
243        7 => "frame_free",
244        8 => "page_fault",
245        9 => "ipc_slow_path",
246        _ => "unknown",
247    }
248}
249
250/// Convenience macro for recording trace events with zero overhead when
251/// disabled.
252#[macro_export]
253macro_rules! trace {
254    ($event_type:expr, $data0:expr, $data1:expr) => {
255        $crate::perf::trace::trace_event($event_type, $data0, $data1)
256    };
257    ($event_type:expr, $data0:expr) => {
258        $crate::perf::trace::trace_event($event_type, $data0, 0)
259    };
260    ($event_type:expr) => {
261        $crate::perf::trace::trace_event($event_type, 0, 0)
262    };
263}