1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 mod bindings;
6 mod hashes;
7
8 pub mod protos {
9 include!(concat!(env!("OUT_DIR"), "/perfetto_protos/generated.rs"));
10 }
11
12 use std::ffi::c_void;
13 use std::ffi::CString;
14 use std::mem::size_of;
15 use std::path::Path;
16 use std::slice;
17 use std::time::Duration;
18
19 pub use bindings::*;
20 pub use cros_tracing_types::static_strings::StaticString;
21 use cros_tracing_types::TraceDuration;
22 use protobuf::Message;
23 use protos::perfetto_config::trace_config::BufferConfig;
24 use protos::perfetto_config::trace_config::DataSource;
25 use protos::perfetto_config::trace_config::IncrementalStateConfig;
26 use protos::perfetto_config::DataSourceConfig;
27 use protos::perfetto_config::TraceConfig;
28 use protos::perfetto_config::TrackEventConfig;
29 use zerocopy::AsBytes;
30 use zerocopy::FromBytes;
31 use zerocopy::FromZeroes;
32
33 /// Randomly generated GUID to help locate the AOT header.
34 const HEADER_MAGIC: &[u8; 16] = b"\x8d\x10\xa3\xee\x79\x1f\x47\x25\xb2\xb8\xb8\x9f\x85\xe7\xd6\x7c";
35
36 /// The optional header written ahead of the trace data.
37 #[repr(C)]
38 #[derive(Copy, Clone, AsBytes, FromZeroes, FromBytes)]
39 struct TraceHeader {
40 magic: [u8; 16],
41 data_size: u64,
42 data_checksum_sha256: [u8; 32],
43 }
44
45 #[macro_export]
46 macro_rules! zero {
47 ($x:ident) => {
48 0
49 };
50 }
51
52 /// Helper macro for perfetto_tags
53 #[macro_export]
54 macro_rules! tag_or_empty_string {
55 () => {
56 "\0".as_ptr() as *const std::ffi::c_char
57 };
58 ($tag:expr) => {
59 concat!($tag, "\0").as_ptr() as *const std::ffi::c_char
60 };
61 }
62
63 /// Macro for creating an array of const char * for perfetto tags.
64 #[macro_export]
65 macro_rules! perfetto_tags {
66 () => {
67 [
68 tag_or_empty_string!(),
69 tag_or_empty_string!(),
70 tag_or_empty_string!(),
71 tag_or_empty_string!(),
72 ]
73 };
74 ($tag0:expr) => {
75 [
76 tag_or_empty_string!($tag0),
77 tag_or_empty_string!(),
78 tag_or_empty_string!(),
79 tag_or_empty_string!(),
80 ]
81 };
82 ($tag0:expr, $tag1:expr) => {
83 [
84 tag_or_empty_string!($tag0),
85 tag_or_empty_string!($tag1),
86 tag_or_empty_string!(),
87 tag_or_empty_string!(),
88 ]
89 };
90 ($tag0:expr, $tag1:expr, $tag2:expr) => {
91 [
92 tag_or_empty_string!($tag0),
93 tag_or_empty_string!($tag1),
94 tag_or_empty_string!($tag2),
95 tag_or_empty_string!(),
96 ]
97 };
98 ($tag0:expr, $tag1:expr, $tag2:expr, $tag3:expr) => {
99 [
100 tag_or_empty_string!($tag0),
101 tag_or_empty_string!($tag1),
102 tag_or_empty_string!($tag2),
103 tag_or_empty_string!($tag3),
104 ]
105 };
106 }
107
108 /// Main macro to be called by any crate wanting to use perfetto tracing. It
109 /// should be called once in your crate outside of any function.
110 ///
111 /// # Arguments
112 /// * `module_path` - is the module path where this
113 /// * The remaining arguments are an arbitrary list of triples that describe the tracing
114 /// categories. They are supplied flattened (e.g. ((a, b, c), (d, e, f)) => (a, b, c, d, e, f).
115 /// Each triple contains:
116 /// - the category name (this is the same name/ident that will be passed to trace point
117 /// macros).
118 /// - a free form text description of the category.
119 /// - the tag set for this category (generated by calling perfetto_tags).
120 ///
121 /// # Examples
122 /// ```no_run
123 /// setup_perfetto!(
124 /// tracing,
125 /// mycrate,
126 /// "General trace points for my crate",
127 /// perfetto_tags!(),
128 /// debug,
129 /// "Debug trace points",
130 /// perfetto_tags!("debug"))
131 /// ```
132 #[macro_export]
133 macro_rules! setup_perfetto {
134 ($mod:ident, $($cat:ident, $description:expr, $tags:expr),+) => {
135 #[allow(non_camel_case_types)]
136 #[derive(Copy, Clone)]
137 pub enum PerfettoCategory {
138 $($cat,)+
139 // Hacky way to get the count of the perfetto categories.
140 CATEGORY_COUNT,
141 }
142
143 /// Const array of perfetto categories that will be passed to perfetto api.
144 pub const CATEGORIES: [&ctrace_category; PerfettoCategory::CATEGORY_COUNT as usize] = [
145 $(
146 &ctrace_category {
147 client_index: PerfettoCategory::$cat as u64,
148 instances_callback: Some(instances_callback),
149 name: concat!(stringify!($cat), "\0").as_ptr() as *const std::ffi::c_char,
150 description: concat!($description, "\0").as_ptr() as *const std::ffi::c_char,
151 tags: $tags
152 },
153 )+
154 ];
155
156 /// Base offset into the global list of categories where our categories live.
157 pub static PERFETTO_CATEGORY_BASE: std::sync::atomic::AtomicU64 =
158 std::sync::atomic::AtomicU64::new(0);
159
160 /// Active trace instance bitmaps for each of our categories. We use a u32 because the
161 /// cperfetto API uses a u32, but really only 8 traces can be active at a time.
162 pub static PERFETTO_CATEGORY_INSTANCES:
163 [std::sync::atomic::AtomicU32; PerfettoCategory::CATEGORY_COUNT as usize] = [
164 $(
165 // Note, we pass $cat to the zero! macro here, which always just returns
166 // 0, because it's impossible to iterate over $cat unless $cat is used.
167 std::sync::atomic::AtomicU32::new($crate::zero!($cat)),
168 )+
169 ];
170
171 /// Register the perfetto categories defined by this macro with the perfetto shared
172 /// library. This should be called once at process startup.
173 pub fn register_categories() {
174 PERFETTO_CATEGORY_BASE.store(
175 unsafe {
176 ctrace_register_categories(
177 CATEGORIES.as_ptr() as *const *const ctrace_category,
178 CATEGORIES.len() as u64,
179 )
180 },
181 std::sync::atomic::Ordering::SeqCst,
182 );
183 }
184
185 /// Callback from the perfetto shared library when the set of active trace instances for
186 /// a given category has changed. Index is the client index of one of our registered
187 /// categories.
188 extern "C" fn instances_callback(instances: u32, index: u64) {
189 PERFETTO_CATEGORY_INSTANCES[index as usize].store(
190 instances, std::sync::atomic::Ordering::SeqCst);
191
192 for cb in PERFETTO_PER_TRACE_CALLBACKS.lock().iter() {
193 cb();
194 }
195 }
196
197
198 static PERFETTO_PER_TRACE_CALLBACKS: sync::Mutex<Vec<fn()>> = sync::Mutex::new(Vec::new());
199
200
201 pub fn add_per_trace_callback(callback: fn()) {
202 PERFETTO_PER_TRACE_CALLBACKS.lock().push(callback);
203 }
204
205 /// Create and return a scoped named trace event, which will start at construction and end when
206 /// the event goes out of scope and is dropped. Will return None if tracing is disabled for
207 /// this category.
208 ///
209 /// # Examples
210 /// ```no_run
211 /// {
212 /// let _trace = trace_event!(my_category, "trace_point_name");
213 /// do_some_work();
214 /// } // _trace dropped here & records the span.
215 /// ```
216 #[macro_export]
217 macro_rules! trace_event {
218 ($category:ident, $name:literal) => {
219 {
220 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
221 [$mod::PerfettoCategory::$category as usize]
222 .load(std::sync::atomic::Ordering::SeqCst);
223
224 if instances != 0 {
225 let category_index = $mod::PERFETTO_CATEGORY_BASE
226 .load(std::sync::atomic::Ordering::SeqCst)
227 + $mod::PerfettoCategory::$category as u64;
228 Some($crate::TraceEvent::new(
229 category_index,
230 instances,
231 concat!($name, "\0").as_ptr() as *const std::ffi::c_char,
232 ))
233 } else {
234 None
235 }
236 }
237 };
238 ($category:ident, $name:expr $(,$t:expr)+) => {
239 // Perfetto doesn't support extra detail arguments, so drop
240 // them.
241 trace_event!($category, $name)
242 };
243 }
244
245 /// Internal macro used to begin a trace event. Not intended for direct
246 /// use by library consumers.
247 #[macro_export]
248 macro_rules! trace_event_begin {
249 ($category:ident, $name:expr) => {
250 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
251 [$mod::PerfettoCategory::$category as usize]
252 .load(std::sync::atomic::Ordering::SeqCst);
253
254 if instances != 0 {
255 let category_index = $mod::PERFETTO_CATEGORY_BASE
256 .load(std::sync::atomic::Ordering::SeqCst)
257 + $mod::PerfettoCategory::$category as u64;
258
259 unsafe {
260 $crate::trace_event_begin(
261 category_index,
262 instances,
263 concat!($name, "\0").as_ptr() as *const std::ffi::c_char,
264 )
265 };
266 }
267 };
268 }
269
270 /// Ends the currently active trace event. Not intended for direct use
271 /// by library consumers.
272 #[macro_export]
273 macro_rules! trace_event_end {
274 ($category:ident) => {
275 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
276 [$mod::PerfettoCategory::$category as usize]
277 .load(std::sync::atomic::Ordering::SeqCst);
278
279 if instances != 0 {
280 let category_index = $mod::PERFETTO_CATEGORY_BASE
281 .load(std::sync::atomic::Ordering::SeqCst)
282 + $mod::PerfettoCategory::$category as u64;
283 unsafe { $crate::trace_event_end(category_index, instances) }
284 }
285 };
286 }
287
288 /// Creates an async flow but does not start it.
289 ///
290 /// Internal wrapper for use by cros_async_trace.
291 #[macro_export]
292 macro_rules! trace_create_async {
293 ($category:expr, $name:expr) => {
294 {
295 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
296 [$category as usize]
297 .load(std::sync::atomic::Ordering::SeqCst);
298
299 if instances != 0 {
300 let category_index = $mod::PERFETTO_CATEGORY_BASE
301 .load(std::sync::atomic::Ordering::SeqCst)
302 + $category as u64;
303
304 let trace_point_name: $crate::StaticString = $name;
305 unsafe {
306 Some($crate::trace_create_async(
307 category_index,
308 instances,
309 trace_point_name.as_ptr(),
310 ))
311 }
312 } else {
313 None
314 }
315 }
316 }
317 }
318
319 /// Starts an existing async flow.
320 ///
321 /// Internal wrapper for use by cros_async_trace.
322 #[macro_export]
323 macro_rules! trace_begin_async {
324 ($category:expr, $name:expr, $optional_terminating_flow_id:expr) => {
325 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
326 [$category as usize]
327 .load(std::sync::atomic::Ordering::SeqCst);
328
329 if instances != 0 {
330 let category_index = $mod::PERFETTO_CATEGORY_BASE
331 .load(std::sync::atomic::Ordering::SeqCst)
332 + $category as u64;
333
334 if let Some(terminating_flow_id) = $optional_terminating_flow_id {
335 let trace_point_name: $crate::StaticString = $name;
336 // Safe because we guarantee $name is a StaticString (which enforces static
337 // a lifetime for the underlying CString).
338 unsafe {
339 $crate::trace_begin_async(
340 category_index,
341 instances,
342 trace_point_name.as_ptr(),
343 terminating_flow_id,
344 )
345 };
346 }
347 }
348 }
349 }
350
351 /// Pauses a running async flow.
352 ///
353 /// Internal wrapper for use by cros_async_trace.
354 #[macro_export]
355 macro_rules! trace_pause_async {
356 ($category:expr) => {
357 {
358 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
359 [$category as usize]
360 .load(std::sync::atomic::Ordering::SeqCst);
361
362 if instances != 0 {
363 let category_index = $mod::PERFETTO_CATEGORY_BASE
364 .load(std::sync::atomic::Ordering::SeqCst)
365 + $category as u64;
366
367 unsafe {
368 // Safe because we are only passing primitives in.
369 Some($crate::trace_pause_async(
370 category_index,
371 instances,
372 ))
373 }
374 } else {
375 None
376 }
377 }
378 }
379 }
380
381 /// Ends a running async flow.
382 ///
383 /// Internal wrapper for use by cros_async_trace.
384 #[macro_export]
385 macro_rules! trace_end_async {
386 ($category:expr) => {
387 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
388 [$category as usize]
389 .load(std::sync::atomic::Ordering::SeqCst);
390
391 if instances != 0 {
392 let category_index = $mod::PERFETTO_CATEGORY_BASE
393 .load(std::sync::atomic::Ordering::SeqCst)
394 + $category as u64;
395
396 // Safe because we are only passing primitives in.
397 unsafe {
398 $crate::trace_end_async(
399 category_index,
400 instances,
401 )
402 };
403 }
404 }
405 }
406
407 /// Emits a counter with the specified name and value. Note that
408 /// Perfetto does NOT average or sample this data, so a high volume of
409 /// calls will very quickly fill the trace buffer.
410 ///
411 /// # Examples
412 /// ```no_run
413 /// trace_counter!(my_category, "counter_name", 500);
414 /// ```
415 #[macro_export]
416 macro_rules! trace_counter {
417 ($category:ident, $name:literal, $value:expr) => {
418 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
419 [$mod::PerfettoCategory::$category as usize]
420 .load(std::sync::atomic::Ordering::SeqCst);
421
422 if instances != 0 {
423 let category_index = $mod::PERFETTO_CATEGORY_BASE
424 .load(std::sync::atomic::Ordering::SeqCst)
425 + $mod::PerfettoCategory::$category as u64;
426
427 // Safe because the counter name is a 'static string.
428 unsafe {
429 $crate::trace_counter(
430 category_index,
431 instances,
432 concat!($name, "\0").as_ptr() as *const std::ffi::c_char,
433 $value,
434 )
435 };
436 }
437 };
438
439 ($category:ident, $name:expr, $value:expr) => {
440 // Required for safety when calling trace_counter.
441 let trace_point_name: $crate::StaticString = $name;
442
443 let instances = $mod::PERFETTO_CATEGORY_INSTANCES
444 [$mod::PerfettoCategory::$category as usize]
445 .load(std::sync::atomic::Ordering::SeqCst);
446
447 if instances != 0 {
448 let category_index = $mod::PERFETTO_CATEGORY_BASE
449 .load(std::sync::atomic::Ordering::SeqCst)
450 + $mod::PerfettoCategory::$category as u64;
451
452 // Safe because we guarantee $name is a StaticString (which enforces static a
453 // lifetime for the underlying CString).
454 unsafe {
455 $crate::trace_counter(
456 category_index,
457 instances,
458 trace_point_name.as_ptr(),
459 $value,
460 )
461 };
462 }
463 };
464 }
465 };
466 }
467
468 /// Perfetto supports two backends, a system backend that runs in a dedicated
469 /// process, and an in process backend. These are selected using this enum.
470 pub enum BackendType {
471 InProcess = BackendType_CTRACE_IN_PROCESS_BACKEND as isize,
472 System = BackendType_CTRACE_SYSTEM_BACKEND as isize,
473 }
474
475 /// Initializes the tracing system. Should not be called directly (use
476 /// `setup_perfetto` instead).
init_tracing(backend: BackendType)477 pub fn init_tracing(backend: BackendType) {
478 let args = ctrace_init_args {
479 api_version: 1,
480 backend: backend as u32,
481 shmem_size_hint_kb: 0,
482 shmem_page_size_hint_kb: 0,
483 shmem_batch_commits_duration_ms: 0,
484 };
485 unsafe { ctrace_init(&args) }
486 }
487
488 /// Rust wrapper for running traces.
489 pub struct Trace {
490 session: ctrace_trace_session_handle,
491 trace_stopped: bool,
492 }
493
494 // Safe because the trace session handle can be sent between threads without ill effect.
495 unsafe impl Sync for Trace {}
496 unsafe impl Send for Trace {}
497
498 impl Trace {
499 /// Starts a trace.
start( duration: TraceDuration, buffer_size_kb: u32, clear_period: Duration, categories: Option<Vec<String>>, ) -> anyhow::Result<Self>500 pub fn start(
501 duration: TraceDuration,
502 buffer_size_kb: u32,
503 clear_period: Duration,
504 categories: Option<Vec<String>>,
505 ) -> anyhow::Result<Self> {
506 let mut config = TraceConfig::new();
507 let mut incremental_state_config = IncrementalStateConfig::new();
508 incremental_state_config.set_clear_period_ms(clear_period.as_millis().try_into()?);
509 config.incremental_state_config = Some(incremental_state_config).into();
510
511 let mut buffer = BufferConfig::new();
512 buffer.set_size_kb(buffer_size_kb);
513 config.buffers.push(buffer);
514
515 let mut data_source = DataSource::new();
516 let mut data_source_config = DataSourceConfig::new();
517 data_source_config.name = Some("track_event".to_owned());
518
519 if let Some(categories) = categories {
520 let mut track_event_config = TrackEventConfig::new();
521 track_event_config.enabled_categories = categories;
522 track_event_config.disabled_categories.push("*".to_string());
523 data_source_config.track_event_config = Some(track_event_config).into();
524 }
525
526 data_source.config = Some(data_source_config).into();
527
528 if let TraceDuration::StopIn(trace_duration) = duration {
529 config.set_duration_ms(trace_duration.as_millis().try_into()?);
530 }
531
532 config.data_sources.push(data_source);
533
534 Ok(Self {
535 session: start_trace_from_proto(config)?,
536 trace_stopped: false,
537 })
538 }
539
540 /// Ends a trace and writes the results to the provided file path.
end(mut self, output: &Path)541 pub fn end(mut self, output: &Path) {
542 // Safe because the session is guaranteed to be valid by self.
543 unsafe { end_trace(self.session, output) }
544 self.trace_stopped = true;
545 }
546
547 /// Ends a trace and returns the trace data. Prepends a magic value & data length to the trace
548 /// data.
end_to_buffer(mut self) -> Vec<u8>549 pub fn end_to_buffer(mut self) -> Vec<u8> {
550 // Safe because the session is guaranteed to be valid by self, and trace_data is disposed
551 // by later calling ctrace_free_trace_buffer.
552 let mut trace_data = unsafe { end_trace_to_buffer(self.session) };
553
554 // Safe because:
555 // 1. trace_data is valid from 0..size.
556 // 2. trace_data lives as long as this slice.
557 let trace_data_slice =
558 unsafe { slice::from_raw_parts(trace_data.data as *mut u8, trace_data.size as usize) };
559
560 let header = TraceHeader {
561 magic: *HEADER_MAGIC,
562 data_size: trace_data.size,
563 data_checksum_sha256: hashes::sha256(trace_data_slice),
564 };
565 let mut trace_vec: Vec<u8> =
566 Vec::with_capacity(size_of::<TraceHeader>() + trace_data.size as usize);
567 trace_vec.extend_from_slice(header.as_bytes());
568 trace_vec.extend_from_slice(trace_data_slice);
569
570 // Safe because trace data is a valid buffer created by ctrace_stop_trace_to_buffer and
571 // there are no other references to it.
572 unsafe { ctrace_free_trace_buffer(&mut trace_data) };
573
574 self.trace_stopped = true;
575 trace_vec
576 }
577 }
578
579 impl Drop for Trace {
drop(&mut self)580 fn drop(&mut self) {
581 if !self.trace_stopped {
582 panic!("Trace must be stopped before it is dropped.")
583 }
584 }
585 }
586
587 /// Start a perfetto trace of duration `duration` and write the output to `output`.
run_trace(duration: Duration, buffer_size: u32, output: &Path)588 pub fn run_trace(duration: Duration, buffer_size: u32, output: &Path) {
589 let output = output.to_owned();
590 std::thread::spawn(move || {
591 let session = start_trace(duration, buffer_size);
592 std::thread::sleep(duration);
593 unsafe { end_trace(session, output.as_path()) };
594 });
595 }
596
597 /// Starts a Perfetto trace with the provided config.
start_trace_from_proto(config: TraceConfig) -> anyhow::Result<ctrace_trace_session_handle>598 pub fn start_trace_from_proto(config: TraceConfig) -> anyhow::Result<ctrace_trace_session_handle> {
599 let mut config_bytes = config.write_to_bytes()?;
600
601 // Safe because config_bytes points to valid memory & we pass its size as required.
602 Ok(unsafe {
603 ctrace_trace_start_from_config_proto(
604 config_bytes.as_mut_ptr() as *mut c_void,
605 config_bytes.len() as u64,
606 )
607 })
608 }
609
610 /// Starts a trace with the given "duration", where duration specifies how much history to hold in
611 /// the ring buffer; in other words, duration is the lookback period when the trace results are
612 /// dumped.
start_trace(duration: Duration, buffer_size_kb: u32) -> ctrace_trace_session_handle613 pub fn start_trace(duration: Duration, buffer_size_kb: u32) -> ctrace_trace_session_handle {
614 unsafe {
615 ctrace_trace_start(&ctrace_trace_config {
616 duration_ms: duration.as_millis() as u32,
617 buffer_size_kb,
618 })
619 }
620 }
621
622 /// End the given trace session and write the results to `output`.
623 /// Safety: trace_session must be a valid trace session from `start_trace`.
end_trace(trace_session: ctrace_trace_session_handle, output: &Path)624 pub unsafe fn end_trace(trace_session: ctrace_trace_session_handle, output: &Path) {
625 let path_c_str = CString::new(output.as_os_str().to_str().unwrap()).unwrap();
626 ctrace_trace_stop(trace_session, path_c_str.as_ptr());
627 }
628
629 /// End the given trace session returns the trace data.
630 ///
631 /// Safety: trace_session must be a valid trace session from `start_trace`.
end_trace_to_buffer( trace_session: ctrace_trace_session_handle, ) -> ctrace_trace_buffer632 pub unsafe fn end_trace_to_buffer(
633 trace_session: ctrace_trace_session_handle,
634 ) -> ctrace_trace_buffer {
635 ctrace_trace_stop_to_buffer(trace_session)
636 }
637
638 /// Add a clock snapshot to the current trace.
639 ///
640 /// This function does not not do any inline checking if a trace is active,
641 /// and thus should only be called in a per-trace callback registered via
642 /// the add_per_trace_callback! macro.
snapshot_clock(mut snapshot: ClockSnapshot)643 pub fn snapshot_clock(mut snapshot: ClockSnapshot) {
644 unsafe { ctrace_add_clock_snapshot(&mut snapshot.snapshot) };
645 }
646
647 /// Represents a Perfetto trace span.
648 pub struct TraceEvent {
649 category_index: u64,
650 instances: u32,
651 }
652
653 impl TraceEvent {
654 #[allow(clippy::not_unsafe_ptr_arg_deref)]
new(category_index: u64, instances: u32, name: *const std::ffi::c_char) -> Self655 pub fn new(category_index: u64, instances: u32, name: *const std::ffi::c_char) -> Self {
656 unsafe {
657 trace_event_begin(
658 category_index,
659 instances,
660 #[allow(clippy::not_unsafe_ptr_arg_deref)]
661 name,
662 )
663 };
664 Self {
665 category_index,
666 instances,
667 }
668 }
669 }
670
671 impl Drop for TraceEvent {
drop(&mut self)672 fn drop(&mut self) {
673 unsafe { trace_event_end(self.category_index, self.instances) }
674 }
675 }
676
677 /// Extension of the Perfetto enum (protos::perfetto_config::BuiltinClock) which
678 /// includes the proposed (but not yet official) TSC clock item.
679 pub enum BuiltinClock {
680 Unknown = 0,
681 Realtime = 1,
682 Coarse = 2,
683 Monotonic = 3,
684 MonotonicCoarse = 4,
685 MonotonicRaw = 5,
686 Boottime = 6,
687 Tsc = 9,
688 }
689
690 /// Wrapper struct around a ctrace_clock_snapshot.
691 pub struct ClockSnapshot {
692 pub snapshot: ctrace_clock_snapshot,
693 }
694
695 impl ClockSnapshot {
new(first: &Clock, second: &Clock) -> ClockSnapshot696 pub fn new(first: &Clock, second: &Clock) -> ClockSnapshot {
697 ClockSnapshot {
698 snapshot: ctrace_clock_snapshot {
699 clocks: [first.clock, second.clock],
700 },
701 }
702 }
703 }
704
705 /// Builder wrapper for a ctrace_clock.
706 pub struct Clock {
707 clock: ctrace_clock,
708 }
709
710 impl Clock {
new(clock_id: u32, timestamp: u64) -> Clock711 pub fn new(clock_id: u32, timestamp: u64) -> Clock {
712 Clock {
713 clock: ctrace_clock {
714 clock_id,
715 timestamp,
716 is_incremental: false,
717 unit_multiplier_ns: 0,
718 },
719 }
720 }
721
set_multiplier(&mut self, multiplier: u64) -> &mut Clock722 pub fn set_multiplier(&mut self, multiplier: u64) -> &mut Clock {
723 self.clock.unit_multiplier_ns = multiplier;
724 self
725 }
726
set_is_incremental(&mut self, is_incremental: bool) -> &mut Clock727 pub fn set_is_incremental(&mut self, is_incremental: bool) -> &mut Clock {
728 self.clock.is_incremental = is_incremental;
729 self
730 }
731 }
732
733 // If running tests in debug mode, ie. `cargo test -p perfetto`,
734 // the cperfetto.dll needs to be imported into the `target` directory.
735 #[cfg(test)]
736 mod tests {
737 #![allow(dead_code)]
738
739 use std::ffi::c_char;
740
741 use cros_tracing_types::static_strings::StaticString;
742
743 use super::*;
744
745 const AOT_BUFFER_SIZE_KB: u32 = 1024;
746 const AOT_BUFFER_CLEAR_PERIOD: Duration = Duration::from_secs(1);
747 setup_perfetto!(tests, future, "Async ftrace points", perfetto_tags!());
748 #[test]
test_async_trace_builds_and_runs()749 fn test_async_trace_builds_and_runs() {
750 tests::register_categories();
751 init_tracing(BackendType::InProcess);
752 let trace = Trace::start(
753 TraceDuration::AlwaysOn,
754 AOT_BUFFER_SIZE_KB,
755 AOT_BUFFER_CLEAR_PERIOD,
756 Some(vec!["future".to_string()]),
757 )
758 .expect("Failed to start trace");
759
760 let static_name = StaticString::register("future_1");
761 let future_category = PerfettoCategory::future;
762
763 let flow_id = tests::trace_create_async!(future_category, static_name);
764 assert!(flow_id.is_some());
765
766 tests::trace_begin_async!(future_category, static_name, flow_id);
767
768 let flow_id = tests::trace_pause_async!(future_category);
769 assert!(flow_id.is_some());
770
771 tests::trace_begin_async!(future_category, static_name, flow_id);
772
773 tests::trace_end_async!(future_category);
774
775 trace.end_to_buffer();
776 }
777
778 #[test]
test_tags_macro_all_empty()779 fn test_tags_macro_all_empty() {
780 let all_tags_empty = perfetto_tags!();
781
782 // SAFETY: strings from perfetto_tags have static lifetime.
783 unsafe {
784 assert_eq!(*(all_tags_empty[0] as *const char), '\0');
785 assert_eq!(*(all_tags_empty[1] as *const char), '\0');
786 assert_eq!(*(all_tags_empty[2] as *const char), '\0');
787 assert_eq!(*(all_tags_empty[3] as *const char), '\0');
788 }
789 }
790
791 #[test]
test_tags_macro_two_used()792 fn test_tags_macro_two_used() {
793 let two_used_tags = perfetto_tags!("tag0", "tag1");
794
795 // SAFETY: strings from perfetto_tags have static lifetime.
796 let tag0 = unsafe { CStr::from_ptr(two_used_tags[0] as *mut c_char) };
797 // SAFETY: strings from perfetto_tags have static lifetime.
798 let tag1 = unsafe { CStr::from_ptr(two_used_tags[1] as *mut c_char) };
799 assert_eq!(tag0.to_str().unwrap(), "tag0");
800 assert_eq!(tag1.to_str().unwrap(), "tag1");
801
802 // SAFETY: strings have static lifetime.
803 unsafe {
804 assert_eq!(*(two_used_tags[2] as *const char), '\0');
805 assert_eq!(*(two_used_tags[3] as *const char), '\0');
806 }
807 }
808 }
809