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}