Skip to main content

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}