libtracecmd/lib.rs
1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
16
17//! This library is a Rust wrapper of
18//! [libtracecmd](https://www.trace-cmd.org/Documentation/libtracecmd/), which allows writing
19//! programs to analyze Linux's [ftrace](https://docs.kernel.org/trace/ftrace.html) data
20//! generated by [trace-cmd](https://github.com/rostedt/trace-cmd).
21//!
22//! # Running a Sample Program
23//!
24//! To get familiar with using this library, you can start by running [a sample program](https://github.com/google/libtracecmd-rs/blob/main/examples/top_n_events.rs).
25//!
26//! ## Preliminary
27//!
28//! First, make sure that `CONFIG_FTRACE` and `CONFIG_FTRACE_SYSCALLS` are enabled in your Linux
29//! kernel.
30//! Then, install a `trace-cmd` binary and libraries to analyze trace data files. If you use
31//! Debian or Ubuntu, they should be installed with the following command:
32//!
33//! ```bash
34//! $ sudo apt install \
35//! trace-cmd \
36//! libtracefs-dev \
37//! libtraceevent-dev \
38//! libtracecmd-dev
39//! ```
40//!
41//! ## Get tracing record
42//!
43//! Run `trace-cmd record` along with your own workloads to record trace events.
44//!
45//! ```bash
46//! # Trace all syscalls called on your system during 10 seconds.
47//! $ trace-cmd record -e syscalls sleep 10
48//! # Then, you can run your own workload to be traced.
49//! ```
50//!
51//! Then, you'll find `trace.dat` in the current directory.
52//!
53//! ## Analyze `trace.dat` with a sample program
54//!
55//! Now, you can run
56//! [a sample code `top_n_events`](https://github.com/google/libtracecmd-rs/blob/main/examples/top_n_events.rs)
57//! to analyze the `trace.dat`.
58//!
59//! ```bash
60//! $ git clone git@github.com:google/libtracecmd-rs.git
61//! $ cd ./libtracecmd-rs
62//! $ cargo run --example top_n_events -- --input ./trace.dat --n 10 --prefix sys_enter_
63//! ```
64//!
65//! Then, you'll get output like the followings:
66//! ```text
67//! Top 10 events:
68//! #1: ioctl: 62424 times
69//! #2: futex: 59074 times
70//! #3: read: 30144 times
71//! #4: write: 28361 times
72//! #5: newfstatat: 22590 times
73//! #6: close: 15893 times
74//! #7: splice: 14650 times
75//! #8: getuid: 13579 times
76//! #9: epoll_pwait: 12298 times
77//! #10: ppoll: 10523 times
78//! ```
79//!
80//! # Writing your own code with the library
81//!
82//! See the documenation on [Handler].
83
84#[allow(
85 clippy::upper_case_acronyms,
86 clippy::useless_transmute,
87 non_upper_case_globals,
88 non_camel_case_types,
89 non_snake_case,
90 dead_code
91)]
92mod bindings {
93 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
94}
95
96use thiserror::Error;
97
98/// Errors that can happen while processing tracing data.
99#[derive(Error, Debug)]
100pub enum Error {
101 /// Failed to open .dat file
102 #[error("failed to open .dat file")]
103 Open,
104 /// Failed to get `tep_handle`
105 #[error("failed to get tep_handle")]
106 Handle,
107 /// Failed to find `tep_handle`
108 #[error("failed to find tep_event")]
109 FindEvent,
110 /// Failed to find `tep_field`
111 #[error("failed to find tep_field")]
112 FindField,
113 /// Invalid PID
114 #[error("invalid PID: {0}")]
115 InvalidPid(String),
116 /// Invalid timestamp
117 #[error("invalid timestamp: {0}")]
118 InvalidTimestamp(String),
119 /// Invalid string
120 #[error("invalid string: {0}")]
121 InvalidString(std::str::Utf8Error),
122 /// Failed to read a field
123 #[error("failed to read a field")]
124 ReadField,
125}
126
127type Result<T> = std::result::Result<T, Error>;
128
129unsafe fn cptr_to_string(ptr: *mut i8) -> Result<String> {
130 let c_str: &std::ffi::CStr = unsafe { std::ffi::CStr::from_ptr(ptr) };
131 Ok(c_str.to_str().map_err(Error::InvalidString)?.to_string())
132}
133
134/// A wrapper of `tracecmd_input` represnting a `trace.dat` file given as the input.
135pub struct Input(*mut bindings::tracecmd_input);
136
137impl Input {
138 /// Opens a given `trace.dat` file and create `Input`.
139 pub fn new(path: &str) -> Result<Self> {
140 // TODO: Support open flags.
141 let handle = unsafe { bindings::tracecmd_open(path.as_ptr() as *mut i8, 0) };
142 if handle.is_null() {
143 return Err(Error::Open);
144 }
145
146 Ok(Input(handle))
147 }
148
149 /// Gets `Handle` from the `Input`.
150 pub fn handle_ref(&self) -> Result<HandleRef> {
151 let ret = unsafe { bindings::tracecmd_get_tep(self.0) };
152 if ret.is_null() {
153 Err(Error::Handle)
154 } else {
155 Ok(HandleRef(ret))
156 }
157 }
158
159 /// Gets an `Event` corresponding to a given `rec`.
160 pub fn find_event(&self, rec: &Record) -> Result<Event> {
161 let handle = self.handle_ref()?;
162 let ptr = unsafe { bindings::tep_find_event_by_record(handle.0, rec.0) };
163 if ptr.is_null() {
164 return Err(Error::FindEvent);
165 }
166 let name = unsafe { cptr_to_string((*ptr).name) }.expect("string");
167
168 Ok(Event { ptr, name })
169 }
170}
171
172impl Drop for Input {
173 fn drop(&mut self) {
174 // Safe because `self.0` must be a valid pointer.
175 unsafe {
176 bindings::tracecmd_close(self.0);
177 }
178 }
179}
180
181/// A wrapper of
182/// [`tep_handle`](https://www.trace-cmd.org/Documentation/libtraceevent/libtraceevent-handle.html),
183/// the main structure representing the trace event parser context.
184pub struct HandleRef(*mut bindings::tep_handle);
185
186impl HandleRef {
187 /// Gets a PID.
188 pub fn pid(&self, rec: &Record) -> i32 {
189 unsafe { bindings::tep_data_pid(self.0, rec.0) }
190 }
191}
192
193/// A wrapper of `tep_record`.
194pub struct Record(*mut bindings::tep_record);
195
196impl Record {
197 /// Gets a timestamp.
198 pub fn ts(&self) -> u64 {
199 unsafe { *self.0 }.ts
200 }
201}
202
203/// A wrapper of `tep_event`.
204pub struct Event {
205 ptr: *mut bindings::tep_event,
206 /// Name of the event.
207 pub name: String,
208}
209
210impl Event {
211 /// Prints each field name followed by the record’s field value according to the field’s type.
212 ///
213 /// This is a wrapper of
214 /// [tep_record_print_fields](https://www.trace-cmd.org/Documentation/libtraceevent/libtraceevent-field_print.html).
215 pub fn print_fields(&self, rec: &Record) {
216 println!("fields: {:?}", self.get_fields(rec));
217 }
218
219 /// Gets each field name follwed by the record's field value according to the field's type.
220 ///
221 /// This is a wrapper of
222 /// [tep_record_print_fields](https://www.trace-cmd.org/Documentation/libtraceevent/libtraceevent-field_print.html).
223 pub fn get_fields(&self, rec: &Record) -> String {
224 let mut seq: bindings::trace_seq = Default::default();
225 unsafe {
226 bindings::trace_seq_init(&mut seq);
227 bindings::trace_seq_reset(&mut seq);
228
229 bindings::tep_record_print_fields(&mut seq, rec.0, self.ptr);
230 bindings::trace_seq_terminate(&mut seq);
231 };
232 let msg = unsafe { std::slice::from_raw_parts(seq.buffer as *mut u8, seq.len as usize) };
233 std::str::from_utf8(msg).unwrap().to_string()
234 }
235}
236
237/// A trait to iterate over trace events and process them one by one.
238///
239/// When you use this trait, you need to implement [Handler::callback] and [Handler::AccumulatedData].
240/// Then, you can call [Handler::process] or [Handler::process_multi] to process the given `trace.dat`.
241/// When [Handler::process] is called, the defined `callback` is called for each events one by one. The last
242/// argument of the `callback` is `&mut Self::AccumulatedData`.
243///
244/// # Example
245///
246/// ```no_run
247/// use libtracecmd::Event;
248/// use libtracecmd::Handler;
249/// use libtracecmd::Input;
250/// use libtracecmd::Record;
251///
252/// #[derive(Default)]
253/// struct MyData {
254/// // fields to accumulate data.
255/// }
256///
257/// impl MyData {
258/// fn print_results(&self) {
259/// // Print accumulated data.
260/// }
261/// }
262///
263/// struct MyStats;
264///
265/// impl Handler for MyStats {
266/// type AccumulatedData = MyData;
267///
268/// fn callback(input: &mut Input, rec: &mut Record, cpu: i32, data: &mut Self::AccumulatedData) -> i32 {
269/// // Write your own logic to analyze `rec` and update `data`.
270/// 0
271/// }
272/// }
273///
274///
275/// let mut input: Input = Input::new("trace.dat").unwrap();
276/// let stats: MyData = MyStats::process(&mut input).unwrap();
277/// stats.print_results();
278/// ```
279///
280/// You can find sample programs in [`/examples/`](https://github.com/google/libtracecmd-rs/tree/main/examples).
281pub trait Handler {
282 /// Type of data passed around among every call of [Self::callback].
283 type AccumulatedData: Default;
284
285 /// A callback that will be called for all events when [Self::process] or [Self::process_multi] is called.
286 fn callback(
287 input: &mut Input,
288 rec: &mut Record,
289 cpu: i32,
290 data: &mut Self::AccumulatedData,
291 ) -> i32;
292
293 /// Processes the given `input` by calling [Self::callback] for each event and returns
294 /// [Self::AccumulatedData] returned by the last call of [Self::callback].
295 ///
296 /// This is a wrapper of [`tracecmd_iterate_events`](https://www.trace-cmd.org/Documentation/libtracecmd/libtracecmd-iterate.html).
297 fn process(input: &mut Input) -> std::result::Result<Self::AccumulatedData, i32> {
298 let mut data: Self::AccumulatedData = Default::default();
299
300 let ret = unsafe {
301 bindings::tracecmd_iterate_events(
302 input.0,
303 // If `cpus` is null, `cpus` and `cpu_size` are ignored and all of CPUs will be
304 // checked.
305 std::ptr::null_mut(), /* cpus */
306 0, /* cpu_size */
307 Some(c_callback::<Self>),
308 &mut data as *mut _ as *mut std::ffi::c_void,
309 )
310 };
311 if ret == 0 {
312 Ok(data)
313 } else {
314 Err(ret)
315 }
316 }
317
318 /// Similar to [Self::process], but can take multiple inputs.
319 ///
320 /// This is useful when you have synchronized multiple trace.dat created by `trace-cmd agent`.
321 /// This is a wrapper of [`tracecmd_iterate_events`](https://www.trace-cmd.org/Documentation/libtracecmd/libtracecmd-iterate.html).
322 fn process_multi(inputs: &mut [Input]) -> std::result::Result<Self::AccumulatedData, i32> {
323 let mut data: Self::AccumulatedData = Default::default();
324 let nr_handles = inputs.len() as i32;
325
326 let mut handles = inputs.iter().map(|input| input.0).collect::<Vec<_>>();
327
328 let ret = unsafe {
329 bindings::tracecmd_iterate_events_multi(
330 handles.as_mut_ptr(),
331 nr_handles,
332 Some(c_callback::<Self>),
333 &mut data as *mut _ as *mut std::ffi::c_void,
334 )
335 };
336 if ret == 0 {
337 Ok(data)
338 } else {
339 Err(ret)
340 }
341 }
342}
343
344unsafe extern "C" fn c_callback<T: Handler + ?Sized>(
345 input: *mut bindings::tracecmd_input,
346 rec: *mut bindings::tep_record,
347 cpu: i32,
348 raw_data: *mut std::ffi::c_void,
349) -> i32 {
350 let mut input = Input(input);
351 let mut rec = Record(rec);
352
353 // TODO: Remove this unnecessary data copy?
354 // What I only need here is a type conversion.
355 let mut data: T::AccumulatedData = Default::default();
356 std::ptr::copy_nonoverlapping(
357 raw_data,
358 &mut data as *mut _ as *mut std::ffi::c_void,
359 std::mem::size_of::<T::AccumulatedData>(),
360 );
361 let res = T::callback(&mut input, &mut rec, cpu, &mut data);
362 std::ptr::copy_nonoverlapping(
363 &mut data as *mut _ as *mut std::ffi::c_void,
364 raw_data,
365 std::mem::size_of::<T::AccumulatedData>(),
366 );
367
368 std::mem::forget(input);
369 std::mem::forget(data);
370
371 res
372}