perfetto_writer/
lib.rs

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
23// Re-export Unit enum for counter tracks
24pub 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(&current) {
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    /// Assert that a Trace matches the text representation stored in a golden file.
651    ///
652    /// This function converts the given Trace to protobuf text format and compares it
653    /// with the contents of the file at the specified path. If the contents don't match,
654    /// the test will fail with a detailed diff.
655    ///
656    /// Set the `UPDATE_GOLDEN` environment variable to update golden files:
657    /// ```bash
658    /// UPDATE_GOLDEN=1 cargo test
659    /// ```
660    ///
661    /// # Arguments
662    ///
663    /// * `trace` - The Trace to compare
664    /// * `golden_path` - Path to the golden file (relative to workspace root)
665    ///
666    /// # Example
667    ///
668    /// ```ignore
669    /// let buf = BytesMut::new();
670    /// let mut ctx = Context::new(buf.writer());
671    /// ctx.event().with_begin().with_name("test").with_track_uuid(1).build();
672    /// let buf: BytesMut = ctx.into_inner().into_inner();
673    /// let trace: Trace = Trace::parse_from_reader(&mut buf.reader())?;
674    /// assert_golden_text(&trace, "tests/golden/simple_event.txtpb")?;
675    /// ```
676    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        // Check if we should update golden files
683        if std::env::var("UPDATE_GOLDEN").is_ok() {
684            // Ensure parent directory exists
685            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        // Read the expected golden file
694        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        // Compare the texts
704        if actual_text != expected_text {
705            // Create a helpful error message
706            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        // Test with owned strings (e.g., from Python or user input)
741        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        // Test with static strings (still works!)
755        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        // Verify we got the events
767        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        // packet[0] is the init packet
793        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        // packet[0] is the init packet
822        // First track descriptor
823        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        // Second track descriptor
830        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        // packet[0] is the init packet
855        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        // packet[0] is the init packet
879        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        // packet[0] is the init packet
905        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        // Find events and verify categories are interned correctly
944        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                    // First two events use category_iid 1 (rendering)
965                    assert_eq!(event.category_iids[0], 1);
966                } else {
967                    // Third event uses category_iid 2 (networking)
968                    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        // packet[0] is the init packet
1003        // packet[1] is the interned name "int_counter"
1004        // packet[2] is the counter event with int value
1005        assert_eq!(trace.packet[2].track_event().counter_value(), 42);
1006        // packet[3] is the interned name "double_counter"
1007        // packet[4] is the counter event with double value
1008        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        // packet[0] is the init packet
1036        // packet[1] is the interned name "flow_start"
1037        // packet[2] is the event with flow_id
1038        assert_eq!(trace.packet[2].track_event().flow_ids[0], 100);
1039        // packet[3] is the interned name "flow_end"
1040        // packet[4] is the event with terminating_flow_id
1041        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        // packet[0] is the init packet
1063        // packet[1] is the interned name
1064        // packet[2] is the event with extra counters
1065        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        // packet[0] is the init packet
1094        // packets 1-5 are interned debug annotation names
1095        // packet[6] is the interned event name
1096        // packet[7] is the event with debug annotations
1097        let event = trace.packet[7].track_event();
1098        assert_eq!(event.debug_annotations.len(), 5);
1099
1100        // Check bool annotation
1101        assert_eq!(event.debug_annotations[0].bool_value(), true);
1102        // Check int annotation
1103        assert_eq!(event.debug_annotations[1].int_value(), -42);
1104        // Check uint annotation
1105        assert_eq!(event.debug_annotations[2].uint_value(), 1024);
1106        // Check double annotation
1107        assert_eq!(event.debug_annotations[3].double_value(), 0.75);
1108        // Check pointer annotation
1109        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        // Create a basic counter track
1120        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        // packet[0] is the init packet
1126        // packet[1] is the track descriptor
1127        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        // Create a counter track with unit
1141        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        // packet[0] is the init packet
1152        // packet[1] is the track descriptor
1153        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        // Create a counter track with custom unit name
1168        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        // packet[0] is the init packet
1179        // packet[1] is the track descriptor
1180        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        // Create a counter track with unit multiplier (e.g., kilobytes instead of bytes)
1195        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        // packet[0] is the init packet
1207        // packet[1] is the track descriptor
1208        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        // Create an incremental counter track (for delta values)
1224        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        // packet[0] is the init packet
1236        // packet[1] is the track descriptor
1237        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        // Create a fully configured counter track
1253        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        // packet[0] is the init packet
1267        // packet[1] is the track descriptor
1268        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        // Use a fixed track UUID for deterministic output
1286        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        // Create a counter track with full configuration
1316        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        // Use a fixed track UUID for deterministic output
1340        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}