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}