1use anyhow::Result;
2use protobuf::{Message, MessageField};
3use smol_str::SmolStr;
4use std::{
5 collections::HashMap,
6 io::{self, Write},
7 sync::atomic::{AtomicU64, Ordering::Relaxed},
8 time::{SystemTime, UNIX_EPOCH},
9};
10
11use perfetto_protos::{
12 counter_descriptor::{CounterDescriptor, counter_descriptor::Unit},
13 debug_annotation::{DebugAnnotation, DebugAnnotationName},
14 interned_data::InternedData,
15 profile_common::InternedString,
16 source_location::SourceLocation,
17 trace::Trace,
18 trace_packet::{TracePacket, trace_packet::SequenceFlags},
19 track_descriptor::TrackDescriptor,
20 track_event::{EventCategory, EventName, TrackEvent, track_event::Type},
21};
22
23pub use perfetto_protos::counter_descriptor::counter_descriptor::Unit as CounterUnit;
25
26#[derive(PartialEq, Debug, Clone, Copy)]
27pub(crate) enum InternID {
28 New(u64),
29 Existing(u64),
30}
31
32impl Into<u64> for InternID {
33 fn into(self) -> u64 {
34 match self {
35 InternID::New(i) => i,
36 InternID::Existing(i) => i,
37 }
38 }
39}
40
41impl Into<MessageField<EventName>> for InternID {
42 fn into(self) -> MessageField<EventName> {
43 MessageField::some(EventName {
44 iid: Some(self.into()),
45 ..Default::default()
46 })
47 }
48}
49
50impl InternID {
51 fn new(self) -> bool {
52 match self {
53 InternID::New(_) => true,
54 InternID::Existing(_) => false,
55 }
56 }
57
58 fn as_u64(self) -> u64 {
59 self.into()
60 }
61}
62
63#[derive(Default, Debug)]
64pub(crate) struct Intern<T> {
65 next_id: u64,
66 items: HashMap<T, u64>,
67}
68
69impl<T: Clone + std::hash::Hash + Eq> Intern<T> {
70 pub(crate) fn intern(&mut self, value: T) -> InternID {
71 if let Some(entry) = self.items.get(&value) {
72 return InternID::Existing(*entry);
73 }
74
75 self.next_id += 1;
76 let id = self.next_id;
77 self.items.insert(value, id);
78 InternID::New(id)
79 }
80}
81
82pub struct Context<W: io::Write> {
83 event_names: Intern<SmolStr>,
84 debug_annotation_names: Intern<SmolStr>,
85 debug_annotation_str_values: Intern<SmolStr>,
86 categories: Intern<SmolStr>,
87 source_locations: Intern<(SmolStr, u32)>,
88 trace: Trace,
89 seq: u32,
90 writer: io::BufWriter<W>,
91 next_id: AtomicU64,
92 thread_tracks: HashMap<i32, u64>,
93}
94impl<W> Context<W>
95where
96 W: io::Write + std::fmt::Debug,
97{
98 pub fn into_inner(mut self) -> W {
99 self.flush().unwrap();
100 self.writer.into_inner().unwrap()
101 }
102}
103
104impl<W> Context<W>
105where
106 W: io::Write,
107{
108 pub fn new(writer: W) -> Self {
109 let mut s = Self {
110 writer: io::BufWriter::new(writer),
111 event_names: Default::default(),
112 debug_annotation_names: Default::default(),
113 debug_annotation_str_values: Default::default(),
114 categories: Default::default(),
115 thread_tracks: Default::default(),
116 source_locations: Default::default(),
117 seq: rand::random(),
118 trace: Default::default(),
119 next_id: 0.into(),
120 };
121 let init = s.init_packet();
122 s.trace.packet.push(init);
123 s
124 }
125
126 #[cfg(test)]
127 pub(crate) fn new_with_seq(writer: W, seq: u32) -> Self {
128 let mut s = Self {
129 writer: io::BufWriter::new(writer),
130 event_names: Default::default(),
131 debug_annotation_names: Default::default(),
132 debug_annotation_str_values: Default::default(),
133 categories: Default::default(),
134 thread_tracks: Default::default(),
135 source_locations: Default::default(),
136 seq,
137 trace: Default::default(),
138 next_id: 0.into(),
139 };
140 let init = s.init_packet();
141 s.trace.packet.push(init);
142 s
143 }
144
145 pub fn current_thread_track(&mut self) -> u64 {
146 let current = current_thread();
147 if let Some(track) = self.thread_tracks.get(¤t) {
148 return *track;
149 }
150 let track = self.track().current_process().current_thread().build();
151 self.thread_tracks.insert(current, track);
152 track
153 }
154
155 fn init_packet(&self) -> TracePacket {
156 let mut tp = TracePacket::new();
157 tp.set_sequence_flags(SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED as u32);
158 tp.set_trusted_packet_sequence_id(self.seq);
159 tp
160 }
161
162 pub fn flush(&mut self) -> Result<()> {
163 let trace = std::mem::take(&mut self.trace);
164 trace.write_to_writer(&mut self.writer)?;
165 self.writer.flush()?;
166 Ok(())
167 }
168 pub fn event<'a>(&'a mut self) -> EventBuilder<'a, W> {
169 EventBuilder::new(self)
170 }
171
172 pub fn next_id(&self) -> u64 {
173 self.next_id.fetch_add(1, Relaxed)
174 }
175
176 pub fn track<'a>(&'a mut self) -> TrackBuilder<'a, W> {
177 let id = self.next_id();
178 TrackBuilder::new(self).uuid(id)
179 }
180
181 fn source_location<'a>(&'a mut self, file: impl Into<SmolStr>, line: u32) -> u64 {
182 let file = file.into();
183 let id = self.source_locations.intern((file.clone(), line));
184 match id {
185 InternID::New(id) => {
186 let mut tp = TracePacket::new();
187 tp.interned_data
188 .mut_or_insert_default()
189 .source_locations
190 .push(SourceLocation {
191 iid: Some(id),
192 file_name: Some(file.to_string()),
193 line_number: Some(line),
194 ..Default::default()
195 });
196 self.push_packet(tp);
197 id
198 }
199 InternID::Existing(id) => id,
200 }
201 }
202
203 fn intern_event_name(&mut self, name: impl Into<SmolStr>) -> InternID {
204 let name = name.into();
205 let id = self.event_names.intern(name.clone());
206 if id.new() {
207 let mut tp = TracePacket::new();
208 let mut itd = InternedData::new();
209 itd.event_names.push(EventName {
210 iid: Some(id.as_u64()),
211 name: Some(name.to_string()),
212 ..Default::default()
213 });
214 tp.interned_data = MessageField::some(itd);
215 self.push_packet(tp);
216 }
217 id
218 }
219
220 fn intern_debug_annotation_name(&mut self, name: impl Into<SmolStr>) -> InternID {
221 let name = name.into();
222 let id = self.debug_annotation_names.intern(name.clone());
223 if id.new() {
224 let mut tp = TracePacket::new();
225 let mut itd = InternedData::new();
226 itd.debug_annotation_names.push(DebugAnnotationName {
227 iid: Some(id.into()),
228 name: Some(name.to_string()),
229 ..Default::default()
230 });
231 tp.interned_data = MessageField::some(itd);
232 self.push_packet(tp);
233 }
234 id
235 }
236
237 fn intern_debug_annotation_str_value(&mut self, value: impl Into<SmolStr>) -> InternID {
238 let value = value.into();
239 let id = self.debug_annotation_str_values.intern(value.clone());
240 if id.new() {
241 let mut tp = TracePacket::new();
242 let mut itd = InternedData::new();
243 itd.debug_annotation_string_values.push(InternedString {
244 iid: Some(id.into()),
245 str: Some(value.as_bytes().to_vec()),
246 ..Default::default()
247 });
248 tp.interned_data = MessageField::some(itd);
249 self.push_packet(tp);
250 }
251 id
252 }
253
254 fn intern_category(&mut self, category: impl Into<SmolStr>) -> InternID {
255 let category = category.into();
256 let id = self.categories.intern(category.clone());
257 if id.new() {
258 let mut tp = TracePacket::new();
259 let mut itd = InternedData::new();
260 itd.event_categories.push(EventCategory {
261 iid: Some(id.as_u64()),
262 name: Some(category.to_string()),
263 ..Default::default()
264 });
265 tp.interned_data = MessageField::some(itd);
266 self.push_packet(tp);
267 }
268 id
269 }
270}
271
272impl<'a, W: Write> Context<W> {
273 fn push_packet(&'a mut self, mut packet: TracePacket) {
274 if !packet.has_trusted_packet_sequence_id() {
275 packet.set_trusted_packet_sequence_id(self.seq);
276 }
277 self.trace.packet.push(packet);
278 }
279}
280pub fn current_thread() -> i32 {
281 #[cfg(target_os = "linux")]
282 {
283 nix::unistd::gettid().as_raw()
284 }
285
286 #[cfg(not(target_os = "linux"))]
287 {
288 (nix::sys::pthread::pthread_self() as i32).abs()
289 }
290}
291
292pub struct TrackBuilder<'a, W: io::Write> {
293 track: TrackDescriptor,
294 ctx: &'a mut Context<W>,
295}
296
297impl<'a, W: io::Write> TrackBuilder<'a, W> {
298 fn new(ctx: &'a mut Context<W>) -> Self {
299 Self {
300 track: TrackDescriptor::new(),
301 ctx,
302 }
303 }
304 pub fn uuid(mut self, id: u64) -> Self {
305 self.track.set_uuid(id);
306 self
307 }
308 pub fn parent_uuid(mut self, id: u64) -> Self {
309 self.track.set_parent_uuid(id);
310 self
311 }
312
313 pub fn pid(mut self, pid: i32) -> Self {
314 self.track.thread.mut_or_insert_default().set_pid(pid);
315 self
316 }
317 pub fn tid(mut self, tid: i32) -> Self {
318 self.track.thread.mut_or_insert_default().set_tid(tid);
319 self
320 }
321
322 pub fn name<T: Into<String>>(mut self, name: T) -> Self {
323 self.track.set_name(name.into());
324 self
325 }
326
327 pub fn current_process(self) -> Self {
328 let pid = std::process::id();
329 self.pid(pid as i32)
330 }
331
332 pub fn current_thread(self) -> Self {
333 self.tid(current_thread())
334 }
335
336 pub fn counter(mut self) -> Self {
337 self.track.counter = protobuf::MessageField::some(CounterDescriptor::new());
338 self
339 }
340
341 pub fn unit(mut self, unit: Unit) -> Self {
342 self.track.counter.mut_or_insert_default().set_unit(unit);
343 self
344 }
345
346 pub fn unit_name<T: Into<String>>(mut self, name: T) -> Self {
347 self.track
348 .counter
349 .mut_or_insert_default()
350 .set_unit_name(name.into());
351 self
352 }
353
354 pub fn unit_multiplier(mut self, multiplier: i64) -> Self {
355 self.track
356 .counter
357 .mut_or_insert_default()
358 .set_unit_multiplier(multiplier);
359 self
360 }
361
362 pub fn is_incremental(mut self, incremental: bool) -> Self {
363 self.track
364 .counter
365 .mut_or_insert_default()
366 .set_is_incremental(incremental);
367 self
368 }
369
370 pub fn build(self) -> u64 {
371 let mut tp = TracePacket::new();
372 let id = self.track.uuid();
373 assert!(
374 self.track.has_uuid(),
375 "track_uuid is required for a track event"
376 );
377 tp.set_track_descriptor(self.track);
378 self.ctx.push_packet(tp);
379 id
380 }
381}
382
383pub struct EventBuilder<'a, W: io::Write> {
384 event: TrackEvent,
385 ctx: &'a mut Context<W>,
386}
387
388impl<'a, W: io::Write> EventBuilder<'a, W> {
389 fn new(ctx: &'a mut Context<W>) -> Self {
390 Self {
391 event: TrackEvent::new(),
392 ctx,
393 }
394 }
395
396 pub fn timestamp_us(&mut self, us: i64) {
397 self.event.set_timestamp_absolute_us(us);
398 }
399
400 pub fn now(&mut self) {
401 let us = SystemTime::now()
402 .duration_since(UNIX_EPOCH)
403 .unwrap()
404 .as_micros() as i64;
405 self.timestamp_us(us);
406 }
407
408 pub fn begin(&mut self) {
409 self.event.set_type(Type::TYPE_SLICE_BEGIN);
410 }
411
412 pub fn end(&mut self) {
413 self.event.set_type(Type::TYPE_SLICE_END);
414 }
415
416 pub fn instant(&mut self) {
417 self.event.set_type(Type::TYPE_INSTANT);
418 }
419
420 pub fn counter(&mut self) {
421 self.event.set_type(Type::TYPE_COUNTER);
422 }
423
424 pub fn category(&mut self, category: impl Into<SmolStr>) {
425 let id = self.ctx.intern_category(category);
426 self.event.category_iids.push(id.into());
427 }
428
429 pub fn source_location(&mut self, file: impl Into<SmolStr>, line: u32) {
430 let loc = self.ctx.source_location(file, line);
431 self.event.set_source_location_iid(loc);
432 }
433
434 pub fn name(&mut self, name: impl Into<SmolStr>) {
435 let id = self.ctx.intern_event_name(name);
436 self.event.set_name_iid(id.into());
437 }
438
439 pub fn debug_str(&mut self, name: impl Into<SmolStr>, value: impl Into<SmolStr>) {
440 let id = self.ctx.intern_debug_annotation_name(name);
441 let vid = self.ctx.intern_debug_annotation_str_value(value);
442 let mut da = DebugAnnotation::new();
443 da.set_name_iid(id.into());
444 da.set_string_value_iid(vid.into());
445 self.event.debug_annotations.push(da);
446 }
447
448 pub fn debug_bool(&mut self, name: impl Into<SmolStr>, value: bool) {
449 let id = self.ctx.intern_debug_annotation_name(name);
450 let mut da = DebugAnnotation::new();
451 da.set_name_iid(id.into());
452 da.set_bool_value(value);
453 self.event.debug_annotations.push(da);
454 }
455
456 pub fn debug_int(&mut self, name: impl Into<SmolStr>, value: i64) {
457 let id = self.ctx.intern_debug_annotation_name(name);
458 let mut da = DebugAnnotation::new();
459 da.set_name_iid(id.into());
460 da.set_int_value(value);
461 self.event.debug_annotations.push(da);
462 }
463
464 pub fn debug_uint(&mut self, name: impl Into<SmolStr>, value: u64) {
465 let id = self.ctx.intern_debug_annotation_name(name);
466 let mut da = DebugAnnotation::new();
467 da.set_name_iid(id.into());
468 da.set_uint_value(value);
469 self.event.debug_annotations.push(da);
470 }
471
472 pub fn debug_double(&mut self, name: impl Into<SmolStr>, value: f64) {
473 let id = self.ctx.intern_debug_annotation_name(name);
474 let mut da = DebugAnnotation::new();
475 da.set_name_iid(id.into());
476 da.set_double_value(value);
477 self.event.debug_annotations.push(da);
478 }
479
480 pub fn debug_pointer(&mut self, name: impl Into<SmolStr>, value: u64) {
481 let id = self.ctx.intern_debug_annotation_name(name);
482 let mut da = DebugAnnotation::new();
483 da.set_name_iid(id.into());
484 da.set_pointer_value(value);
485 self.event.debug_annotations.push(da);
486 }
487
488 pub fn track_uuid(&mut self, id: u64) {
489 self.event.set_track_uuid(id);
490 }
491
492 pub fn counter_value(&mut self, value: i64) {
493 self.event.set_counter_value(value);
494 }
495
496 pub fn double_counter_value(&mut self, value: f64) {
497 self.event.set_double_counter_value(value);
498 }
499
500 pub fn flow_id(&mut self, id: u64) {
501 self.event.flow_ids.push(id);
502 }
503
504 pub fn terminating_flow_id(&mut self, id: u64) {
505 self.event.terminating_flow_ids.push(id);
506 }
507
508 pub fn extra_counter(&mut self, track_uuid: u64, value: i64) {
509 self.event.extra_counter_track_uuids.push(track_uuid);
510 self.event.extra_counter_values.push(value);
511 }
512
513 pub fn extra_double_counter(&mut self, track_uuid: u64, value: f64) {
514 self.event.extra_double_counter_track_uuids.push(track_uuid);
515 self.event.extra_double_counter_values.push(value);
516 }
517
518 pub fn with_timestamp_us(mut self, us: i64) -> Self {
519 self.timestamp_us(us);
520 self
521 }
522
523 pub fn with_now(mut self) -> Self {
524 self.now();
525 self
526 }
527
528 pub fn with_begin(mut self) -> Self {
529 self.begin();
530 self
531 }
532
533 pub fn with_end(mut self) -> Self {
534 self.end();
535 self
536 }
537
538 pub fn with_instant(mut self) -> Self {
539 self.instant();
540 self
541 }
542
543 pub fn with_counter(mut self) -> Self {
544 self.counter();
545 self
546 }
547
548 pub fn with_category(mut self, category: impl Into<SmolStr>) -> Self {
549 self.category(category);
550 self
551 }
552
553 pub fn with_source_location(mut self, file: impl Into<SmolStr>, line: u32) -> Self {
554 self.source_location(file, line);
555 self
556 }
557
558 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
559 self.name(name);
560 self
561 }
562
563 pub fn with_track_uuid(mut self, id: u64) -> Self {
564 self.track_uuid(id);
565 self
566 }
567
568 pub fn with_debug_str(mut self, name: impl Into<SmolStr>, value: impl Into<SmolStr>) -> Self {
569 self.debug_str(name, value);
570 self
571 }
572
573 pub fn with_debug_bool(mut self, name: impl Into<SmolStr>, value: bool) -> Self {
574 self.debug_bool(name, value);
575 self
576 }
577
578 pub fn with_debug_int(mut self, name: impl Into<SmolStr>, value: i64) -> Self {
579 self.debug_int(name, value);
580 self
581 }
582
583 pub fn with_debug_uint(mut self, name: impl Into<SmolStr>, value: u64) -> Self {
584 self.debug_uint(name, value);
585 self
586 }
587
588 pub fn with_debug_double(mut self, name: impl Into<SmolStr>, value: f64) -> Self {
589 self.debug_double(name, value);
590 self
591 }
592
593 pub fn with_debug_pointer(mut self, name: impl Into<SmolStr>, value: u64) -> Self {
594 self.debug_pointer(name, value);
595 self
596 }
597
598 pub fn with_counter_value(mut self, value: i64) -> Self {
599 self.counter_value(value);
600 self
601 }
602
603 pub fn with_double_counter_value(mut self, value: f64) -> Self {
604 self.double_counter_value(value);
605 self
606 }
607
608 pub fn with_flow_id(mut self, id: u64) -> Self {
609 self.flow_id(id);
610 self
611 }
612
613 pub fn with_terminating_flow_id(mut self, id: u64) -> Self {
614 self.terminating_flow_id(id);
615 self
616 }
617
618 pub fn with_extra_counter(mut self, track_uuid: u64, value: i64) -> Self {
619 self.extra_counter(track_uuid, value);
620 self
621 }
622
623 pub fn with_extra_double_counter(mut self, track_uuid: u64, value: f64) -> Self {
624 self.extra_double_counter(track_uuid, value);
625 self
626 }
627
628 pub fn build(self) {
629 let mut tp = TracePacket::new();
630 assert!(
631 self.event.has_track_uuid(),
632 "track_uuid is required for a track event"
633 );
634 tp.set_track_event(self.event);
635 self.ctx.push_packet(tp);
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use anyhow::Result;
643 use assert_matches::assert_matches;
644 use bytes::{Buf, BufMut, BytesMut};
645 use perfetto_protos::trace::Trace;
646 use protobuf::Message;
647 use std::fs;
648 use std::path::Path;
649
650 fn assert_golden_text(trace: &Trace, golden_path: impl AsRef<Path>) -> Result<()> {
677 use protobuf::text_format;
678
679 let golden_path = golden_path.as_ref();
680 let actual_text = text_format::print_to_string_pretty(trace);
681
682 if std::env::var("UPDATE_GOLDEN").is_ok() {
684 if let Some(parent) = golden_path.parent() {
686 fs::create_dir_all(parent)?;
687 }
688 fs::write(golden_path, &actual_text)?;
689 eprintln!("Updated golden file: {}", golden_path.display());
690 return Ok(());
691 }
692
693 let expected_text = fs::read_to_string(golden_path).map_err(|e| {
695 anyhow::anyhow!(
696 "Failed to read golden file at '{}': {}.\n\
697 Hint: Run with UPDATE_GOLDEN=1 to create this file.",
698 golden_path.display(),
699 e
700 )
701 })?;
702
703 if actual_text != expected_text {
705 let diff_msg = format!(
707 "\nGolden file mismatch for: {}\n\
708 \n\
709 Expected:\n\
710 {}\n\
711 \n\
712 Actual:\n\
713 {}\n\
714 \n\
715 Hint: Run with UPDATE_GOLDEN=1 to update the golden file.",
716 golden_path.display(),
717 expected_text,
718 actual_text
719 );
720 anyhow::bail!(diff_msg);
721 }
722
723 Ok(())
724 }
725
726 #[test]
727 fn same_id() -> Result<()> {
728 let mut i = Intern::default();
729 let id = i.intern("a");
730 assert_eq!(id.as_u64(), i.intern("a").as_u64());
731 Ok(())
732 }
733
734 #[test]
735 fn owned_strings_work() -> Result<()> {
736 let buf = BytesMut::new();
737 let mut ctx = Context::new(buf.writer());
738 let track = ctx.track().current_process().current_thread().build();
739
740 let event_name = String::from("dynamic_event");
742 let category = String::from("custom_category");
743 let annotation_name = String::from("user_data");
744 let annotation_value = String::from("hello from owned string!");
745
746 ctx.event()
747 .with_begin()
748 .with_name(event_name)
749 .with_category(category)
750 .with_track_uuid(track)
751 .with_debug_str(annotation_name, annotation_value)
752 .build();
753
754 ctx.event()
756 .with_end()
757 .with_name("static_event")
758 .with_category("static_category")
759 .with_track_uuid(track)
760 .with_debug_str("key", "value")
761 .build();
762
763 let buf: BytesMut = ctx.into_inner().into_inner();
764 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
765
766 assert!(trace.packet.len() > 4);
768 Ok(())
769 }
770
771 #[test]
772 fn event_round_trip() -> Result<()> {
773 let buf = BytesMut::new();
774 let mut ctx = Context::new(buf.writer());
775 ctx.event()
776 .with_begin()
777 .with_name("test")
778 .with_track_uuid(1)
779 .build();
780 ctx.event()
781 .with_end()
782 .with_name("test")
783 .with_track_uuid(1)
784 .build();
785 ctx.event()
786 .with_begin()
787 .with_name("best")
788 .with_track_uuid(1)
789 .build();
790 let buf: BytesMut = ctx.into_inner().into_inner();
791 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
792 assert_matches!(trace.packet[1].interned_data.as_ref(), Some(InternedData{ event_names, .. }) if event_names[0].name() == "test");
794 assert_eq!(trace.packet[2].track_event().name_iid(), 1);
795 assert_eq!(trace.packet[3].track_event().name_iid(), 1);
796 assert_matches!(trace.packet[4].interned_data.as_ref(), Some(InternedData{ event_names, .. }) if event_names[0].name() == "best");
797 assert_eq!(trace.packet[5].track_event().name_iid(), 2);
798
799 Ok(())
800 }
801
802 #[test]
803 fn track_round_trip() -> Result<()> {
804 let buf = BytesMut::new();
805 let mut ctx = Context::new(buf.writer());
806 ctx.track()
807 .uuid(100)
808 .name("main_thread")
809 .pid(100)
810 .tid(101)
811 .build();
812 ctx.track()
813 .uuid(101)
814 .name("worker_thread")
815 .pid(100)
816 .tid(102)
817 .build();
818 let buf: BytesMut = ctx.into_inner().into_inner();
819 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
820
821 let track1 = trace.packet[1].track_descriptor();
824 assert_eq!(track1.uuid(), 100);
825 assert_eq!(track1.name(), "main_thread");
826 assert_eq!(track1.thread.pid(), 100);
827 assert_eq!(track1.thread.tid(), 101);
828
829 let track2 = trace.packet[2].track_descriptor();
831 assert_eq!(track2.uuid(), 101);
832 assert_eq!(track2.name(), "worker_thread");
833 assert_eq!(track2.thread.pid(), 100);
834 assert_eq!(track2.thread.tid(), 102);
835
836 Ok(())
837 }
838
839 #[test]
840 fn track_current_process() -> Result<()> {
841 let buf = BytesMut::new();
842 let mut ctx = Context::new(buf.writer());
843 let expected_pid = std::process::id();
844
845 ctx.track()
846 .uuid(200)
847 .name("process_track")
848 .current_process()
849 .build();
850
851 let buf: BytesMut = ctx.into_inner().into_inner();
852 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
853
854 let track = trace.packet[1].track_descriptor();
856 assert_eq!(track.uuid(), 200);
857 assert_eq!(track.name(), "process_track");
858 assert_eq!(track.thread.pid(), expected_pid as i32);
859
860 Ok(())
861 }
862
863 #[test]
864 fn track_current_thread() -> Result<()> {
865 let buf = BytesMut::new();
866 let mut ctx = Context::new(buf.writer());
867 let expected_tid = current_thread();
868
869 ctx.track()
870 .uuid(201)
871 .name("thread_track")
872 .current_thread()
873 .build();
874
875 let buf: BytesMut = ctx.into_inner().into_inner();
876 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
877
878 let track = trace.packet[1].track_descriptor();
880 assert_eq!(track.uuid(), 201);
881 assert_eq!(track.name(), "thread_track");
882 assert_eq!(track.thread.tid(), expected_tid as i32);
883
884 Ok(())
885 }
886
887 #[test]
888 fn track_current_process_and_thread() -> Result<()> {
889 let buf = BytesMut::new();
890 let mut ctx = Context::new(buf.writer());
891 let expected_pid = std::process::id();
892 let expected_tid = current_thread();
893
894 ctx.track()
895 .uuid(202)
896 .name("full_track")
897 .current_process()
898 .current_thread()
899 .build();
900
901 let buf: BytesMut = ctx.into_inner().into_inner();
902 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
903
904 let track = trace.packet[1].track_descriptor();
906 assert_eq!(track.uuid(), 202);
907 assert_eq!(track.name(), "full_track");
908 assert_eq!(track.thread.pid(), expected_pid as i32);
909 assert_eq!(track.thread.tid(), expected_tid as i32);
910
911 Ok(())
912 }
913
914 #[test]
915 fn category_interning() -> Result<()> {
916 let buf = BytesMut::new();
917 let mut ctx = Context::new(buf.writer());
918
919 ctx.event()
920 .with_begin()
921 .with_name("event1")
922 .with_category("rendering")
923 .with_track_uuid(1)
924 .build();
925
926 ctx.event()
927 .with_end()
928 .with_name("event2")
929 .with_category("rendering")
930 .with_track_uuid(1)
931 .build();
932
933 ctx.event()
934 .with_instant()
935 .with_name("event3")
936 .with_category("networking")
937 .with_track_uuid(1)
938 .build();
939
940 let buf: BytesMut = ctx.into_inner().into_inner();
941 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
942
943 let mut event_count = 0;
945 let mut category_rendering_found = false;
946 let mut category_networking_found = false;
947
948 for packet in &trace.packet {
949 if let Some(interned) = packet.interned_data.as_ref() {
950 if !interned.event_categories.is_empty() {
951 if interned.event_categories[0].name() == "rendering" {
952 category_rendering_found = true;
953 assert_eq!(interned.event_categories[0].iid(), 1);
954 } else if interned.event_categories[0].name() == "networking" {
955 category_networking_found = true;
956 assert_eq!(interned.event_categories[0].iid(), 2);
957 }
958 }
959 }
960 if packet.has_track_event() {
961 event_count += 1;
962 let event = packet.track_event();
963 if event_count <= 2 {
964 assert_eq!(event.category_iids[0], 1);
966 } else {
967 assert_eq!(event.category_iids[0], 2);
969 }
970 }
971 }
972
973 assert!(category_rendering_found);
974 assert!(category_networking_found);
975 assert_eq!(event_count, 3);
976
977 Ok(())
978 }
979
980 #[test]
981 fn counter_values() -> Result<()> {
982 let buf = BytesMut::new();
983 let mut ctx = Context::new(buf.writer());
984
985 ctx.event()
986 .with_counter()
987 .with_name("int_counter")
988 .with_counter_value(42)
989 .with_track_uuid(1)
990 .build();
991
992 ctx.event()
993 .with_counter()
994 .with_name("double_counter")
995 .with_double_counter_value(3.14)
996 .with_track_uuid(1)
997 .build();
998
999 let buf: BytesMut = ctx.into_inner().into_inner();
1000 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1001
1002 assert_eq!(trace.packet[2].track_event().counter_value(), 42);
1006 assert_eq!(trace.packet[4].track_event().double_counter_value(), 3.14);
1009
1010 Ok(())
1011 }
1012
1013 #[test]
1014 fn flow_ids() -> Result<()> {
1015 let buf = BytesMut::new();
1016 let mut ctx = Context::new(buf.writer());
1017
1018 ctx.event()
1019 .with_begin()
1020 .with_name("flow_start")
1021 .with_flow_id(100)
1022 .with_track_uuid(1)
1023 .build();
1024
1025 ctx.event()
1026 .with_end()
1027 .with_name("flow_end")
1028 .with_terminating_flow_id(100)
1029 .with_track_uuid(1)
1030 .build();
1031
1032 let buf: BytesMut = ctx.into_inner().into_inner();
1033 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1034
1035 assert_eq!(trace.packet[2].track_event().flow_ids[0], 100);
1039 assert_eq!(trace.packet[4].track_event().terminating_flow_ids[0], 100);
1042
1043 Ok(())
1044 }
1045
1046 #[test]
1047 fn extra_counters() -> Result<()> {
1048 let buf = BytesMut::new();
1049 let mut ctx = Context::new(buf.writer());
1050
1051 ctx.event()
1052 .with_instant()
1053 .with_name("event_with_extras")
1054 .with_extra_counter(200, 123)
1055 .with_extra_double_counter(201, 45.67)
1056 .with_track_uuid(1)
1057 .build();
1058
1059 let buf: BytesMut = ctx.into_inner().into_inner();
1060 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1061
1062 let event = trace.packet[2].track_event();
1066 assert_eq!(event.extra_counter_track_uuids[0], 200);
1067 assert_eq!(event.extra_counter_values[0], 123);
1068 assert_eq!(event.extra_double_counter_track_uuids[0], 201);
1069 assert_eq!(event.extra_double_counter_values[0], 45.67);
1070
1071 Ok(())
1072 }
1073
1074 #[test]
1075 fn debug_annotations_types() -> Result<()> {
1076 let buf = BytesMut::new();
1077 let mut ctx = Context::new(buf.writer());
1078
1079 ctx.event()
1080 .with_instant()
1081 .with_name("annotated_event")
1082 .with_debug_bool("is_enabled", true)
1083 .with_debug_int("count", -42)
1084 .with_debug_uint("size", 1024)
1085 .with_debug_double("ratio", 0.75)
1086 .with_debug_pointer("ptr", 0xdeadbeef)
1087 .with_track_uuid(1)
1088 .build();
1089
1090 let buf: BytesMut = ctx.into_inner().into_inner();
1091 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1092
1093 let event = trace.packet[7].track_event();
1098 assert_eq!(event.debug_annotations.len(), 5);
1099
1100 assert_eq!(event.debug_annotations[0].bool_value(), true);
1102 assert_eq!(event.debug_annotations[1].int_value(), -42);
1104 assert_eq!(event.debug_annotations[2].uint_value(), 1024);
1106 assert_eq!(event.debug_annotations[3].double_value(), 0.75);
1108 assert_eq!(event.debug_annotations[4].pointer_value(), 0xdeadbeef);
1110
1111 Ok(())
1112 }
1113
1114 #[test]
1115 fn counter_track_basic() -> Result<()> {
1116 let buf = BytesMut::new();
1117 let mut ctx = Context::new(buf.writer());
1118
1119 ctx.track().uuid(300).name("memory_usage").counter().build();
1121
1122 let buf: BytesMut = ctx.into_inner().into_inner();
1123 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1124
1125 let track = trace.packet[1].track_descriptor();
1128 assert_eq!(track.uuid(), 300);
1129 assert_eq!(track.name(), "memory_usage");
1130 assert!(track.counter.is_some());
1131
1132 Ok(())
1133 }
1134
1135 #[test]
1136 fn counter_track_with_unit() -> Result<()> {
1137 let buf = BytesMut::new();
1138 let mut ctx = Context::new(buf.writer());
1139
1140 ctx.track()
1142 .uuid(301)
1143 .name("bytes_allocated")
1144 .counter()
1145 .unit(Unit::UNIT_SIZE_BYTES)
1146 .build();
1147
1148 let buf: BytesMut = ctx.into_inner().into_inner();
1149 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1150
1151 let track = trace.packet[1].track_descriptor();
1154 assert_eq!(track.uuid(), 301);
1155 assert_eq!(track.name(), "bytes_allocated");
1156 assert!(track.counter.is_some());
1157 assert_eq!(track.counter.unit(), Unit::UNIT_SIZE_BYTES);
1158
1159 Ok(())
1160 }
1161
1162 #[test]
1163 fn counter_track_with_custom_unit_name() -> Result<()> {
1164 let buf = BytesMut::new();
1165 let mut ctx = Context::new(buf.writer());
1166
1167 ctx.track()
1169 .uuid(302)
1170 .name("cpu_temperature")
1171 .counter()
1172 .unit_name("celsius")
1173 .build();
1174
1175 let buf: BytesMut = ctx.into_inner().into_inner();
1176 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1177
1178 let track = trace.packet[1].track_descriptor();
1181 assert_eq!(track.uuid(), 302);
1182 assert_eq!(track.name(), "cpu_temperature");
1183 assert!(track.counter.is_some());
1184 assert_eq!(track.counter.unit_name(), "celsius");
1185
1186 Ok(())
1187 }
1188
1189 #[test]
1190 fn counter_track_with_multiplier() -> Result<()> {
1191 let buf = BytesMut::new();
1192 let mut ctx = Context::new(buf.writer());
1193
1194 ctx.track()
1196 .uuid(303)
1197 .name("memory_kb")
1198 .counter()
1199 .unit(Unit::UNIT_SIZE_BYTES)
1200 .unit_multiplier(1024)
1201 .build();
1202
1203 let buf: BytesMut = ctx.into_inner().into_inner();
1204 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1205
1206 let track = trace.packet[1].track_descriptor();
1209 assert_eq!(track.uuid(), 303);
1210 assert_eq!(track.name(), "memory_kb");
1211 assert!(track.counter.is_some());
1212 assert_eq!(track.counter.unit(), Unit::UNIT_SIZE_BYTES);
1213 assert_eq!(track.counter.unit_multiplier(), 1024);
1214
1215 Ok(())
1216 }
1217
1218 #[test]
1219 fn counter_track_incremental() -> Result<()> {
1220 let buf = BytesMut::new();
1221 let mut ctx = Context::new(buf.writer());
1222
1223 ctx.track()
1225 .uuid(304)
1226 .name("packet_count_delta")
1227 .counter()
1228 .unit(Unit::UNIT_COUNT)
1229 .is_incremental(true)
1230 .build();
1231
1232 let buf: BytesMut = ctx.into_inner().into_inner();
1233 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1234
1235 let track = trace.packet[1].track_descriptor();
1238 assert_eq!(track.uuid(), 304);
1239 assert_eq!(track.name(), "packet_count_delta");
1240 assert!(track.counter.is_some());
1241 assert_eq!(track.counter.unit(), Unit::UNIT_COUNT);
1242 assert_eq!(track.counter.is_incremental(), true);
1243
1244 Ok(())
1245 }
1246
1247 #[test]
1248 fn counter_track_full_configuration() -> Result<()> {
1249 let buf = BytesMut::new();
1250 let mut ctx = Context::new(buf.writer());
1251
1252 ctx.track()
1254 .uuid(305)
1255 .name("network_throughput")
1256 .counter()
1257 .unit(Unit::UNIT_SIZE_BYTES)
1258 .unit_name("bytes_per_second")
1259 .unit_multiplier(1)
1260 .is_incremental(false)
1261 .build();
1262
1263 let buf: BytesMut = ctx.into_inner().into_inner();
1264 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1265
1266 let track = trace.packet[1].track_descriptor();
1269 assert_eq!(track.uuid(), 305);
1270 assert_eq!(track.name(), "network_throughput");
1271 assert!(track.counter.is_some());
1272 assert_eq!(track.counter.unit(), Unit::UNIT_SIZE_BYTES);
1273 assert_eq!(track.counter.unit_name(), "bytes_per_second");
1274 assert_eq!(track.counter.unit_multiplier(), 1);
1275 assert_eq!(track.counter.is_incremental(), false);
1276
1277 Ok(())
1278 }
1279
1280 #[test]
1281 fn golden_simple_event() -> Result<()> {
1282 let buf = BytesMut::new();
1283 let mut ctx = Context::new_with_seq(buf.writer(), 12345);
1284
1285 let track = ctx.track().uuid(100).name("test_thread").build();
1287
1288 ctx.event()
1289 .with_begin()
1290 .with_name("test_event")
1291 .with_category("test_category")
1292 .with_track_uuid(track)
1293 .build();
1294
1295 ctx.event()
1296 .with_end()
1297 .with_name("test_event")
1298 .with_category("test_category")
1299 .with_track_uuid(track)
1300 .build();
1301
1302 let buf: BytesMut = ctx.into_inner().into_inner();
1303 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1304
1305 assert_golden_text(&trace, "tests/golden/simple_event.txtpb")?;
1306
1307 Ok(())
1308 }
1309
1310 #[test]
1311 fn golden_counter_track() -> Result<()> {
1312 let buf = BytesMut::new();
1313 let mut ctx = Context::new_with_seq(buf.writer(), 12345);
1314
1315 ctx.track()
1317 .uuid(100)
1318 .name("cpu_usage")
1319 .counter()
1320 .unit(Unit::UNIT_COUNT)
1321 .unit_name("percent")
1322 .unit_multiplier(1)
1323 .is_incremental(false)
1324 .build();
1325
1326 let buf: BytesMut = ctx.into_inner().into_inner();
1327 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1328
1329 assert_golden_text(&trace, "tests/golden/counter_track.txtpb")?;
1330
1331 Ok(())
1332 }
1333
1334 #[test]
1335 fn golden_debug_annotations() -> Result<()> {
1336 let buf = BytesMut::new();
1337 let mut ctx = Context::new_with_seq(buf.writer(), 12345);
1338
1339 let track = ctx.track().uuid(200).name("annotation_thread").build();
1341
1342 ctx.event()
1343 .with_begin()
1344 .with_name("annotated_event")
1345 .with_category("annotations")
1346 .with_track_uuid(track)
1347 .with_debug_bool("is_enabled", true)
1348 .with_debug_int("count", 42)
1349 .with_debug_uint("id", 123)
1350 .with_debug_double("ratio", 3.14)
1351 .with_debug_str("message", "Hello, Perfetto!")
1352 .build();
1353
1354 let buf: BytesMut = ctx.into_inner().into_inner();
1355 let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
1356
1357 assert_golden_text(&trace, "tests/golden/debug_annotations.txtpb")?;
1358
1359 Ok(())
1360 }
1361}