Skip to main content

tracing_perfetto_sdk_sys/
lib.rs

1//! # `tracing-perfetto-sdk-sys`: C++ bindings to the raw Perfetto SDK
2//!
3//! This crate only contains low-level bindings to the C++ library.  While the
4//! interface is safe, it is recommended to use a higher level API, for example
5//! via the `tracing-perfetto-sdk-layer` crate.
6#![deny(clippy::all)]
7
8use std::sync::mpsc;
9
10#[cfg(feature = "async")]
11use futures::channel::oneshot;
12
13/// FFI bridge: Definitions of functions and types that are shared across the
14/// C++ boundary.
15#[cxx::bridge]
16pub mod ffi {
17    pub enum LogLev {
18        Debug = 0,
19        Info = 1,
20        Important = 2,
21        Error = 3,
22    }
23
24    pub struct DebugStringAnnotation {
25        pub key: &'static str,
26        pub value: String,
27    }
28
29    pub struct DebugBoolAnnotation {
30        pub key: &'static str,
31        pub value: bool,
32    }
33
34    pub struct DebugIntAnnotation {
35        pub key: &'static str,
36        pub value: i64,
37    }
38
39    pub struct DebugDoubleAnnotation {
40        pub key: &'static str,
41        pub value: f64,
42    }
43
44    pub struct DebugAnnotations<'a> {
45        pub strings: &'a [DebugStringAnnotation],
46        pub bools: &'a [DebugBoolAnnotation],
47        pub ints: &'a [DebugIntAnnotation],
48        pub doubles: &'a [DebugDoubleAnnotation],
49    }
50
51    extern "Rust" {
52        // Opaque type passed to C++ code to be sent back during callbacks; essentially
53        // like `void *context` but type-safe
54        type PollTracesCtx;
55        type FlushCtx;
56    }
57
58    unsafe extern "C++" {
59        include!("src/perfetto-bindings.h");
60
61        /// Initialize the global tracing infrastructure.
62        ///
63        /// Must be called once before all other functions in this module.
64        fn perfetto_global_init(
65            log_callback: fn(level: LogLev, line: i32, filename: &str, message: &str),
66            name: &str,
67            enable_in_process_backend: bool,
68            enable_system_backend: bool,
69        );
70
71        /// The native C++ class that is safe to be passed across the C++/Rust
72        /// boundary via a `unique_ptr`.
73        type PerfettoTracingSession;
74
75        /// Create a new tracing session using the provided Protobuf-encoded
76        /// TraceConfig.
77        ///
78        /// If `output_fd` is non-negative, we will write traces directly to the
79        /// provided file descriptor, in which case `poll_traces` will never
80        /// return any data.
81        fn new_tracing_session(
82            trace_config_bytes: &[u8],
83            output_fd: i32,
84        ) -> Result<UniquePtr<PerfettoTracingSession>>;
85
86        /// Create a trace event signifying that a new slice (aka span) begins.
87        ///
88        /// Make sure to call this in the hot path instead of delaying the call
89        /// to some background queue, etc., as the call will collect additional
90        /// timing data at the time of the actual call.
91        ///
92        /// This call is very cheap and will be a no-op if trace collection is
93        /// not yet started/enabled.
94        fn trace_track_event_slice_begin<'a>(
95            track_uuid: u64,
96            name: &str,
97            location_file: &str,
98            location_line: u32,
99            debug_annotations: &DebugAnnotations<'a>,
100        );
101
102        /// Create a trace event signifying that a slice (aka span) ends.
103        ///
104        /// Make sure to call this in the hot path instead of delaying the call
105        /// to some background queue, etc., as the call will collect additional
106        /// timing data at the time of the actual call.
107        ///
108        /// This call is very cheap and will be a no-op if trace collection is
109        /// not yet started/enabled.
110        fn trace_track_event_slice_end(
111            track_uuid: u64,
112            name: &str,
113            location_file: &str,
114            location_line: u32,
115        );
116
117        /// Create a trace event signifying that an instant event has happened,
118        /// similar to a log message or something else that takes ~zero time.
119        ///
120        /// Make sure to call this in the hot path instead of delaying the call
121        /// to some background queue, etc., as the call will collect additional
122        /// timing data at the time of the actual call.
123        ///
124        /// This call is very cheap and will be a no-op if trace collection is
125        /// not yet started/enabled.
126        fn trace_track_event_instant<'a>(
127            track_uuid: u64,
128            name: &str,
129            location_file: &str,
130            location_line: u32,
131            debug_annotations: &DebugAnnotations<'a>,
132        );
133
134        /// Create a track descriptor for a process.
135        ///
136        /// Make sure to call this in the hot path instead of delaying the call
137        /// to some background queue, etc., as the call will collect additional
138        /// timing data at the time of the actual call.
139        ///
140        /// This call is very cheap and will be a no-op if trace collection is
141        /// not yet started/enabled.
142        fn trace_track_descriptor_process(
143            parent_uuid: u64,
144            track_uuid: u64,
145            process_name: &str,
146            process_pid: u32,
147        );
148
149        /// Create a track descriptor for a thread.
150        ///
151        /// Make sure to call this in the hot path instead of delaying the call
152        /// to some background queue, etc., as the call will collect additional
153        /// timing data at the time of the actual call.
154        ///
155        /// This call is very cheap and will be a no-op if trace collection is
156        /// not yet started/enabled.
157        fn trace_track_descriptor_thread(
158            parent_uuid: u64,
159            track_uuid: u64,
160            process_pid: u32,
161            thread_name: &str,
162            thread_tid: u32,
163        );
164
165        /// Get the current trace time according to Perfetto's managed monotonic
166        /// clock(s).
167        fn trace_time_ns() -> u64;
168
169        /// Get the id of the clock that is used by `trace_time_ns`.
170        ///
171        /// Valid values correspond to enum variants of the `BuiltinClock`
172        /// protobuf enum.
173        fn trace_clock_id() -> u32;
174
175        /// Start collecting traces from all data sources.
176        fn start(self: Pin<&mut PerfettoTracingSession>);
177
178        /// Stop collecting traces from all data sources.
179        fn stop(self: Pin<&mut PerfettoTracingSession>);
180
181        /// Flush buffered traces.
182        ///
183        /// The passed-in callback is called with `true` on success; `false`
184        /// indicates that some data source didn't ack before the
185        /// timeout, or because something else went wrong (e.g. tracing
186        /// system wasn't initialized).
187        fn flush(
188            self: Pin<&mut PerfettoTracingSession>,
189            timeout_ms: u32,
190            ctx: Box<FlushCtx>,
191            done: fn(ctx: Box<FlushCtx>, success: bool),
192        );
193
194        /// Poll for new traces, and call the provided `done` callback with new
195        /// trace records.
196        ///
197        /// Polling will only return data if an `output_fd` was *not* specified
198        /// when creating the session.
199        ///
200        /// If there are no more records, the callback will be called with an
201        /// empty slice and `has_more == true` as a special case to signal EOF.
202        ///
203        /// The data in the callback buffer is guaranteed to consist of complete
204        /// trace records; in other words, there will not be any partial records
205        /// that cross buffer boundaries.
206        fn poll_traces(
207            self: Pin<&mut PerfettoTracingSession>,
208            ctx: Box<PollTracesCtx>,
209            done: fn(ctx: Box<PollTracesCtx>, data: &[u8], has_more: bool),
210        );
211    }
212}
213
214// Safe to use session from all threads
215unsafe impl Send for ffi::PerfettoTracingSession {}
216unsafe impl Sync for ffi::PerfettoTracingSession {}
217
218/// A context that will be passed-in in a call to [`ffi::poll_traces`] and later
219/// passed back in the `done` callback when the async operation has completed.
220pub struct PollTracesCtx(CallbackSender<PolledTraces>);
221
222/// A context that will be passed-in in a call to [`ffi::flush`] and later
223/// passed back in the `done` callback when the async operation has completed.
224pub struct FlushCtx(CallbackSender<bool>);
225
226/// Traces returned from `poll_traces`/the channel returned by
227/// `PollTracesCtx::new`.
228pub struct PolledTraces {
229    pub data: bytes::BytesMut,
230    pub has_more: bool,
231}
232
233enum CallbackSender<A> {
234    Sync(mpsc::Sender<A>),
235    #[cfg(feature = "async")]
236    Async(Option<oneshot::Sender<A>>),
237}
238
239/// A context to be passed into `poll_traces`.
240///
241/// Intended to be called like:
242///
243/// ```no_run
244/// # use std::pin::Pin;
245/// # use tracing_perfetto_sdk_sys::ffi::PerfettoTracingSession;
246/// # use tracing_perfetto_sdk_sys::{PollTracesCtx, PolledTraces};
247/// let session: Pin<&mut PerfettoTracingSession> = todo!();
248/// let (ctx, rx) = PollTracesCtx::new_sync();
249/// session.poll_traces(Box::new(ctx), PollTracesCtx::callback);
250/// let polled_traces: PolledTraces = rx.recv().unwrap(); // Should return polled traces
251/// ```
252impl PollTracesCtx {
253    pub fn new_sync() -> (Self, mpsc::Receiver<PolledTraces>) {
254        let (tx, rx) = mpsc::channel();
255        let tx = CallbackSender::Sync(tx);
256        (Self(tx), rx)
257    }
258
259    #[cfg(feature = "async")]
260    pub fn new_async() -> (Self, oneshot::Receiver<PolledTraces>) {
261        let (tx, rx) = oneshot::channel();
262        let tx = CallbackSender::Async(Some(tx));
263        (Self(tx), rx)
264    }
265
266    #[allow(clippy::boxed_local)]
267    pub fn callback(mut self: Box<Self>, data: &[u8], has_more: bool) {
268        let data = bytes::BytesMut::from(data);
269        self.0.send(PolledTraces { data, has_more });
270    }
271}
272
273/// A context to be passed into `flush`.
274///
275/// Intended to be called like:
276///
277/// ```no_run
278/// # use std::pin::Pin;
279/// # use tracing_perfetto_sdk_sys::ffi::PerfettoTracingSession;
280/// # use tracing_perfetto_sdk_sys::FlushCtx;
281/// let session: Pin<&mut PerfettoTracingSession> = todo!();
282/// let (ctx, rx) = FlushCtx::new_sync();
283/// let timeout_ms = 100;
284/// session.flush(timeout_ms, Box::new(ctx), FlushCtx::callback);
285/// let success: bool = rx.recv().unwrap();
286/// ```
287impl FlushCtx {
288    pub fn new_sync() -> (Self, mpsc::Receiver<bool>) {
289        let (tx, rx) = mpsc::channel();
290        let tx = CallbackSender::Sync(tx);
291        (Self(tx), rx)
292    }
293
294    #[cfg(feature = "async")]
295    pub fn new_async() -> (Self, oneshot::Receiver<bool>) {
296        let (tx, rx) = oneshot::channel();
297        let tx = CallbackSender::Async(Some(tx));
298        (Self(tx), rx)
299    }
300
301    #[allow(clippy::boxed_local)]
302    pub fn callback(mut self: Box<Self>, success: bool) {
303        self.0.send(success);
304    }
305}
306
307impl<A> CallbackSender<A> {
308    fn send(&mut self, value: A) {
309        match self {
310            CallbackSender::Sync(tx) => {
311                let _ = tx.send(value);
312            }
313            #[cfg(feature = "async")]
314            CallbackSender::Async(tx) => {
315                if let Some(tx) = tx.take() {
316                    let _ = tx.send(value);
317                }
318            }
319        }
320    }
321}