xref: /aosp_15_r20/external/crosvm/docs/book/src/tracing.md (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1# Tracing
2
3Crosvm supports tracing to allow developers to debug and diagnose problems and check performance
4optimizations.
5
6The crate `cros_tracing` is used as a frontend for trace points across the crosvm codebase. It is
7disabled by default but we can enable it with a compile-time flag. It is written to be extensible
8and support multiple backends.
9
10The currently supported backends are:
11
12- [`noop`](https://github.com/google/crosvm/blob/main/cros_tracing/src/noop.rs): No tracing is
13  enabled. All trace points are compiled out of the application so there is no performance
14  degradation. This is the default backend when no tracing flag is provided.
15- [`trace_marker`](https://crosvm.dev/doc/cros_tracing/trace_marker/index.html):
16  [ftrace](https://docs.kernel.org/trace/ftrace.html) backend to log trace events to the Linux
17  kernel. Only supported on Linux systems. Enabled by compiling crosvm with the
18  `--features trace_marker` flag. (On CrOS it is USE flag `crosvm-trace-marker`)
19
20## cros_tracing Overview
21
22The cros_tracing API consists of the following:
23
24- `cros_tracing::init()`: called at initialization time in `src/main.rs` to set up any
25  tracing-specific initialization logic (opening files, set up global state, etc).
26- `cros_tracing::push_descriptors!()`: a macro that needs to be called every time crosvm sets up a
27  sandbox jail before forking. It adds trace-specific file descriptors to the list of descriptors
28  allowed to be accessed inside the jail, if any.
29- `cros_tracing::trace_simple_print!()`: a simple macro that behaves like a log() print and sends a
30  simple message to the tracing backend. In case of the `trace_marker` backend, this will show up as
31  a message in the ftrace/print list of events.
32- `cros_tracing::trace_event_begin!()`: a macro that tracks a tracing context for the given category
33  and emits tracing events. It increased the counter of trace events for that context, if the
34  category is enabled.
35- `cros_tracing::trace_event_end!()`: the opposite of `trace_event_begin!()`. It decreases the
36  counter of currently traced events for that category, if the category is enabled.
37- `cros_tracing::trace_event!()`: a macro that returns a trace context. It records when it is first
38  executed and the given tag + state. When the returned structure goes out of scope, it is
39  automatically collected and the event is recorded. It is useful to trace entry and exit points in
40  function calls. It is equivalent to calling `trace_event_begin!()`, logging data, and then calling
41  `trace_event_end!()` before it goes out of scope. It's recommended to use `trace_event!()` rather
42  than call `trace_event_begin!()` and `trace_event_end!()` individually.
43
44The categories that are currently supported by cros_tracing are:
45
46- VirtioFs
47- VirtioNet
48- USB
49- gpu_display
50- VirtioBlk
51- VirtioScsi
52
53### The trace_marker Backend
54
55The `trace_marker` backend assumes that the host kernel has tracefs enabled and
56`/sys/kernel/tracing/trace_marker` is writable by the host when the crosvm process starts. If the
57file cannot be accessed, tracing will not work.
58
59### Usage
60
61First, we want to build crosvm with trace_marker enabled:
62
63```sh
64cargo build --features trace_marker
65```
66
67To verify that tracing is working, first start a trace capture on the host. You can use something
68like [trace-cmd](https://man7.org/linux/man-pages/man1/trace-cmd.1.html) or manually enable tracing
69in the system from the terminal:
70
71```sh
72sudo echo 1 > /sys/kernel/tracing/tracing_on
73```
74
75We can check that virtiofs tracing is working by launching crosvm with a virtiofs filesystem:
76
77```sh
78sudo crosvm run --disable-sandbox --shared-dir ${MOUNTPOINT}:mtdroot:type=fs -p "rootfstype=virtiofs root=mtdroot rw init=/bin/bash" ${KERNEL}
79```
80
81Where `${MOUNTPOINT}` is your virtiofs filesystem and `${KERNEL}` is your linux kernel.
82
83In another terminal, open a `cat` stream on the `/sys/kernel/tracing/trace_pipe` file to view the
84tracing events in real time:
85
86```sh
87sudo cat /sys/kernel/tracing/trace_pipe
88```
89
90As you issue virtiofs requests, you should see events showing up like:
91
92```
93<...>-3802142 [011] ..... 2179601.746212: tracing_mark_write: fuse server: handle_message: in_header=InHeader { len: 64, opcode: 18, unique: 814, nodeid: 42, uid: 0, gid: 0, pid: 0, padding: 0 }
94<...>-3802142 [011] ..... 2179601.746226: tracing_mark_write: 503 VirtioFs Enter: release - (self.tag: "mtdroot")(inode: 42)(handle: 35)
95<...>-3802142 [011] ..... 2179601.746244: tracing_mark_write: 503 VirtioFs Exit: release
96```
97
98### Adding Trace Points
99
100You can add you own trace points by changing the code and recompiling.
101
102If you just need to add a simple one-off trace point, you can use `trace_simple_print!()` like this
103(taken from `devices/src/virtio/fs/worker.rs`):
104
105```rust
106pub fn process_fs_queue<F: FileSystem + Sync>(
107    mem: &GuestMemory,
108    interrupt: &Interrupt,
109    queue: &mut Queue,
110    server: &Arc<fuse::Server<F>>,
111    tube: &Arc<Mutex<Tube>>,
112    slot: u32,
113) -> Result<()> {
114    // Added simple print here
115    cros_tracing::trace_simple_print!("Hello world.");
116    let mapper = Mapper::new(Arc::clone(tube), slot);
117    while let Some(avail_desc) = queue.pop(mem) {
118        let reader =
119            Reader::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
120        let writer =
121            Writer::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
122
123        let total = server.handle_message(reader, writer, &mapper)?;
124
125        queue.add_used(mem, avail_desc.index, total as u32);
126        queue.trigger_interrupt();
127    }
128```
129
130Recompile and you will see your message show up like:
131
132```
133<...>-3803691 [006] ..... 2180094.296405: tracing_mark_write: Hello world.
134```
135
136So far so good, but to get the most out of it you might want to record how long the function takes
137to run and some extra parameters. In that case you want to use `trace_event!()` instead:
138
139```rust
140pub fn process_fs_queue<F: FileSystem + Sync>(
141    mem: &GuestMemory,
142    interrupt: &Interrupt,
143    queue: &mut Queue,
144    server: &Arc<fuse::Server<F>>,
145    tube: &Arc<Mutex<Tube>>,
146    slot: u32,
147) -> Result<()> {
148    // Added trace event with slot
149    let _trace = cros_tracing::trace_event!(VirtioFs, "process_fs_queue", slot);
150    let mapper = Mapper::new(Arc::clone(tube), slot);
151    while let Some(avail_desc) = queue.pop(mem) {
152        let reader =
153            Reader::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
154        let writer =
155            Writer::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
156
157        let total = server.handle_message(reader, writer, &mapper)?;
158
159        queue.add_used(mem, avail_desc.index, total as u32);
160        queue.trigger_interrupt();
161    }
162```
163
164Recompile and this will show up:
165
166```
167<...>-3805264 [017] ..... 2180567.774540: tracing_mark_write: 512 VirtioFs Enter: process_fs_queue - (slot: 0)
168<...>-3805264 [017] ..... 2180567.774551: tracing_mark_write: 512 VirtioFs Exit: process_fs_queue
169```
170
171The number `512` in the log corresponds to a unique identifier for that event so it's easier to
172trace which `Enter` corresponds to which `Exit`. Note how the value of `slot` also has been
173recorded. To be able to output the state, the data type needs to support the `fmt::Debug` trait.
174
175NOTE: The unique identifier for each event is unique only per-process. If the crosvm process forks
176(like spawning new devices) then it is possible for two events from different processes to have the
177same ID, in which case it's important to look at the recorded PID that emitted each event in the
178trace.
179
180The numbers like `2180567.774540` and `2180567.774551` in the example above are the timestamps for
181that event, in `<sec>.<usec>` format. We can see that the `process_fs_queue` call took 11usec to
182execute.
183
184In this last example we used the `VirtioFs` category tag. If you want to add a new category tag to
185`trace_marker`, it can be done by adding it to the the `setup_trace_marker!()` call in
186`cros_tracing/src/trace_marker.rs`:
187
188```rust
189// List of categories that can be enabled.
190setup_trace_marker!(
191    (VirtioFs, true),
192    (VirtioNet, true),
193    (USB, true),
194    (gpu_display, true),
195    (VirtioBlk, true),
196    (VirtioScsi, true),
197    (NewCategory, true)
198);
199```
200
201If the value is `false` then the events will not be traced. This can be useful when you just want to
202trace a specific category and don't care about the rest, you can disable them in the code and
203recompile crosvm.
204
205NOTE: Trace events are compile-time to reduce runtime overhead in non-tracing builds so a lot of
206changes require recompiling and re-deploying crosvm.
207