starnix_core/task/syscall_trace.rs
1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Tracks the `CATEGORY_STARNIX` duration of the syscall running on the current thread, so that
6//! code inside the syscall can end that duration early.
7//!
8//! `TraceMarkerFile::write` uses this to end the syscall's slice before it records an
9//! `ftrace/print` (`atrace`) event. Otherwise the syscall slice ends after the `atrace` event on
10//! the same thread track, and Perfetto's strict slice nesting truncates the `atrace` slice. Linux
11//! `ftrace` traces avoid this the same way, in Perfetto's
12//! `SyscallTracker::MaybeTruncateOngoingWriteSlice`.
13
14use fuchsia_trace::{TraceCategoryContext, trace_site_t};
15use starnix_logging::CATEGORY_STARNIX;
16use std::cell::Cell;
17
18thread_local! {
19 /// The name and start time of the syscall duration that is open on the current thread, if any.
20 ///
21 /// This is a `const`-initialized thread-local without a destructor, so accessing it is cheap.
22 /// A `CurrentTask` runs syscalls only on its own thread, so this holds the state of that task's
23 /// current syscall.
24 static SYSCALL_TRACE: Cell<Option<(&'static str, zx::BootTicks)>> = const { Cell::new(None) };
25}
26
27/// Ends the syscall duration started by [`begin_syscall_trace`] when dropped, unless
28/// [`complete_syscall_trace`] already ended it.
29#[must_use = "the syscall duration ends when the scope is dropped"]
30pub struct SyscallTraceScope(());
31
32impl Drop for SyscallTraceScope {
33 fn drop(&mut self) {
34 complete_syscall_trace();
35 }
36}
37
38/// Starts a `CATEGORY_STARNIX` duration for the syscall about to run on the current thread.
39///
40/// Returns `None`, without calling `name`, if the category is disabled. Otherwise the duration
41/// ends when the returned scope is dropped, or earlier at a call to [`complete_syscall_trace`].
42#[inline]
43pub fn begin_syscall_trace(name: impl FnOnce() -> &'static str) -> Option<SyscallTraceScope> {
44 static CACHE: trace_site_t = trace_site_t::new(0);
45 // The context is dropped right away. `complete_syscall_trace` acquires it again, so the trace
46 // buffers can be flushed while the syscall runs, as with `fuchsia_trace::duration!`.
47 TraceCategoryContext::acquire_cached(CATEGORY_STARNIX, &CACHE)?;
48 SYSCALL_TRACE.set(Some((name(), zx::BootTicks::get())));
49 Some(SyscallTraceScope(()))
50}
51
52/// Ends the syscall duration that is open on the current thread, if any, and writes it to the
53/// trace.
54///
55/// Called early by `TraceMarkerFile::write`, right before it records an `ftrace/print` event, so
56/// that the syscall slice ends at or before the event's timestamp.
57#[inline]
58pub fn complete_syscall_trace() {
59 if let Some((name, start_time)) = SYSCALL_TRACE.take() {
60 fuchsia_trace::complete_duration(CATEGORY_STARNIX, name, start_time, &[]);
61 }
62}
63
64/// Opens a syscall duration on the current thread even if `CATEGORY_STARNIX` is disabled, so
65/// tests can check that code ends it. Drop the returned scope to clear it.
66pub fn begin_syscall_trace_for_testing(name: &'static str) -> SyscallTraceScope {
67 SYSCALL_TRACE.set(Some((name, zx::BootTicks::get())));
68 SyscallTraceScope(())
69}
70
71/// Returns whether a syscall duration is open on the current thread.
72pub fn has_active_syscall_trace_for_testing() -> bool {
73 SYSCALL_TRACE.with(|trace| trace.get().is_some())
74}