Skip to main content

ktrace_rs/
lib.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#![no_std]
8
9use unittest as _;
10
11use arch_rs::{InterruptDisableGuard, curr_cpu_num, ints_disabled};
12use core::cell::UnsafeCell;
13use core::mem::{MaybeUninit, size_of};
14use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
15use core::{ffi, ptr, slice};
16use kalloc::Box;
17use kernel::thread::{FxtRef, ThreadPtr};
18pub use kstring::declare_interned_category;
19use kstring::declare_interned_string;
20use kstring::interned_category::InternedCategory;
21pub use kstring::interned_string::InternedString;
22pub use platform_rs::{InstantBootTicks, timer_current_boot_ticks};
23use spsc_buffer::{Buffer, NoOpAllocator, Reservation};
24use zx_status::Status;
25
26// Re-export the ktrace macros from the sub-crate.
27pub use ktrace_macro::*;
28#[allow(unused_extern_crates)]
29extern crate self as ktrace_rs;
30
31// LINT.IfChange(KTraceState)
32#[repr(C)]
33pub struct KTraceState {
34    pub categories_bitmask: AtomicU32,
35    pub writes_enabled: AtomicBool,
36}
37const _: () = assert!(size_of::<KTraceState>() == 8);
38// LINT.ThenChange(//zircon/kernel/include/lib/ktrace.h:KTraceState)
39
40declare_interned_category!(META_CAT, "kernel:meta", extern);
41declare_interned_string!(DROP_STATS_REF, "drop_stats", extern);
42declare_interned_string!(NUM_RECORDS_REF, "num_records", extern);
43declare_interned_string!(NUM_BYTES_REF, "num_bytes", extern);
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Context {
47    Thread = 0,
48    Cpu = 1,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum EventType {
53    Instant = 0,
54    Counter = 1,
55    DurationBegin = 2,
56    DurationEnd = 3,
57    DurationComplete = 4,
58    FlowBegin = 8,
59    FlowStep = 9,
60    FlowEnd = 10,
61}
62
63/// The value of a trace argument.
64pub enum ArgValue<'a> {
65    Null,
66    Bool(bool),
67    Int32(i32),
68    Uint32(u32),
69    Int64(i64),
70    Uint64(u64),
71    Double(f64),
72    String(&'a str),
73    Pointer(usize),
74    Koid(u64),
75}
76
77impl<'a> From<bool> for ArgValue<'a> {
78    fn from(v: bool) -> Self {
79        ArgValue::Bool(v)
80    }
81}
82impl<'a> From<i32> for ArgValue<'a> {
83    fn from(v: i32) -> Self {
84        ArgValue::Int32(v)
85    }
86}
87impl<'a> From<u32> for ArgValue<'a> {
88    fn from(v: u32) -> Self {
89        ArgValue::Uint32(v)
90    }
91}
92impl<'a> From<i64> for ArgValue<'a> {
93    fn from(v: i64) -> Self {
94        ArgValue::Int64(v)
95    }
96}
97impl<'a> From<u64> for ArgValue<'a> {
98    fn from(v: u64) -> Self {
99        ArgValue::Uint64(v)
100    }
101}
102impl<'a> From<f64> for ArgValue<'a> {
103    fn from(v: f64) -> Self {
104        ArgValue::Double(v)
105    }
106}
107impl<'a> From<&'a str> for ArgValue<'a> {
108    fn from(v: &'a str) -> Self {
109        ArgValue::String(v)
110    }
111}
112
113pub struct Argument<'a> {
114    name: &'static InternedString,
115    value: ArgValue<'a>,
116}
117
118impl<'a> Argument<'a> {
119    pub fn new(name: &'static InternedString, value: impl Into<ArgValue<'a>>) -> Self {
120        Self { name, value: value.into() }
121    }
122
123    /// Returns the size of this argument in 64-bit words.
124    fn size_words(&self) -> usize {
125        match &self.value {
126            ArgValue::Null | ArgValue::Bool(_) | ArgValue::Int32(_) | ArgValue::Uint32(_) => 1,
127            ArgValue::Int64(_)
128            | ArgValue::Uint64(_)
129            | ArgValue::Double(_)
130            | ArgValue::Pointer(_)
131            | ArgValue::Koid(_) => 2,
132            ArgValue::String(s) => 1 + (s.len() + 7) / 8,
133        }
134    }
135
136    fn write(&self, res: &mut KTraceReservation<'_>) -> Result<(), Status> {
137        let name_id = self.name.id() as u64;
138        let mut header = 0u64;
139        header |= (name_id & 0xffff) << 16; // NameRef
140
141        match &self.value {
142            ArgValue::Null => {
143                header |= 0u64; // ArgumentType::kNull (0)
144                res.write_word(header)?;
145            }
146            ArgValue::Int32(v) => {
147                header |= 1u64; // ArgumentType::kInt32 (1)
148                header |= ((*v as u32) as u64) << 32;
149                res.write_word(header)?;
150            }
151            ArgValue::Uint32(v) => {
152                header |= 2u64; // ArgumentType::kUint32 (2)
153                header |= (*v as u64) << 32;
154                res.write_word(header)?;
155            }
156            ArgValue::Int64(v) => {
157                header |= 3u64; // ArgumentType::kInt64 (3)
158                res.write_word(header)?;
159                res.write_word(*v as u64)?;
160            }
161            ArgValue::Uint64(v) => {
162                header |= 4u64; // ArgumentType::kUint64 (4)
163                res.write_word(header)?;
164                res.write_word(*v)?;
165            }
166            ArgValue::Double(v) => {
167                header |= 5u64; // ArgumentType::kDouble (5)
168                res.write_word(header)?;
169                res.write_word(v.to_bits())?;
170            }
171            ArgValue::String(s) => {
172                header |= 6u64; // ArgumentType::kString (6)
173                let string_len = s.len();
174                header |= (((string_len + 7) / 8) as u64) << 4; // ArgumentSize in words
175                header |= (string_len as u64) << 32; // String length in bytes
176                res.write_word(header)?;
177                res.write_bytes(s.as_bytes())?;
178            }
179            ArgValue::Pointer(v) => {
180                header |= 7u64; // ArgumentType::kPointer (7)
181                res.write_word(header)?;
182                res.write_word(*v as u64)?;
183            }
184            ArgValue::Koid(v) => {
185                header |= 8u64; // ArgumentType::kKoid (8)
186                res.write_word(header)?;
187                res.write_word(*v)?;
188            }
189            ArgValue::Bool(v) => {
190                header |= 9u64; // ArgumentType::kBool (9)
191                header |= ((*v as u64) & 1) << 32;
192                res.write_word(header)?;
193            }
194        }
195        Ok(())
196    }
197}
198
199pub struct KTraceScope<'a> {
200    category: &'static InternedCategory,
201    name: &'static InternedString,
202    timestamp: InstantBootTicks,
203    context: Context,
204    args: &'a [Argument<'a>],
205}
206
207impl<'a> KTraceScope<'a> {
208    #[inline(never)]
209    #[cold]
210    pub fn begin(
211        category: &'static InternedCategory,
212        name: &'static InternedString,
213        context: Context,
214        args: &'a [Argument<'a>],
215    ) -> Self {
216        let timestamp = timer_current_boot_ticks();
217        Self { category, name, timestamp, context, args }
218    }
219}
220
221impl<'a> Drop for KTraceScope<'a> {
222    #[inline(never)]
223    #[cold]
224    fn drop(&mut self) {
225        let end_time = timer_current_boot_ticks();
226        let ktrace = KTrace::get_instance();
227        ktrace.emit_event(
228            EventType::DurationComplete,
229            self.category,
230            self.name,
231            self.timestamp,
232            self.context,
233            Some(end_time.0 as u64),
234            self.args,
235        );
236    }
237}
238
239// LINT.IfChange(DroppedRecordDurationEvent)
240#[repr(C)]
241struct DroppedRecordDurationEvent {
242    header: u64,
243    start: InstantBootTicks,
244    process_id: u64,
245    thread_id: u64,
246    num_dropped_arg: u64,
247    bytes_dropped_arg: u64,
248    end: InstantBootTicks,
249}
250const _: () = assert!(size_of::<DroppedRecordDurationEvent>() == 56);
251// LINT.ThenChange(//zircon/kernel/lib/percpu_writer/include/lib/percpu_writer/buffer.h:DroppedRecordDurationEvent)
252
253// LINT.IfChange(DroppedRecordStats)
254/// This struct keeps track of the duration, number, and size of trace records dropped when the
255/// buffer is full. These statistics are emitted to the trace buffer as a duration as soon as
256/// space is available to do so, at which point the values are reset to 0, or false in the case
257/// of has_dropped.
258#[repr(C)]
259#[derive(Default, Debug, Clone)]
260pub struct DroppedRecordStats {
261    pub first_dropped: InstantBootTicks,
262    pub last_dropped: InstantBootTicks,
263
264    /// By storing num_dropped and bytes_dropped in 32-bit values, we ensure that they can each
265    /// be stored in a single 64-bit word in the FXT record we emit when space is available.
266    pub num_dropped: u32,
267    pub bytes_dropped: u32,
268    pub has_dropped: bool,
269}
270const _: () = assert!(size_of::<DroppedRecordStats>() == 32);
271// LINT.ThenChange(//zircon/kernel/lib/percpu_writer/include/lib/percpu_writer/buffer.h:DroppedRecordStats)
272
273impl DroppedRecordStats {
274    pub fn reset(&mut self) {
275        self.first_dropped = InstantBootTicks(0);
276        self.last_dropped = InstantBootTicks(0);
277        self.num_dropped = 0;
278        self.bytes_dropped = 0;
279        self.has_dropped = false;
280    }
281
282    pub fn track(&mut self, now: InstantBootTicks, size: u32) {
283        if !self.has_dropped {
284            self.first_dropped = now;
285            self.has_dropped = true;
286        }
287        self.last_dropped = now;
288        self.num_dropped = self.num_dropped.wrapping_add(1);
289        self.bytes_dropped = self.bytes_dropped.wrapping_add(size);
290    }
291
292    pub fn has_dropped(&self) -> bool {
293        self.has_dropped
294    }
295}
296
297/// A Rust implementation of `percpu_writer::Buffer` that wraps a static reference to an existing
298/// `spsc_buffer::Buffer` and tracks dropped trace records.
299pub struct KTraceBuffer {
300    buffer: &'static mut Buffer<NoOpAllocator>,
301    pub drop_stats: &'static mut DroppedRecordStats,
302    size: u32,
303    cpu_ref_header_entry: u16,
304    pub process_koid: u64,
305    pub thread_koid: u64,
306}
307
308// SAFETY: Access to the per-CPU buffers is synchronized by the caller (typically via disabling
309// interrupts).
310unsafe impl Send for KTraceBuffer {}
311unsafe impl Sync for KTraceBuffer {}
312
313impl KTraceBuffer {
314    /// Constructs a `KTraceBuffer` from a static reference to an existing `spsc_buffer::Buffer`,
315    /// a static reference to `DroppedRecordStats`, and its metadata.
316    pub fn new(
317        buffer: &'static mut Buffer<NoOpAllocator>,
318        drop_stats: &'static mut DroppedRecordStats,
319        cpu_ref_header_entry: u16,
320        process_koid: u64,
321        thread_koid: u64,
322    ) -> Self {
323        let size = buffer.size();
324        Self { buffer, drop_stats, size, cpu_ref_header_entry, process_koid, thread_koid }
325    }
326
327    /// Returns the size of the backing storage.
328    pub fn size(&self) -> u32 {
329        self.size
330    }
331
332    /// Drains the underlying Buffer.
333    pub fn drain(&self) -> Result<(), Status> {
334        self.buffer.drain()
335    }
336
337    /// Copies `len` bytes out of the buffer using the provided `copy_fn`.
338    pub fn read<F>(&self, copy_fn: F, len: u32) -> Result<u32, Status>
339    where
340        F: FnMut(u32, &[u8]) -> Result<(), Status>,
341    {
342        self.buffer.read(copy_fn, len)
343    }
344
345    /// Reserves a block of the given size in the buffer, interposing to write dropped record
346    /// statistics if any were tracked.
347    pub fn reserve(&mut self, header: u64) -> Result<KTraceReservation<'_>, Status> {
348        debug_assert!(ints_disabled());
349        // Compute the number of bytes we need to reserve from the provided fxt header.
350        let record_type = (header & 0xf) as u32;
351        let num_words = if record_type == 15 {
352            // Large record
353            ((header >> 4) & 0xffffffff) as u32
354        } else {
355            // Normal record
356            ((header >> 4) & 0xfff) as u32
357        };
358        let size = num_words * 8;
359
360        let mut total_size = size;
361        let event = if self.drop_stats.has_dropped() {
362            total_size += size_of::<DroppedRecordDurationEvent>() as u32;
363            Some(self.serialize_drop_stats())
364        } else {
365            None
366        };
367
368        match self.buffer.reserve(total_size) {
369            Err(status) => {
370                let now = timer_current_boot_ticks();
371                self.drop_stats.track(now, size);
372                Err(status)
373            }
374            Ok(mut res) => {
375                if let Some(event) = event {
376                    // SAFETY: DroppedRecordDurationEvent is repr(C) and has a defined binary
377                    // layout.
378                    let event_bytes = unsafe {
379                        slice::from_raw_parts(
380                            ptr::from_ref(&event).cast::<u8>(),
381                            size_of::<DroppedRecordDurationEvent>(),
382                        )
383                    };
384                    res.write(event_bytes)?;
385                    self.drop_stats.reset();
386                }
387                Ok(KTraceReservation::new(res, header))
388            }
389        }
390    }
391
392    /// Emit the dropped record stats to the trace buffer if we're currently tracking them.
393    pub fn emit_drop_stats(&mut self) -> Result<(), Status> {
394        debug_assert!(ints_disabled());
395        if !self.drop_stats.has_dropped() {
396            return Ok(());
397        }
398
399        // Serialize the event first so we release the borrow on self before calling reserve.
400        let event = self.serialize_drop_stats();
401
402        let mut res = self.buffer.reserve(size_of::<DroppedRecordDurationEvent>() as u32)?;
403        // SAFETY: DroppedRecordDurationEvent is repr(C) and has a defined binary layout.
404        let event_bytes = unsafe {
405            slice::from_raw_parts(
406                ptr::from_ref(&event).cast::<u8>(),
407                size_of::<DroppedRecordDurationEvent>(),
408            )
409        };
410        res.write(event_bytes)?;
411        res.commit()?;
412
413        // Reset the fields directly rather than calling reset_drop_stats() to avoid
414        // borrowing the entire self while res is in scope.
415        self.drop_stats.reset();
416        Ok(())
417    }
418
419    /// Resets the dropped records statistics to their initial values.
420    pub fn reset_drop_stats(&mut self) {
421        self.drop_stats.reset();
422    }
423
424    fn serialize_drop_stats(&self) -> DroppedRecordDurationEvent {
425        let mut header = 4u64; // RecordType::kEvent (4)
426        let record_size_words = (size_of::<DroppedRecordDurationEvent>() / 8) as u64;
427        header |= record_size_words << 4; // RecordSize
428        header |= 4u64 << 16; // EventType::kDurationComplete (4)
429        header |= 2u64 << 20; // ArgumentCount = 2
430        header |= (self.cpu_ref_header_entry as u64) << 24;
431        header |= (META_CAT.label().id() as u64) << 32;
432        header |= (DROP_STATS_REF.id() as u64) << 48;
433
434        // Pack the arguments.
435        // In FXT:
436        // ArgumentType::kUint32 is 2
437        // ArgumentSize is 1 word (8 bytes)
438        // NameRef is packed into bits 16..31
439        // Value is packed into bits 32..63
440        let mut num_dropped_arg = 2u64;
441        num_dropped_arg |= 1u64 << 4;
442        num_dropped_arg |= (NUM_RECORDS_REF.id() as u64) << 16;
443        num_dropped_arg |= (self.drop_stats.num_dropped as u64) << 32;
444
445        let mut bytes_dropped_arg = 2u64;
446        bytes_dropped_arg |= 1u64 << 4;
447        bytes_dropped_arg |= (NUM_BYTES_REF.id() as u64) << 16;
448        bytes_dropped_arg |= (self.drop_stats.bytes_dropped as u64) << 32;
449
450        DroppedRecordDurationEvent {
451            header,
452            start: self.drop_stats.first_dropped,
453            process_id: self.process_koid,
454            thread_id: self.thread_koid,
455            num_dropped_arg,
456            bytes_dropped_arg,
457            end: self.drop_stats.last_dropped,
458        }
459    }
460}
461
462/// KTraceReservation encapsulates a pending write to the buffer.
463#[derive(Debug)]
464pub struct KTraceReservation<'a> {
465    reservation: Reservation<'a>,
466}
467
468impl<'a> KTraceReservation<'a> {
469    fn new(reservation: Reservation<'a>, header: u64) -> Self {
470        let mut this = Self { reservation };
471        let _ = this.write_word(header);
472        this
473    }
474
475    /// Writes a single 64-bit word into the reservation.
476    pub fn write_word(&mut self, word: u64) -> Result<(), Status> {
477        debug_assert!(ints_disabled());
478        self.reservation.write(&word.to_ne_bytes())
479    }
480
481    /// Writes a byte slice into the reservation, padding to an 8-byte boundary.
482    pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Status> {
483        debug_assert!(ints_disabled());
484        self.reservation.write(bytes)?;
485        let num_bytes = bytes.len();
486        let aligned_bytes = (num_bytes + 7) & !7;
487        let num_zeros_to_write = aligned_bytes - num_bytes;
488        if num_zeros_to_write > 0 {
489            let zero = [0u8; 8];
490            self.reservation.write(&zero[..num_zeros_to_write])?;
491        }
492        Ok(())
493    }
494
495    /// Commits the reservation, making it visible to the reader.
496    pub fn commit(self) -> Result<(), Status> {
497        debug_assert!(ints_disabled());
498        self.reservation.commit()
499    }
500}
501
502/// A pure Rust implementation of KTrace.
503pub struct KTrace {
504    /// Reference to the shared C++ KTraceState.
505    // TODO(https://fxbug.dev/517305548): This should be made a direct allocation, and not a
506    // reference, once the C++ implementation is removed.
507    state: &'static KTraceState,
508
509    /// A heap-allocated slice of atomic pointers to per-CPU buffers.
510    /// Allocated once at boot time, and accessed completely lock-free on the hot path by CPU ID.
511    buffers: Box<[AtomicPtr<KTraceBuffer>]>,
512}
513
514// SAFETY: KTrace is a global singleton. Access to the per-CPU buffers is synchronized.
515unsafe impl Sync for KTrace {}
516unsafe impl Send for KTrace {}
517
518#[repr(transparent)]
519struct KTraceSingleton(UnsafeCell<MaybeUninit<KTrace>>);
520
521unsafe impl Sync for KTraceSingleton {}
522unsafe impl Send for KTraceSingleton {}
523
524static INSTANCE: KTraceSingleton = KTraceSingleton(UnsafeCell::new(MaybeUninit::uninit()));
525
526impl KTrace {
527    /// Retrieve the global instance of KTrace.
528    pub fn get_instance() -> &'static Self {
529        // SAFETY: KTrace must be initialized during kernel boot.
530        unsafe { &*INSTANCE.0.get().cast::<KTrace>() }
531    }
532
533    /// Returns the raw pointer to the KTraceBuffer for the given CPU.
534    pub fn get_buffer(&self, cpu: usize) -> *mut KTraceBuffer {
535        self.buffers[cpu].load(Ordering::Acquire)
536    }
537
538    /// Reserves a slot of memory to write a record into.
539    ///
540    /// # Safety
541    ///
542    /// This method MUST be invoked with interrupts disabled to enforce the single-writer invariant.
543    pub unsafe fn reserve(&self, header: u64) -> Result<KTraceReservation<'_>, Status> {
544        debug_assert!(ints_disabled());
545        if !self.writes_enabled() {
546            return Err(Status::BAD_STATE);
547        }
548
549        // Direct, lock-free indexing into the slice, followed by loading the atomic pointer!
550        let ptr = self.buffers[curr_cpu_num() as usize].load(Ordering::Acquire);
551        if ptr.is_null() {
552            return Err(Status::BAD_STATE);
553        }
554
555        let buf = unsafe { &mut *ptr };
556        buf.reserve(header)
557    }
558
559    /// Returns true if writes are currently enabled.
560    pub fn writes_enabled(&self) -> bool {
561        self.state.writes_enabled.load(Ordering::Acquire)
562    }
563
564    /// Returns the categories bitmask.
565    pub fn categories_bitmask(&self) -> u32 {
566        self.state.categories_bitmask.load(Ordering::Acquire)
567    }
568
569    /// Returns true if the given category is enabled.
570    pub fn is_category_enabled(&self, category: &InternedCategory) -> bool {
571        let category_index = category.index();
572        if category_index == InternedCategory::INVALID_INDEX {
573            return false;
574        }
575        let bitmask = self.categories_bitmask();
576        (bitmask & (1 << category_index)) != 0
577    }
578
579    /// Low-level helper to write an FXT kernel object record.
580    ///
581    /// This is not inlined to reduce code size at the instrumentation sites.
582    #[inline(never)]
583    #[cold]
584    pub fn emit_kernel_object_outlined(
585        &self,
586        koid: u64,
587        obj_type: u32,
588        name: &InternedString,
589        args: &[Argument<'_>],
590    ) {
591        let _guard = InterruptDisableGuard::new();
592        if !self.writes_enabled() {
593            return;
594        }
595
596        let base_size = 2; // Header, KOID
597        let args_size: usize = args.iter().map(|a| a.size_words()).sum();
598        let total_size_words = base_size + args_size;
599
600        if total_size_words > 0xfff {
601            return;
602        }
603
604        let mut header = 7u64; // RecordType::kKernelObject (7)
605        header |= (total_size_words as u64) << 4; // RecordSize
606        header |= (obj_type as u64) << 16; // ObjectType
607        header |= (name.id() as u64) << 24; // NameStringRef
608        header |= (args.len() as u64) << 40; // ArgumentCount
609
610        if let Ok(mut res) = unsafe { self.reserve(header) } {
611            let _ = res.write_word(koid);
612            for arg in args {
613                let _ = arg.write(&mut res);
614            }
615            let _ = res.commit();
616        }
617    }
618
619    /// Low-level helper to write a generic FXT event record.
620    ///
621    /// This is not inlined to reduce code size at the instrumentation sites.
622    #[inline(never)]
623    #[cold]
624    pub fn emit_event(
625        &self,
626        event_type: EventType,
627        category: &InternedCategory,
628        name: &InternedString,
629        timestamp: InstantBootTicks,
630        context: Context,
631        content: Option<u64>,
632        args: &[Argument<'_>],
633    ) {
634        let _guard = InterruptDisableGuard::new();
635        if !self.writes_enabled() {
636            return;
637        }
638
639        // 1. Get the process/thread KOIDs for the context.
640        let (process_koid, thread_koid) = match context {
641            Context::Thread => {
642                // SAFETY: ktrace is initialized and running after threading has been initialized.
643                let FxtRef { pid, tid } = unsafe { ThreadPtr::current().fxt_ref() };
644                (pid, tid)
645            }
646            Context::Cpu => {
647                let cpu = curr_cpu_num() as usize;
648                let ptr = self.buffers[cpu].load(Ordering::Acquire);
649                if ptr.is_null() {
650                    return;
651                }
652                let buf = unsafe { &*ptr };
653                (buf.process_koid, buf.thread_koid)
654            }
655        };
656
657        // 2. Calculate the record size.
658        let base_size = 4; // Header, Timestamp, Process KOID, Thread KOID
659        let content_size = if content.is_some() { 1 } else { 0 };
660        let args_size: usize = args.iter().map(|a| a.size_words()).sum();
661        let total_size_words = base_size + content_size + args_size;
662
663        if total_size_words > 0xfff {
664            return;
665        }
666
667        // 3. Construct the header.
668        let mut header = 4u64; // RecordType::kEvent (4)
669        header |= (total_size_words as u64) << 4; // RecordSize
670        header |= (event_type as u32 as u64) << 16; // EventType
671        header |= (args.len() as u64) << 20; // ArgumentCount
672        header |= (category.label().id() as u64) << 32; // CategoryStringRef
673        header |= (name.id() as u64) << 48; // NameStringRef
674
675        // 4. Reserve space and write the record.
676        if let Ok(mut res) = unsafe { self.reserve(header) } {
677            let _ = res.write_word(timestamp.0 as u64);
678            let _ = res.write_word(process_koid);
679            let _ = res.write_word(thread_koid);
680
681            for arg in args {
682                let _ = arg.write(&mut res);
683            }
684
685            if let Some(c) = content {
686                let _ = res.write_word(c);
687            }
688
689            let _ = res.commit();
690        }
691    }
692}
693
694/// Initializes the global KTrace instance with the given number of CPU buffers.
695///
696/// # Safety
697///
698/// This must be called at most once during kernel boot.
699#[unsafe(no_mangle)]
700pub unsafe extern "C" fn rust_ktrace_init(num_buffers: u32, state_ptr: *mut ffi::c_void) -> i32 {
701    if num_buffers == 0 || state_ptr.is_null() {
702        return Status::INVALID_ARGS.into_raw();
703    }
704
705    // SAFETY: The caller guarantees that state_ptr points to a valid KTraceState instance
706    // which has static storage duration (lives forever) and is safe to access concurrently
707    // (since its fields are atomic).
708    let state = unsafe { &*state_ptr.cast::<KTraceState>() };
709
710    let buffers = match Box::<[AtomicPtr<KTraceBuffer>]>::try_new_zeroed_slice(num_buffers as usize)
711    {
712        Ok(b) => b,
713        Err(_) => return Status::NO_MEMORY.into_raw(),
714    };
715
716    let ktrace = KTrace { state, buffers };
717
718    unsafe {
719        let slot = INSTANCE.0.get();
720        ptr::write(slot.cast::<KTrace>(), ktrace);
721    }
722
723    Status::OK.into_raw()
724}
725
726/// Initializes the KTraceBuffer for a specific CPU using a pointer to the C++ SpscBuffer.
727///
728/// # Safety
729///
730/// - `spsc_buffer_ptr` must point to a valid C++ `SpscBuffer` instance
731///   which is binary-compatible with `spsc_buffer::Buffer<NoOpAllocator>`.
732#[unsafe(no_mangle)]
733pub unsafe extern "C" fn rust_ktrace_init_cpu_buffer(
734    cpu_num: u32,
735    spsc_buffer_ptr: *mut ffi::c_void,
736    drop_stats_ptr: *mut ffi::c_void,
737    process_koid: u64,
738    thread_koid: u64,
739    cpu_ref_header_entry: u16,
740) -> i32 {
741    let ktrace = KTrace::get_instance();
742    if cpu_num >= ktrace.buffers.len() as u32 {
743        return Status::INVALID_ARGS.into_raw();
744    }
745
746    // SAFETY: The caller guarantees the pointers are valid, 'static, and binary-compatible with
747    // their respective Rust types.
748    let (buffer, drop_stats) = unsafe {
749        (
750            &mut *spsc_buffer_ptr.cast::<Buffer<NoOpAllocator>>(),
751            &mut *drop_stats_ptr.cast::<DroppedRecordStats>(),
752        )
753    };
754
755    // Allocate the KTraceBuffer on the heap.
756    let buf =
757        KTraceBuffer::new(buffer, drop_stats, cpu_ref_header_entry, process_koid, thread_koid);
758
759    let boxed_buf = match Box::try_new(buf) {
760        Ok(b) => b,
761        Err(_) => return Status::NO_MEMORY.into_raw(),
762    };
763
764    let raw_ptr = Box::into_raw(boxed_buf);
765
766    // Store the pointer atomically.
767    let old_ptr = ktrace.buffers[cpu_num as usize].swap(raw_ptr, Ordering::AcqRel);
768    if !old_ptr.is_null() {
769        // If there was a previous buffer, reclaim and drop it.
770        unsafe {
771            let _ = Box::from_raw(old_ptr);
772        }
773    }
774
775    Status::OK.into_raw()
776}
777
778/// KTrace tests
779#[cfg(all(not(gcc), ktest))]
780#[unittest::test_suite(name = "ktrace_rust")]
781mod tests {
782    use arch_rs::{InterruptDisableGuard, curr_cpu_num, max_num_cpus};
783    use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
784    use core::{ffi, ptr};
785    use kernel::thread::{FxtRef, ThreadPtr};
786    use spsc_buffer::Buffer;
787    use unittest::{
788        assert_eq, assert_ok, expect_eq, expect_false, expect_ne, expect_true, unwrap_ok,
789    };
790
791    declare_interned_category!(MEMORY_CAT, "kernel:memory", extern);
792    declare_interned_category!(SCHED_CAT, "kernel:sched", extern);
793    declare_interned_category!(CONTENTION_CAT, "kernel:contention", extern);
794    declare_interned_category!(IPC_CAT, "kernel:ipc", extern);
795    declare_interned_category!(IRQ_CAT, "kernel:irq", extern);
796
797    /// Initialization and size/metadata attributes.
798    #[test]
799    fn init_and_size() {
800        let mut storage = [0u8; 256];
801        let mut inner_buf = unsafe { Buffer::from_raw_parts(storage.as_mut_ptr(), storage.len()) };
802        let leaked_ref = unsafe { &mut *ptr::from_mut(&mut inner_buf) };
803        let mut stats = DroppedRecordStats::default();
804        let leaked_stats = unsafe { &mut *ptr::from_mut(&mut stats) };
805        let kbuf = KTraceBuffer::new(leaked_ref, leaked_stats, 1, 100, 200);
806
807        expect_eq!(kbuf.size(), 256);
808        expect_eq!(kbuf.process_koid, 100);
809        expect_eq!(kbuf.thread_koid, 200);
810    }
811
812    /// Reservation, writing words, and committing.
813    #[test]
814    fn test_write() {
815        let _guard = InterruptDisableGuard::new();
816        let mut storage = [0u8; 256];
817        let mut inner_buf = unsafe { Buffer::from_raw_parts(storage.as_mut_ptr(), storage.len()) };
818        let leaked_ref = unsafe { &mut *ptr::from_mut(&mut inner_buf) };
819        let mut stats = DroppedRecordStats::default();
820        let leaked_stats = unsafe { &mut *ptr::from_mut(&mut stats) };
821        let mut kbuf = KTraceBuffer::new(leaked_ref, leaked_stats, 1, 100, 200);
822
823        // Reserve 16 bytes (2 words).
824        let header = 4u64 | (2u64 << 4);
825        let mut res = unwrap_ok!(kbuf.reserve(header));
826
827        // Write a word (8 bytes).
828        assert_ok!(res.write_word(0xabcdef0123456789));
829        assert_ok!(res.commit());
830
831        // Read it back.
832        let mut read_bytes = [0u8; 16];
833        let read_len = unwrap_ok!(kbuf.read(
834            |offset, src| {
835                read_bytes[offset as usize..offset as usize + src.len()].copy_from_slice(src);
836                Ok(())
837            },
838            16,
839        ));
840
841        assert_eq!(read_len, 16);
842        expect_eq!(u64::from_ne_bytes(read_bytes[0..8].try_into().unwrap()), header);
843        expect_eq!(u64::from_ne_bytes(read_bytes[8..16].try_into().unwrap()), 0xabcdef0123456789);
844    }
845
846    // Verifies tracking of dropped records and their subsequent serialization when space becomes
847    // available.
848    //
849    /// Dropped record tracking and serialization.
850    #[test]
851    fn dropped_record_tracking() {
852        let _guard = InterruptDisableGuard::new();
853        let mut storage = [0u8; 128]; // small buffer
854        let mut inner_buf = unsafe { Buffer::from_raw_parts(storage.as_mut_ptr(), storage.len()) };
855        let leaked_ref = unsafe { &mut *ptr::from_mut(&mut inner_buf) };
856        let mut stats = DroppedRecordStats::default();
857        let leaked_stats = unsafe { &mut *ptr::from_mut(&mut stats) };
858        let mut kbuf = KTraceBuffer::new(leaked_ref, leaked_stats, 1, 100, 200);
859
860        // Reserve almost all space.
861        // 128 bytes total. Let's reserve 96 bytes (12 words).
862        let header = 4u64 | (12u64 << 4);
863        let mut res = unwrap_ok!(kbuf.reserve(header));
864        assert_ok!(res.write_bytes(&[0u8; 88]));
865        assert_ok!(res.commit());
866
867        // Now, try to reserve 64 bytes (8 words). This should fail because there are only 32 bytes
868        // left.
869        let header2 = 4u64 | (8u64 << 4);
870        expect_true!(kbuf.reserve(header2).err() == Some(Status::NO_SPACE));
871
872        // This failed reservation should have been tracked!
873        expect_true!(kbuf.drop_stats.has_dropped());
874        expect_eq!(kbuf.drop_stats.num_dropped, 1);
875        expect_eq!(kbuf.drop_stats.bytes_dropped, 64);
876
877        // Now, drain the buffer to free all space.
878        assert_ok!(kbuf.drain());
879
880        // Now, reserve 16 bytes (2 words).
881        // Since first_dropped was set, this reservation should successfully write the 56-byte
882        // dropped records duration event first!
883        let header3 = 4u64 | (2u64 << 4);
884        let mut res3 = unwrap_ok!(kbuf.reserve(header3));
885        assert_ok!(res3.write_bytes(&[0u8; 8]));
886        assert_ok!(res3.commit());
887
888        // The dropped stats should have been reset!
889        expect_false!(kbuf.drop_stats.has_dropped());
890        expect_eq!(kbuf.drop_stats.num_dropped, 0);
891        expect_eq!(kbuf.drop_stats.bytes_dropped, 0);
892
893        // Let's read the buffer content.
894        let mut read_bytes = [0u8; 72];
895        let read_len = unwrap_ok!(kbuf.read(
896            |offset, src| {
897                read_bytes[offset as usize..offset as usize + src.len()].copy_from_slice(src);
898                Ok(())
899            },
900            72,
901        ));
902        expect_eq!(read_len, 72);
903
904        // Verify the DroppedRecordDurationEvent header:
905        let event_header = u64::from_ne_bytes(read_bytes[0..8].try_into().unwrap());
906
907        expect_eq!(event_header & 0xf, 4);
908        expect_eq!((event_header >> 4) & 0xfff, 7);
909        expect_eq!((event_header >> 16) & 0xf, 4);
910        expect_eq!((event_header >> 20) & 0xf, 2);
911        expect_eq!((event_header >> 24) & 0xff, 1);
912        expect_eq!((event_header >> 32) & 0xffff, u64::from(META_CAT.label().id()));
913        expect_eq!((event_header >> 48) & 0xffff, u64::from(DROP_STATS_REF.id()));
914
915        // Verify process_koid (100) and thread_koid (200)
916        expect_eq!(u64::from_ne_bytes(read_bytes[16..24].try_into().unwrap()), 100);
917        expect_eq!(u64::from_ne_bytes(read_bytes[24..32].try_into().unwrap()), 200);
918
919        // Verify the two arguments:
920        let num_dropped_arg = u64::from_ne_bytes(read_bytes[32..40].try_into().unwrap());
921        expect_eq!(num_dropped_arg & 0xf, 2);
922        expect_eq!((num_dropped_arg >> 4) & 0xfff, 1);
923        expect_eq!((num_dropped_arg >> 16) & 0xffff, u64::from(NUM_RECORDS_REF.id()));
924        expect_eq!((num_dropped_arg >> 32) & 0xffffffff, 1);
925
926        let bytes_dropped_arg = u64::from_ne_bytes(read_bytes[40..48].try_into().unwrap());
927        expect_eq!(bytes_dropped_arg & 0xf, 2);
928        expect_eq!((bytes_dropped_arg >> 16) & 0xffff, u64::from(NUM_BYTES_REF.id()));
929        expect_eq!((bytes_dropped_arg >> 32) & 0xffffffff, 64);
930
931        // Verify the reservation header3:
932        let res_header = u64::from_ne_bytes(read_bytes[56..64].try_into().unwrap());
933        expect_eq!(res_header, header3);
934    }
935
936    /// Direct invocation of KTraceBuffer::emit_drop_stats.
937    #[test]
938    fn emit_drop_stats() {
939        let _guard = InterruptDisableGuard::new();
940        let mut storage = [0u8; 128];
941        let mut inner_buf = unsafe { Buffer::from_raw_parts(storage.as_mut_ptr(), storage.len()) };
942        let leaked_ref = unsafe { &mut *ptr::from_mut(&mut inner_buf) };
943        let mut stats = DroppedRecordStats::default();
944        let leaked_stats = unsafe { &mut *ptr::from_mut(&mut stats) };
945        let mut kbuf = KTraceBuffer::new(leaked_ref, leaked_stats, 1, 100, 200);
946
947        // 1. Force a failed reservation to track a dropped record.
948        let header = 4u64 | (32u64 << 4);
949        expect_true!(kbuf.reserve(header).err() == Some(Status::NO_SPACE));
950
951        expect_true!(kbuf.drop_stats.has_dropped());
952        expect_eq!(kbuf.drop_stats.num_dropped, 1);
953        expect_eq!(kbuf.drop_stats.bytes_dropped, 256);
954
955        // 2. Call emit_drop_stats directly.
956        assert_ok!(kbuf.emit_drop_stats());
957
958        expect_false!(kbuf.drop_stats.has_dropped());
959        expect_eq!(kbuf.drop_stats.num_dropped, 0);
960        expect_eq!(kbuf.drop_stats.bytes_dropped, 0);
961
962        // 3. Read and verify the event.
963        let mut read_bytes = [0u8; 56];
964        let read_len = unwrap_ok!(kbuf.read(
965            |offset, src| {
966                read_bytes[offset as usize..offset as usize + src.len()].copy_from_slice(src);
967                Ok(())
968            },
969            56,
970        ));
971
972        expect_eq!(read_len, 56);
973
974        let event_header = u64::from_ne_bytes(read_bytes[0..8].try_into().unwrap());
975        expect_eq!(event_header & 0xf, 4);
976        expect_eq!((event_header >> 4) & 0xfff, 7);
977        expect_eq!((event_header >> 16) & 0xf, 4);
978        expect_eq!((event_header >> 20) & 0xf, 2);
979        expect_eq!((event_header >> 24) & 0xff, 1);
980        expect_eq!((event_header >> 32) & 0xffff, u64::from(META_CAT.label().id()));
981        expect_eq!((event_header >> 48) & 0xffff, u64::from(DROP_STATS_REF.id()));
982
983        expect_eq!(u64::from_ne_bytes(read_bytes[16..24].try_into().unwrap()), 100);
984        expect_eq!(u64::from_ne_bytes(read_bytes[24..32].try_into().unwrap()), 200);
985
986        let num_dropped_arg = u64::from_ne_bytes(read_bytes[32..40].try_into().unwrap());
987        expect_eq!(num_dropped_arg & 0xf, 2);
988        expect_eq!((num_dropped_arg >> 16) & 0xffff, u64::from(NUM_RECORDS_REF.id()));
989        expect_eq!((num_dropped_arg >> 32) & 0xffffffff, 1);
990
991        let bytes_dropped_arg = u64::from_ne_bytes(read_bytes[40..48].try_into().unwrap());
992        expect_eq!(bytes_dropped_arg & 0xf, 2);
993        expect_eq!((bytes_dropped_arg >> 16) & 0xffff, u64::from(NUM_BYTES_REF.id()));
994        expect_eq!((bytes_dropped_arg >> 32) & 0xffffffff, 256);
995    }
996
997    // Verifies the full global lifecycle of KTrace: initialization, category bitmasks, CPU buffer
998    // allocation, and high-level tracing macro execution.
999    //
1000    /// Validate full global lifecycle.
1001    #[test]
1002    fn global_lifecycle() {
1003        let _guard = InterruptDisableGuard::new();
1004        // Initialize indices of the categories we're testing.
1005        META_CAT.set_index(0, kstring::interned_category::InternedCategory::INVALID_INDEX);
1006        MEMORY_CAT.set_index(1, kstring::interned_category::InternedCategory::INVALID_INDEX);
1007        SCHED_CAT.set_index(2, kstring::interned_category::InternedCategory::INVALID_INDEX);
1008        CONTENTION_CAT.set_index(3, kstring::interned_category::InternedCategory::INVALID_INDEX);
1009        IPC_CAT.set_index(4, kstring::interned_category::InternedCategory::INVALID_INDEX);
1010        IRQ_CAT.set_index(5, kstring::interned_category::InternedCategory::INVALID_INDEX);
1011
1012        // 1. Initialize the global KTrace instance with the system CPU count buffers and a local
1013        // mock state.
1014        let num_cpus = max_num_cpus();
1015        let mut local_state = KTraceState {
1016            categories_bitmask: AtomicU32::new(0),
1017            writes_enabled: AtomicBool::new(false),
1018        };
1019        let local_state_ptr = ptr::from_mut(&mut local_state).cast::<ffi::c_void>();
1020
1021        let status = unsafe { rust_ktrace_init(num_cpus, local_state_ptr) };
1022        expect_eq!(status, 0);
1023
1024        let ktrace = KTrace::get_instance();
1025
1026        // 2. Verify initial states.
1027        expect_false!(ktrace.writes_enabled());
1028        expect_eq!(ktrace.categories_bitmask(), 0);
1029        expect_false!(ktrace.is_category_enabled(&META_CAT));
1030        expect_false!(ktrace.is_category_enabled(&IRQ_CAT));
1031
1032        // 3. Test writes_enabled.
1033        local_state.writes_enabled.store(true, Ordering::Release);
1034        expect_true!(ktrace.writes_enabled());
1035        local_state.writes_enabled.store(false, Ordering::Release);
1036        expect_false!(ktrace.writes_enabled());
1037
1038        // 4. Test categories_bitmask.
1039        let mask = (1 << MEMORY_CAT.index()) | (1 << CONTENTION_CAT.index());
1040        local_state.categories_bitmask.store(mask, Ordering::Release);
1041        expect_eq!(ktrace.categories_bitmask(), mask);
1042        expect_false!(ktrace.is_category_enabled(&META_CAT));
1043        expect_true!(ktrace.is_category_enabled(&MEMORY_CAT));
1044        expect_false!(ktrace.is_category_enabled(&SCHED_CAT));
1045        expect_true!(ktrace.is_category_enabled(&CONTENTION_CAT));
1046        expect_false!(ktrace.is_category_enabled(&IPC_CAT));
1047
1048        // 5. Test CPU buffer initialization and reserve.
1049        let mut storage = [0u8; 256];
1050        let mut inner_buf = unsafe { Buffer::from_raw_parts(storage.as_mut_ptr(), storage.len()) };
1051        let inner_buf_ptr = ptr::from_mut(&mut inner_buf).cast::<ffi::c_void>();
1052        let mut stats = DroppedRecordStats::default();
1053        let stats_ptr = ptr::from_mut(&mut stats).cast::<ffi::c_void>();
1054
1055        // Initialize current CPU buffer.
1056        let cpu = curr_cpu_num();
1057        let init_status = unsafe {
1058            rust_ktrace_init_cpu_buffer(
1059                cpu, // cpu_num
1060                inner_buf_ptr,
1061                stats_ptr,
1062                100, // process_koid
1063                200, // thread_koid
1064                1,   // cpu_ref_header_entry
1065            )
1066        };
1067        expect_eq!(init_status, 0);
1068
1069        // If writes are disabled, reserving should fail.
1070        let header = 4u64 | (2u64 << 4); // Event with 2 words
1071        expect_true!(unsafe { ktrace.reserve(header) }.err() == Some(Status::BAD_STATE));
1072
1073        // Enable writes.
1074        local_state.writes_enabled.store(true, Ordering::Release);
1075
1076        // Now reserve should succeed!
1077        let mut res = unwrap_ok!(unsafe { ktrace.reserve(header) });
1078        assert_ok!(res.write_word(0x1234567890abcdef));
1079        assert_ok!(res.commit());
1080
1081        // Read and verify the written record from the current CPU buffer.
1082        let ptr = ktrace.get_buffer(cpu as usize);
1083        expect_false!(ptr.is_null());
1084        let buf = unsafe { &*ptr };
1085
1086        let mut read_bytes = [0u8; 16];
1087        let read_len = unwrap_ok!(buf.read(
1088            |offset, src| {
1089                read_bytes[offset as usize..offset as usize + src.len()].copy_from_slice(src);
1090                Ok(())
1091            },
1092            16,
1093        ));
1094
1095        expect_eq!(read_len, 16);
1096        expect_eq!(u64::from_ne_bytes(read_bytes[0..8].try_into().unwrap()), header);
1097        expect_eq!(u64::from_ne_bytes(read_bytes[8..16].try_into().unwrap()), 0x1234567890abcdef);
1098
1099        // 6. Test Rust macros!
1100        let mask = (1 << META_CAT.index()) | (1 << MEMORY_CAT.index());
1101        local_state.categories_bitmask.store(mask, Ordering::Release);
1102        expect_true!(ktrace.is_category_enabled(&META_CAT));
1103
1104        // Let's emit an instant event using the macro!
1105        instant!(META_CAT, "my_event", "arg1" => 42i32, "arg2" => "hello");
1106
1107        // Now let's read the record back and verify it!
1108        let mut read_bytes = [0u8; 128];
1109        let read_len = unwrap_ok!(buf.read(
1110            |offset, src| {
1111                read_bytes[offset as usize..offset as usize + src.len()].copy_from_slice(src);
1112                Ok(())
1113            },
1114            56,
1115        ));
1116
1117        expect_eq!(read_len, 56);
1118
1119        // Verify Header Word
1120        let header_word = u64::from_ne_bytes(read_bytes[0..8].try_into().unwrap());
1121        expect_eq!(header_word & 0xf, 4);
1122        expect_eq!((header_word >> 4) & 0xfff, 7);
1123        expect_eq!((header_word >> 16) & 0xf, 0);
1124        expect_eq!((header_word >> 20) & 0xf, 2);
1125        expect_eq!((header_word >> 32) & 0xffff, u64::from(META_CAT.label().id()));
1126        let name_id = (header_word >> 48) & 0xffff;
1127        expect_ne!(name_id, 0);
1128
1129        // Verify Timestamp (word 1)
1130        let timestamp_word = u64::from_ne_bytes(read_bytes[8..16].try_into().unwrap());
1131        expect_ne!(timestamp_word, 0);
1132
1133        // Verify KOIDs (words 2 & 3)
1134        let process_koid = u64::from_ne_bytes(read_bytes[16..24].try_into().unwrap());
1135        let thread_koid = u64::from_ne_bytes(read_bytes[24..32].try_into().unwrap());
1136        // SAFETY: Tests run after threading has been initialized.
1137        let FxtRef { pid: expected_proc, tid: expected_thread } =
1138            unsafe { ThreadPtr::current().fxt_ref() };
1139        expect_eq!(process_koid, expected_proc);
1140        expect_eq!(thread_koid, expected_thread);
1141
1142        // Verify Argument 1 ("arg1" => 42i32) (word 4)
1143        let arg1_header = u64::from_ne_bytes(read_bytes[32..40].try_into().unwrap());
1144        expect_eq!(arg1_header & 0xf, 1);
1145        expect_eq!((arg1_header >> 32) & 0xffffffff, 42);
1146        let arg1_name_id = (arg1_header >> 16) & 0xffff;
1147        expect_ne!(arg1_name_id, 0);
1148
1149        // Verify Argument 2 ("arg2" => "hello") (words 5 & 6)
1150        let arg2_header = u64::from_ne_bytes(read_bytes[40..48].try_into().unwrap());
1151        expect_eq!(arg2_header & 0xf, 6);
1152        expect_eq!((arg2_header >> 4) & 0xf, 1);
1153        expect_eq!((arg2_header >> 32) & 0xffffffff, 5);
1154        let arg2_name_id = (arg2_header >> 16) & 0xffff;
1155        expect_ne!(arg2_name_id, 0);
1156
1157        let arg2_val = &read_bytes[48..56];
1158        expect_true!(&arg2_val[0..5] == b"hello");
1159        expect_true!(&arg2_val[5..8] == &[0, 0, 0]);
1160    }
1161}
1162
1163//
1164// Zircon C++ kernel-tests FFI tests.
1165//
1166
1167/// Test-only FFI helper to write a single word record from Rust.
1168///
1169/// # Safety
1170///
1171/// This must be called with interrupts disabled.
1172#[cfg(ktest)]
1173#[unsafe(no_mangle)]
1174pub unsafe extern "C" fn rust_ktrace_test_interop(header: u64, val: u64) -> i32 {
1175    let ktrace = KTrace::get_instance();
1176    // SAFETY: The caller guarantees interrupts are disabled.
1177    if let Ok(mut res) = unsafe { ktrace.reserve(header) } {
1178        let _ = res.write_word(val);
1179        let _ = res.commit();
1180        0
1181    } else {
1182        -1
1183    }
1184}
1185
1186/// Test-only FFI helper to exercise all ktrace macros from Rust.
1187///
1188/// # Safety
1189///
1190/// This must be called with interrupts disabled and KTrace active.
1191#[cfg(ktest)]
1192#[unsafe(no_mangle)]
1193pub unsafe extern "C" fn rust_ktrace_test_macros() {
1194    // Exercise each macro. We'll write specific, distinguishable events.
1195    instant!("kernel:meta", "rust_instant", Context::Thread, "val" => 101u32);
1196    duration_begin!("kernel:meta", "rust_duration", Context::Thread, "val" => 102u32);
1197    duration_end!("kernel:meta", "rust_duration", Context::Thread, "val" => 103u32);
1198    counter!("kernel:meta", "rust_counter", 104u64, "val" => 105u32);
1199    flow_begin!("kernel:meta", "rust_flow", 106u64, "val" => 107u32);
1200    flow_step!("kernel:meta", "rust_flow", 106u64, "val" => 108u32);
1201    flow_end!("kernel:meta", "rust_flow", 106u64, "val" => 109u32);
1202    complete!("kernel:meta", "rust_complete", InstantBootTicks(110i64), "val" => 111u32);
1203    kernel_object!("kernel:meta", 112u64, 1u32, "rust_kernel_obj", "val" => 113u32);
1204    kernel_object_always!(114u64, 2u32, "rust_kernel_obj_always", "val" => 115u32);
1205}