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