1use crate::schema::DebugAnnotation;
6use crate::schema::ThreadDescriptor;
7use crate::schema::TracePacket;
8use crate::schema::TrackDescriptor;
9use prost::Message;
10use rand::RngCore;
11use rand::rngs::ThreadRng;
12use std::cell::RefCell;
13use std::collections::HashMap;
14use std::path::Path;
15use std::sync::atomic::AtomicBool;
16use std::sync::atomic::Ordering;
17
18#[cfg(all(feature = "enable", target_os = "macos"))]
19#[path = "os_macos.rs"]
20mod os;
21
22#[cfg(all(feature = "enable", unix, not(target_os = "macos")))]
23#[path = "os_unix.rs"]
24mod os;
25
26#[cfg(all(feature = "enable", windows))]
27#[path = "os_windows.rs"]
28mod os;
29
30#[cfg(not(feature = "enable"))]
31#[path = "os_disabled.rs"]
32mod os;
33
34#[cfg(feature = "fastant")]
35type Instant = fastant::Instant;
36
37#[cfg(not(feature = "fastant"))]
38type Instant = std::time::SystemTime;
39
40mod schema;
41
42#[macro_export]
55macro_rules! scope {
56 ($($args:tt)*) => {
57 let _guard = $crate::start_span!($($args)*);
58 };
59}
60
61#[macro_export]
75macro_rules! start_span {
76 ($name:expr $(, $($arg_name:ident $( = $arg_value:expr)?),*)?) => {{
77 const SOURCE_INFO: $crate::SourceInfo = $crate::SourceInfo {
78 name: $name,
79 file: file!(),
80 line: line!(),
81 arg_names: &[$($(stringify!($arg_name)),*)?],
82 };
83 if $crate::is_enabled() {
84 $crate::record_event($crate::Event::StartSpan(&SOURCE_INFO));
85 $crate::record_event($crate::Event::Timestamp($crate::time()));
86 $($($crate::RecordArg::record_arg(
87 $crate::start_span!(@arg_value $arg_name $($arg_value)?)
88 );)*)?
89 }
90
91 $crate::SpanGuard::new(&SOURCE_INFO)
92 }};
93
94 (@arg_value $name:ident) => {
95 $name
96 };
97
98 (@arg_value $name:ident $value:expr) => {
99 $value
100 };
101}
102
103#[derive(PartialEq, Eq, Hash, Clone, Copy)]
104struct Pid(i32);
105
106pub struct SpanGuard {
110 #[cfg(feature = "enable")]
111 pub source: &'static SourceInfo,
112}
113
114pub struct ThreadTraceData {
116 events: Vec<Event>,
117 pid: Pid,
118 tid: Pid,
119 thread_name: Option<String>,
120}
121
122impl ThreadTraceData {
123 pub fn take_current_thread() -> Self {
124 let thread = std::thread::current();
125 Self {
126 events: EVENTS.take(),
127 pid: os::getpid(),
128 tid: os::gettid(),
129 thread_name: thread.name().map(str::to_owned),
130 }
131 }
132}
133
134pub const EVENTS_PER_SPAN: usize = 4;
136
137pub const EVENTS_PER_ARG: usize = 1;
139
140pub const EVENTS_PER_COUNTER: usize = 2;
142
143pub fn current_thread_reserve(additional: usize) {
151 EVENTS.with_borrow_mut(|events| events.reserve(additional))
152}
153
154pub trait RecordArg {
156 fn record_arg(self);
157}
158
159impl RecordArg for bool {
160 fn record_arg(self) {
161 record_event(Event::Bool(self));
162 }
163}
164
165impl RecordArg for f64 {
166 fn record_arg(self) {
167 record_event(Event::F64(self));
168 }
169}
170
171impl RecordArg for u64 {
172 fn record_arg(self) {
173 record_event(Event::U64(self));
174 }
175}
176
177impl RecordArg for u32 {
178 fn record_arg(self) {
179 record_event(Event::U64(self.into()));
180 }
181}
182
183impl RecordArg for u16 {
184 fn record_arg(self) {
185 record_event(Event::U64(self.into()));
186 }
187}
188
189impl RecordArg for u8 {
190 fn record_arg(self) {
191 record_event(Event::U64(self.into()));
192 }
193}
194
195impl RecordArg for usize {
196 fn record_arg(self) {
197 record_event(Event::U64(self as u64));
198 }
199}
200
201impl RecordArg for i64 {
202 fn record_arg(self) {
203 record_event(Event::I64(self));
204 }
205}
206
207impl RecordArg for i32 {
208 fn record_arg(self) {
209 record_event(Event::I64(self.into()));
210 }
211}
212
213impl RecordArg for i16 {
214 fn record_arg(self) {
215 record_event(Event::I64(self.into()));
216 }
217}
218
219impl RecordArg for i8 {
220 fn record_arg(self) {
221 record_event(Event::I64(self.into()));
222 }
223}
224
225impl RecordArg for String {
226 fn record_arg(self) {
227 record_event(Event::String(self));
228 }
229}
230
231impl RecordArg for isize {
232 fn record_arg(self) {
233 record_event(Event::I64(self as i64));
234 }
235}
236
237impl RecordArg for &str {
238 fn record_arg(self) {
239 let mut pending: &[u8] = &[];
240 for chunk in self.as_bytes().chunks(STR_PART_LEN) {
241 if let Some(part_bytes) = pending.first_chunk::<STR_PART_LEN>() {
242 record_event(Event::StrPart(*part_bytes));
243 }
244 pending = chunk;
245 }
246 let mut padded_bytes = [0; STR_PART_LEN];
247 padded_bytes[..pending.len()].copy_from_slice(pending);
248 record_event(Event::StrEnd {
249 len: pending.len() as u8,
250 bytes: padded_bytes,
251 });
252 }
253}
254
255#[doc(hidden)]
256#[derive(Debug)]
257pub enum Event {
258 StartSpan(&'static SourceInfo),
260
261 EndSpan(&'static SourceInfo),
263
264 Timestamp(Instant),
266
267 Bool(bool),
268 U64(u64),
269 I64(i64),
270 F64(f64),
271 String(String),
272
273 StrPart([u8; STR_PART_LEN]),
276
277 StrEnd {
279 len: u8,
280 bytes: [u8; STR_PART_LEN],
281 },
282
283 CounterI64 {
285 uuid: u64,
286 value: i64,
287 },
288
289 CounterF64 {
291 uuid: u64,
292 value: f64,
293 },
294}
295
296const STR_PART_LEN: usize = 15;
298
299#[doc(hidden)]
300#[derive(Debug)]
301pub struct SourceInfo {
302 pub name: &'static str,
303 pub file: &'static str,
304 pub line: u32,
305 pub arg_names: &'static [&'static str],
306}
307
308#[doc(hidden)]
309#[inline(always)]
310pub fn record_event(event: Event) {
311 EVENTS.with_borrow_mut(|events| events.push(event));
312}
313
314thread_local! {
315 static EVENTS: RefCell<Vec<Event>> = const { RefCell::new(Vec::new()) };
316}
317
318thread_local! {
319 static RNG: RefCell<ThreadRng> = RefCell::new(ThreadRng::default());
320}
321
322#[doc(hidden)]
323#[inline(always)]
324pub fn time() -> Instant {
325 Instant::now()
326}
327
328impl Drop for SpanGuard {
329 fn drop(&mut self) {
330 #[cfg(feature = "enable")]
331 if is_enabled() {
332 record_event(Event::EndSpan(self.source));
333 record_event(Event::Timestamp(time()));
334 }
335 }
336}
337
338impl SpanGuard {
339 #[doc(hidden)]
340 #[allow(unused_variables)]
341 pub fn new(source: &'static SourceInfo) -> Self {
342 #[cfg(feature = "enable")]
343 {
344 Self { source }
345 }
346 #[cfg(not(feature = "enable"))]
347 {
348 Self {}
349 }
350 }
351}
352
353const CLOCK_ID: u32 = 6;
354
355static RUNTIME_ENABLED: AtomicBool = AtomicBool::new(false);
356
357pub fn start() -> Result<(), TracingDisabledAtBuildTime> {
360 if !cfg!(feature = "enable") {
361 return Err(TracingDisabledAtBuildTime);
362 }
363
364 RUNTIME_ENABLED.store(true, Ordering::Relaxed);
365 Ok(())
366}
367
368pub fn is_enabled() -> bool {
370 cfg!(feature = "enable") && RUNTIME_ENABLED.load(Ordering::Relaxed)
371}
372
373#[derive(Debug)]
376pub struct TracingDisabledAtBuildTime;
377
378#[derive(Debug)]
380pub struct TracingDisabled;
381
382pub struct TraceBuilder {
399 trace: schema::Trace,
400 pending_interned: Option<schema::InternedData>,
401 name_ids: HashMap<&'static str, u64>,
402 debug_annotation_name_ids: HashMap<&'static str, u64>,
403 source_location_ids: HashMap<(&'static str, u32), u64>,
404 thread_uuids: HashMap<Pid, Uuid>,
405 sequence_id: u32,
406 #[cfg(feature = "fastant")]
407 time_anchor: fastant::Anchor,
408}
409
410impl TraceBuilder {
411 pub fn new() -> Result<TraceBuilder, TracingDisabled> {
412 if !is_enabled() {
413 return Err(TracingDisabled);
414 }
415
416 let sequence_id = RNG.with_borrow_mut(|rng| rng.next_u32());
417
418 let mut builder = TraceBuilder {
419 sequence_id,
420 trace: Default::default(),
421 pending_interned: Default::default(),
422 name_ids: Default::default(),
423 source_location_ids: Default::default(),
424 debug_annotation_name_ids: Default::default(),
425 thread_uuids: Default::default(),
426 #[cfg(feature = "fastant")]
427 time_anchor: fastant::Anchor::new(),
428 };
429
430 builder.add_packet(TracePacket {
431 sequence_flags: Some(
432 schema::trace_packet::SequenceFlags::SeqIncrementalStateCleared as u32,
433 ),
434 ..Default::default()
435 });
436
437 Ok(builder)
438 }
439
440 pub fn process_thread_data(&mut self, thread: &ThreadTraceData) -> &mut Self {
442 let thread_uuid = self.thread_uuid(thread);
443
444 let mut events = thread.events.iter();
445
446 while let Some(event) = events.next() {
447 match event {
448 Event::StartSpan(source_info) => {
449 self.emit_track_event(
450 source_info,
451 schema::track_event::Type::SliceBegin,
452 &mut events,
453 thread_uuid,
454 );
455 }
456 Event::EndSpan(source_info) => {
457 self.emit_track_event(
458 source_info,
459 schema::track_event::Type::SliceEnd,
460 &mut events,
461 thread_uuid,
462 );
463 }
464 Event::CounterI64 { uuid, value } => {
465 self.emit_counter_event(
466 *uuid,
467 &mut events,
468 schema::track_event::CounterValueField::CounterValue(*value),
469 );
470 }
471 Event::CounterF64 { uuid, value } => {
472 self.emit_counter_event(
473 *uuid,
474 &mut events,
475 schema::track_event::CounterValueField::DoubleCounterValue(*value),
476 );
477 }
478 other => panic!("Internal error: Unexpected event {other:?}"),
479 }
480 }
481
482 self
483 }
484
485 pub fn encode_to_vec(&self) -> Vec<u8> {
487 self.trace.encode_to_vec()
488 }
489
490 pub fn write_to_file(&self, path: impl AsRef<Path>) -> Result<(), std::io::Error> {
491 std::fs::write(path, self.encode_to_vec())
492 }
493
494 fn name_id(&mut self, name: &'static str) -> u64 {
495 let next_id = self.name_ids.len() as u64 + 1;
496 *self.name_ids.entry(name).or_insert_with(|| {
497 self.pending_interned
498 .get_or_insert_default()
499 .event_names
500 .push(schema::EventName {
501 iid: Some(next_id),
502 name: Some(name.to_owned()),
503 });
504 next_id
505 })
506 }
507
508 fn debug_annotation_name_id(&mut self, name: &'static str) -> u64 {
509 let next_id = self.debug_annotation_name_ids.len() as u64 + 1;
510 *self
511 .debug_annotation_name_ids
512 .entry(name)
513 .or_insert_with(|| {
514 self.pending_interned
515 .get_or_insert_default()
516 .debug_annotation_names
517 .push(schema::DebugAnnotationName {
518 iid: Some(next_id),
519 name: Some(name.to_owned()),
520 });
521 next_id
522 })
523 }
524
525 fn source_location_id(&mut self, source_location: &'static SourceInfo) -> u64 {
526 let next_id = self.source_location_ids.len() as u64 + 1;
527 *self
528 .source_location_ids
529 .entry((source_location.file, source_location.line))
530 .or_insert_with(|| {
531 self.pending_interned
532 .get_or_insert_default()
533 .source_locations
534 .push(schema::SourceLocation {
535 iid: Some(next_id),
536 file_name: Some(source_location.file.to_owned()),
537 function_name: None,
538 line_number: Some(source_location.line),
539 });
540 next_id
541 })
542 }
543
544 fn emit_track_event(
545 &mut self,
546 source_info: &'static SourceInfo,
547 kind: schema::track_event::Type,
548 events: &mut std::slice::Iter<Event>,
549 thread_uuid: Uuid,
550 ) {
551 let Some(Event::Timestamp(timestamp)) = events.next() else {
552 panic!("Internal error: Timestamp must follow top-level events");
553 };
554
555 let name_id = self.name_id(source_info.name);
556 let source_location_id = self.source_location_id(source_info);
557 let mut track_event = schema::TrackEvent::default();
558 track_event.set_type(kind);
559 track_event.name_field = Some(schema::track_event::NameField::NameIid(name_id));
560 track_event.source_location_field = Some(
561 schema::track_event::SourceLocationField::SourceLocationIid(source_location_id),
562 );
563 track_event.track_uuid = Some(thread_uuid.0);
564
565 if kind == schema::track_event::Type::SliceBegin && !source_info.arg_names.is_empty() {
566 track_event.debug_annotations = source_info
567 .arg_names
568 .iter()
569 .map(|arg_name| {
570 let value = convert_next_arg(events);
571 DebugAnnotation {
572 name_field: Some(schema::debug_annotation::NameField::NameIid(
573 self.debug_annotation_name_id(arg_name),
574 )),
575 value: Some(value),
576 }
577 })
578 .collect();
579 }
580
581 let packet = TracePacket {
582 timestamp: Some(self.get_unix_nanos(*timestamp)),
583 timestamp_clock_id: Some(CLOCK_ID),
584 data: Some(schema::trace_packet::Data::TrackEvent(track_event)),
585 interned_data: self.pending_interned.take(),
586 ..Default::default()
587 };
588
589 self.add_packet(packet);
590 }
591
592 fn emit_counter_event(
593 &mut self,
594 uuid: u64,
595 events: &mut std::slice::Iter<Event>,
596 counter_value_field: schema::track_event::CounterValueField,
597 ) {
598 let Some(Event::Timestamp(timestamp)) = events.next() else {
599 panic!("Internal error: Counter event must be followed by Timestamp");
600 };
601
602 let packet = TracePacket {
603 timestamp: Some(self.get_unix_nanos(*timestamp)),
604 timestamp_clock_id: Some(CLOCK_ID),
605 data: Some(schema::trace_packet::Data::TrackEvent(schema::TrackEvent {
606 track_uuid: Some(uuid),
607 r#type: Some(schema::track_event::Type::Counter as i32),
608 counter_value_field: Some(counter_value_field),
609 ..Default::default()
610 })),
611 ..Default::default()
612 };
613
614 self.add_packet(packet);
615 }
616
617 fn thread_uuid(&mut self, thread: &ThreadTraceData) -> Uuid {
618 if let Some(uuid) = self.thread_uuids.get(&thread.tid) {
619 return *uuid;
620 }
621
622 let uuid = Uuid::new();
623
624 self.add_packet(TracePacket {
625 data: Some(schema::trace_packet::Data::TrackDescriptor(
626 TrackDescriptor {
627 uuid: Some(uuid.0),
628 thread: Some(ThreadDescriptor {
629 pid: Some(thread.pid.0),
630 tid: Some(thread.tid.0),
631 thread_name: thread.thread_name.clone(),
632 }),
633 ..Default::default()
634 },
635 )),
636 ..Default::default()
637 });
638
639 self.thread_uuids.insert(thread.tid, uuid);
640
641 uuid
642 }
643
644 fn add_packet(&mut self, mut packet: TracePacket) {
645 packet.optional_trusted_packet_sequence_id = Some(
646 schema::trace_packet::OptionalTrustedPacketSequenceId::TrustedPacketSequenceId(
647 self.sequence_id,
648 ),
649 );
650 self.trace.packet.push(packet);
651 }
652
653 #[cfg(feature = "fastant")]
654 fn get_unix_nanos(&self, timestamp: Instant) -> u64 {
655 timestamp.as_unix_nanos(&self.time_anchor)
656 }
657
658 #[cfg(not(feature = "fastant"))]
659 fn get_unix_nanos(&self, timestamp: Instant) -> u64 {
660 timestamp
661 .duration_since(std::time::UNIX_EPOCH)
662 .unwrap()
663 .as_nanos() as u64
664 }
665}
666
667fn convert_next_arg(events: &mut std::slice::Iter<'_, Event>) -> schema::debug_annotation::Value {
669 let event = events.next().expect("Internal error: missing arg value");
670
671 use schema::debug_annotation::Value;
672 match event {
673 Event::StartSpan(_) => panic!("Internal error: Unexpected StartSpan"),
674 Event::EndSpan(_) => panic!("Internal error: Unexpected EndSpan"),
675 Event::Timestamp(_) => panic!("Internal error: Unexpected Timestamp"),
676 Event::CounterI64 { .. } => panic!("Internal error: Unexpected CounterI64"),
677 Event::CounterF64 { .. } => panic!("Internal error: Unexpected CounterF64"),
678 Event::Bool(value) => Value::BoolValue(*value),
679 Event::U64(value) => Value::UintValue(*value),
680 Event::I64(value) => Value::IntValue(*value),
681 Event::F64(value) => Value::DoubleValue(*value),
682 Event::String(value) => Value::StringValue(value.clone()),
683 Event::StrPart(bytes) => {
684 let mut merged_bytes = Vec::new();
685 merged_bytes.extend_from_slice(bytes);
686 loop {
687 match events.next() {
688 Some(Event::StrPart(bytes)) => {
689 merged_bytes.extend_from_slice(bytes);
690 }
691 Some(Event::StrEnd { len, bytes }) => {
692 merged_bytes.extend_from_slice(&bytes[..*len as usize]);
693 break Value::StringValue(String::from_utf8(merged_bytes).unwrap());
695 }
696 other => panic!(
697 "Internal error: Unexpected event {other:?} while looking for StrEnd"
698 ),
699 }
700 }
701 }
702 Event::StrEnd { len, bytes } => {
703 Value::StringValue(str::from_utf8(&bytes[..*len as usize]).unwrap().to_owned())
704 }
705 }
706}
707
708impl Uuid {
709 fn new() -> Uuid {
710 Uuid(RNG.with_borrow_mut(|rng| rng.next_u64()))
711 }
712}
713
714impl std::error::Error for TracingDisabledAtBuildTime {}
715
716impl std::fmt::Display for TracingDisabledAtBuildTime {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 write!(
719 f,
720 "The \"enable\" feature of perfetto-recorder is not active"
721 )
722 }
723}
724
725impl std::error::Error for TracingDisabled {}
726
727impl std::fmt::Display for TracingDisabled {
728 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729 write!(f, "The `perfetto_recorder::start()` was not called")
730 }
731}
732
733#[derive(Clone, Copy, Debug)]
734struct Uuid(u64);
735
736#[derive(Debug, Clone)]
738pub enum CounterUnit {
739 Unspecified,
741 TimeNs,
743 Count,
745 SizeBytes,
747 Custom(String),
749}
750
751impl CounterUnit {
752 fn to_proto_unit(&self) -> Option<i32> {
753 Some(match self {
754 CounterUnit::Unspecified => schema::counter_descriptor::Unit::Unspecified,
755 CounterUnit::TimeNs => schema::counter_descriptor::Unit::TimeNs,
756 CounterUnit::Count => schema::counter_descriptor::Unit::Count,
757 CounterUnit::SizeBytes => schema::counter_descriptor::Unit::SizeBytes,
758 CounterUnit::Custom(_) => schema::counter_descriptor::Unit::Unspecified,
759 } as i32)
760 }
761
762 fn to_proto_unit_name(&self) -> Option<String> {
763 if let Self::Custom(name) = self {
764 Some(name.clone())
765 } else {
766 None
767 }
768 }
769}
770
771#[derive(Debug, Clone, Copy)]
773pub struct CounterTrack {
774 uuid: u64,
775}
776
777impl CounterTrack {
778 #[inline(always)]
801 pub fn record_i64(&mut self, timestamp: Instant, value: i64) {
802 if !RUNTIME_ENABLED.load(Ordering::Relaxed) {
803 return;
804 }
805 record_event(Event::CounterI64 {
806 uuid: self.uuid,
807 value,
808 });
809 record_event(Event::Timestamp(timestamp));
810 }
811
812 #[inline(always)]
835 pub fn record_f64(&mut self, timestamp: Instant, value: f64) {
836 if !RUNTIME_ENABLED.load(Ordering::Relaxed) {
837 return;
838 }
839 record_event(Event::CounterF64 {
840 uuid: self.uuid,
841 value,
842 });
843 record_event(Event::Timestamp(timestamp));
844 }
845}
846
847impl TraceBuilder {
848 pub fn create_counter_track(
875 &mut self,
876 name: impl Into<String>,
877 unit: CounterUnit,
878 unit_multiplier: i64,
879 is_incremental: bool,
880 ) -> CounterTrack {
881 let uuid = Uuid::new();
882
883 self.add_packet(TracePacket {
884 data: Some(schema::trace_packet::Data::TrackDescriptor(
885 TrackDescriptor {
886 uuid: Some(uuid.0),
887 parent_uuid: None,
888 process: None,
889 thread: None,
890 counter: Some(schema::CounterDescriptor {
891 unit: unit.to_proto_unit(),
892 unit_name: unit.to_proto_unit_name(),
893 unit_multiplier: Some(unit_multiplier),
894 is_incremental: Some(is_incremental),
895 }),
896 static_or_dynamic_name: Some(
897 schema::track_descriptor::StaticOrDynamicName::Name(name.into()),
898 ),
899 },
900 )),
901 ..Default::default()
902 });
903
904 CounterTrack { uuid: uuid.0 }
905 }
906}
907
908#[cfg(test)]
909mod tests {
910 use super::*;
911
912 #[cfg_attr(not(feature = "enable"), ignore)]
913 #[test]
914 fn test_basic_usage() {
915 start().unwrap();
916 {
917 scope!(
918 "foo",
919 value = 1_u64,
920 foo = 2_i64,
921 baz = "baz",
922 baz_owned = "baz".to_owned()
923 );
924 scope!("bar");
925 }
926
927 let num_events = EVENTS.with_borrow(|events| events.len());
928 assert_eq!(num_events, 12);
929
930 TraceBuilder::new()
931 .unwrap()
932 .process_thread_data(&ThreadTraceData::take_current_thread())
933 .encode_to_vec();
934 }
935
936 #[cfg_attr(feature = "enable", ignore)]
937 #[test]
938 fn test_no_execution_when_disabled() {
939 fn do_not_run() -> u32 {
940 panic!("This should not be called");
941 }
942
943 scope!("foo", value = do_not_run());
944 }
945
946 #[test]
949 fn str_encoding() {
950 for l in 0..100 {
951 let string: String = (0..l)
952 .map(|i| char::from_u32('A' as u32 + i).unwrap())
953 .collect();
954 let str_slice = string.as_str();
955 RecordArg::record_arg(str_slice);
956 let events = EVENTS.take();
957 let mut events = events.iter();
958 match convert_next_arg(&mut events) {
959 schema::debug_annotation::Value::StringValue(actual) => {
960 assert_eq!(actual, string);
961 }
962 other => panic!("Unexpected event: {other:?}"),
963 }
964 assert!(events.next().is_none());
965 }
966 }
967
968 #[cfg_attr(not(feature = "enable"), ignore)]
969 #[test]
970 fn test_counter_tracks() {
971 start().unwrap();
972
973 let mut trace = TraceBuilder::new().unwrap();
974
975 let mut cpu_counter =
977 trace.create_counter_track("CPU Usage", CounterUnit::Custom("%".to_string()), 1, false);
978
979 let mut memory_counter =
980 trace.create_counter_track("Memory", CounterUnit::SizeBytes, 1024 * 1024, false);
981
982 let mut count_counter = trace.create_counter_track(
983 "Events",
984 CounterUnit::Count,
985 1,
986 true, );
988
989 let t1 = time();
991 cpu_counter.record_f64(t1, 42.5);
992 memory_counter.record_i64(t1, 1024);
993 count_counter.record_i64(t1, 100);
994
995 let t2 = time();
996 cpu_counter.record_f64(t2, 75.0);
997 memory_counter.record_i64(t2, 2048);
998 count_counter.record_i64(t2, 50);
999
1000 let thread_data = ThreadTraceData::take_current_thread();
1002 trace.process_thread_data(&thread_data);
1003
1004 let bytes = trace.encode_to_vec();
1006 assert!(!bytes.is_empty());
1007 }
1008}