cros_tracing/
trace_marker.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![deny(missing_docs)]
6
7use std::fs::File;
8use std::fs::OpenOptions;
9use std::io::Write;
10use std::os::unix::io::AsRawFd;
11use std::path::Path;
12
13use base::error;
14use base::RawDescriptor;
15use sync::Mutex;
16
17static TRACE_MARKER_FILE: Mutex<Option<File>> = Mutex::new(None);
18
19#[macro_export]
20/// This macro is used as a placeholder to let us iterate over the compile-time
21/// allocated vector of categories when we statically initialize it.
22/// This macro should only be used internally to this crate.
23macro_rules! zero_internal {
24    ($x:ident) => {
25        0
26    };
27}
28
29#[macro_export]
30/// This macro expands an expression with its value for easier printing.
31/// `expand_fmt_internal!(my_var)` becomes `"(my_var: {:?})"`.
32macro_rules! expand_fmt_internal {
33    ($x:expr) => {
34        std::concat!("(", std::stringify!($x), ": {:?})")
35    };
36}
37
38#[macro_export]
39/// Macro used to handle fd permanency across jailed devices.
40/// If we run crosvm without `--disable-sandbox`, we need to add the `trace_marker`
41/// file descriptor to the list of file descriptors allowed to be accessed by the
42/// sandboxed process. We call this macro to add the file descriptor to the list
43/// of `keep_rds` that the process is allowed to access every time we jail.
44macro_rules! push_descriptors {
45    ($fd_vec:expr) => {
46        $crate::push_descriptors_internal($fd_vec);
47    };
48}
49
50#[macro_export]
51/// Prints a single non-scoped message without creating a trace context.
52/// The tagged variant lets us enable or disable individual categories.
53macro_rules! trace_simple_print {
54    ($category: ident, $($t:tt)+) => {{
55        if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
56            $crate::trace_simple_print!($($t)*);
57        }
58    }};
59    ($($t:tt)*) => {{
60        $crate::trace_simple_print_internal(std::format!($($t)*));
61    }};
62}
63
64/// Platform-specific implementation of the `push_descriptors!` macro. If the
65/// `trace_marker` file has been initialized properly, it adds its file descriptor
66/// to the list of file descriptors that are allowed to be accessed when the process
67/// is jailed in the sandbox.
68///
69/// # Arguments
70///
71/// * `keep_rds` - List of file descriptors that will be accessible after jailing
72pub fn push_descriptors_internal(keep_rds: &mut Vec<RawDescriptor>) {
73    if let Some(file) = TRACE_MARKER_FILE.lock().as_ref() {
74        let fd = file.as_raw_fd();
75        if !keep_rds.contains(&fd) {
76            keep_rds.push(fd);
77        }
78    }
79}
80
81#[macro_export]
82/// Macro used to set up the trace environment categories. It takes a variable
83/// number of arguments in pairs of category, boolean value on whether or not the
84/// tracing category is enabled at compile time.
85///
86/// # Example usage
87///
88/// ```ignore
89/// setup_trace_marker!(
90///     (Category1, true),
91///     (Category2, false)
92/// );
93/// ```
94///
95/// Categories that are enabled will have their events traced at runtime via
96/// `trace_event_begin!()`, `trace_event_end!()`, or `trace_event!()` scoped tracing.
97/// The categories that are marked as false will have their events skipped.
98macro_rules! setup_trace_marker {
99 ($(($cat:ident, $enabled:literal)),+) => {
100     #[allow(non_camel_case_types, missing_docs)]
101     /// The tracing categories that the trace_marker backend supports.
102     pub enum TracedCategories {
103         $($cat,)+
104         /// Hacky way to get the count of how many tracing categories we have in total.
105         CATEGORY_COUNT,
106     }
107
108     /// Vector counting how many tracing event contexts are running for each category.
109     pub static CATEGORY_COUNTER: [std::sync::atomic::AtomicI64; TracedCategories::CATEGORY_COUNT as usize] = [
110         $(
111             // Note, we pass $cat to the zero_internal! macro here, which always just returns
112             // 0, because it's impossible to iterate over $cat unless $cat is used.
113             std::sync::atomic::AtomicI64::new(zero_internal!($cat)),
114         )+
115     ];
116
117     /// Vector used to test if a category is enabled or not for tracing.
118     pub static ENABLED_CATEGORIES: [std::sync::atomic::AtomicBool; TracedCategories::CATEGORY_COUNT as usize] = [
119         $(
120             std::sync::atomic::AtomicBool::new($enabled),
121         )+
122     ];
123
124     /// Sequential identifier for scoped trace events. This unique identifier is incremented
125     /// for each new `trace_event()` call.
126     pub static EVENT_COUNTER: std::sync::atomic::AtomicU64 =
127         std::sync::atomic::AtomicU64::new(0);
128 }
129}
130
131#[macro_export]
132/// Returns a Trace object with a new name and index and traces its enter state, if the
133/// given category identifier is enabled. Extra args can be provided for easier debugging.
134/// Upon exiting its scope, it is automatically collected and its exit gets traced.
135///
136/// If the category identifier is not enabled for this event, nothing happens and the trace
137/// event is skipped.
138///
139/// # Example usage
140///
141/// ```ignore
142/// {
143///    let _trace = trace_event!(Category, "exec", param1, param2);
144///
145///    // ... Rest of code ...
146///
147///    // End of `_trace`'s lifetime so `trace_event_end` is called.
148/// }
149/// ```
150///
151/// This will output in `trace_marker`:
152///
153///   - `$uid: $category Enter: exec - (param1: ...)(param2: ...)`
154///
155/// and when _trace runs out of scope, it will output:
156///
157///   - `$uid: Exit: exec`
158///
159/// where `$uid` will be the same unique value across those two events.
160macro_rules! trace_event {
161    ($category:ident, $name:literal, $($arg:expr),+) => {{
162        if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
163            $crate::trace_event_begin!($category);
164            let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
165            $crate::trace_simple_print!($category,
166                                        "{} {} Enter: {} - {}",
167                                        index,
168                                        std::stringify!($category),
169                                        $name,
170                                        // Creates a formatted list for each argument and their
171                                        // values.
172                                        std::format_args!(std::concat!($($crate::expand_fmt_internal!($arg),)*), $($arg),*));
173            Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))
174        } else {
175            None
176        }
177    }};
178    ($category:ident, $name:expr) => {{
179        if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
180            $crate::trace_event_begin!($category);
181            let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
182            $crate::trace_simple_print!($category,
183                                        "{} {} Enter: {}", index, std::stringify!($category), $name);
184            Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))
185        } else {
186            None
187        }
188    }};
189}
190
191#[macro_export]
192/// Begins a tracing event context in the given category, it increases the counter
193/// of the currently traced events for that category by one.
194///
195/// # Arguments
196///
197/// * `category` - Identifier name of the category.
198macro_rules! trace_event_begin {
199    ($category:ident) => {
200        $crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]
201            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
202    };
203}
204
205#[macro_export]
206/// Ends a tracing event context in the given category, it decreases the counter
207/// of the currently traced events for that category by one.
208///
209/// # Arguments
210///
211/// * `category` - Identifier name of the category.
212macro_rules! trace_event_end {
213    ($category:ident) => {
214        if ($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize]
215            .load(std::sync::atomic::Ordering::Relaxed))
216        {
217            $crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]
218                .fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
219        }
220    };
221    ($category_id:expr) => {
222        if ($crate::ENABLED_CATEGORIES[$category_id as usize]
223            .load(std::sync::atomic::Ordering::Relaxed))
224        {
225            $crate::CATEGORY_COUNTER[$category_id as usize]
226                .fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
227        }
228    };
229}
230
231// List of categories that can be enabled.
232// If a category is marked as disabled here, no events will be processed for it.
233setup_trace_marker!(
234    (VirtioFs, true),
235    (VirtioNet, true),
236    (USB, true),
237    (gpu_display, true),
238    (VirtioBlk, true),
239    (VirtioScsi, true)
240);
241
242/// Platform-specific implementation of the `trace_simple_print!` macro. If tracing
243/// is enabled on the system, it writes the given message to the `trace_marker` file.
244///
245/// # Arguments
246///
247/// * `message` - The message to be written
248pub fn trace_simple_print_internal(message: String) {
249    // In case tracing is not working or the trace marker file is None we can
250    // just ignore this. We don't need to handle the error here.
251    if let Some(file) = TRACE_MARKER_FILE.lock().as_mut() {
252        // We ignore the error here in case write!() fails, because the trace
253        // marker file would be normally closed by the system unless we are
254        // actively tracing the runtime. It is not an error.
255        let _ = write!(file, "{message}");
256    };
257}
258
259/// Initializes the trace_marker backend. It attepts to open the `trace_marker`
260/// file and keep a reference that can be shared throughout the lifetime of the
261/// crosvm process.
262///
263/// If tracefs is not available on the system or the file cannot be opened,
264/// tracing will not work but the crosvm process will still continue execution
265/// without tracing.
266pub fn init() {
267    let mut trace_marker_file = TRACE_MARKER_FILE.lock();
268    if trace_marker_file.is_some() {
269        return;
270    }
271
272    let path = Path::new("/sys/kernel/tracing/trace_marker");
273    let file = match OpenOptions::new().read(false).write(true).open(path) {
274        Ok(f) => f,
275        Err(e) => {
276            error!(
277                "Failed to open {}: {}. Tracing will not work.",
278                path.display(),
279                e
280            );
281            return;
282        }
283    };
284
285    *trace_marker_file = Some(file);
286}
287
288/// A trace context obtained from a `trace_event!()` call.
289pub struct Trace {
290    /// Unique identifier for the specific event.
291    identifier: u64,
292    /// Name of the trace event.
293    name: String,
294    /// Category name to which the event belongs.
295    category: String,
296    /// Category ID to which the event belongs.
297    category_id: usize,
298}
299
300impl Trace {
301    /// Returns a Trace object with the given name, id, and category
302    pub fn new(identifier: u64, name: &str, category: &str, category_id: usize) -> Self {
303        Trace {
304            identifier,
305            name: name.to_string(),
306            category: category.to_string(),
307            category_id,
308        }
309    }
310}
311
312impl Drop for Trace {
313    fn drop(&mut self) {
314        trace_simple_print!("{} {} Exit: {}", self.identifier, self.category, self.name);
315        trace_event_end!(self.category_id);
316    }
317}