1#![cfg_attr(feature = "benchmarks", feature(test))]
15#![allow(clippy::identity_op, clippy::new_without_default, clippy::trivially_copy_pass_by_ref)]
16
17#[macro_use]
18extern crate lazy_static;
19extern crate time;
20
21#[macro_use]
22extern crate serde_derive;
23
24extern crate serde;
25
26#[macro_use]
27extern crate log;
28
29extern crate libc;
30
31#[cfg(feature = "benchmarks")]
32extern crate test;
33
34#[cfg(any(test, feature = "json_payload", feature = "chroma_trace_dump"))]
35#[cfg_attr(any(test), macro_use)]
36extern crate serde_json;
37
38mod fixed_lifo_deque;
39mod sys_pid;
40mod sys_tid;
41
42#[cfg(feature = "chrome_trace_event")]
43pub mod chrome_trace_dump;
44
45use crate::fixed_lifo_deque::FixedLifoDeque;
46use std::borrow::Cow;
47use std::cmp;
48use std::collections::HashMap;
49use std::fmt;
50use std::fs;
51use std::hash::{Hash, Hasher};
52use std::mem::size_of;
53use std::path::Path;
54use std::string::ToString;
55use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
56use std::sync::Mutex;
57
58pub type StrCow = Cow<'static, str>;
59
60#[derive(Clone, Debug)]
61pub enum CategoriesT {
62 StaticArray(&'static [&'static str]),
63 DynamicArray(Vec<String>),
64}
65
66trait StringArrayEq<Rhs: ?Sized = Self> {
67 fn arr_eq(&self, other: &Rhs) -> bool;
68}
69
70impl StringArrayEq<[&'static str]> for Vec<String> {
71 fn arr_eq(&self, other: &[&'static str]) -> bool {
72 if self.len() != other.len() {
73 return false;
74 }
75
76 for i in 0..self.len() {
77 if self[i] != other[i] {
78 return false;
79 }
80 }
81 true
82 }
83}
84
85impl StringArrayEq<Vec<String>> for &'static [&'static str] {
86 fn arr_eq(&self, other: &Vec<String>) -> bool {
87 if self.len() != other.len() {
88 return false;
89 }
90 for i in 0..self.len() {
91 if self[i] != other[i] {
92 return false;
93 }
94 }
95 true
96 }
97}
98
99impl PartialEq for CategoriesT {
100 fn eq(&self, other: &CategoriesT) -> bool {
101 match *self {
102 CategoriesT::StaticArray(ref self_arr) => match *other {
103 CategoriesT::StaticArray(ref other_arr) => self_arr.eq(other_arr),
104 CategoriesT::DynamicArray(ref other_arr) => self_arr.arr_eq(other_arr),
105 },
106 CategoriesT::DynamicArray(ref self_arr) => match *other {
107 CategoriesT::StaticArray(ref other_arr) => self_arr.arr_eq(other_arr),
108 CategoriesT::DynamicArray(ref other_arr) => self_arr.eq(other_arr),
109 },
110 }
111 }
112}
113
114impl Eq for CategoriesT {}
115
116impl serde::Serialize for CategoriesT {
117 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118 where
119 S: serde::Serializer,
120 {
121 self.join(",").serialize(serializer)
122 }
123}
124
125impl<'de> serde::Deserialize<'de> for CategoriesT {
126 fn deserialize<D>(deserializer: D) -> Result<CategoriesT, D::Error>
127 where
128 D: serde::Deserializer<'de>,
129 {
130 use serde::de::Visitor;
131 struct CategoriesTVisitor;
132
133 impl<'de> Visitor<'de> for CategoriesTVisitor {
134 type Value = CategoriesT;
135
136 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
137 formatter.write_str("comma-separated strings")
138 }
139
140 fn visit_str<E>(self, v: &str) -> Result<CategoriesT, E>
141 where
142 E: serde::de::Error,
143 {
144 let categories = v.split(',').map(ToString::to_string).collect();
145 Ok(CategoriesT::DynamicArray(categories))
146 }
147 }
148
149 deserializer.deserialize_str(CategoriesTVisitor)
150 }
151}
152
153impl CategoriesT {
154 pub fn join(&self, sep: &str) -> String {
155 match *self {
156 CategoriesT::StaticArray(ref arr) => arr.join(sep),
157 CategoriesT::DynamicArray(ref vec) => vec.join(sep),
158 }
159 }
160}
161
162macro_rules! categories_from_constant_array {
163 ($num_args: expr) => {
164 impl From<&'static [&'static str; $num_args]> for CategoriesT {
165 fn from(c: &'static [&'static str; $num_args]) -> CategoriesT {
166 CategoriesT::StaticArray(c)
167 }
168 }
169 };
170}
171
172categories_from_constant_array!(0);
173categories_from_constant_array!(1);
174categories_from_constant_array!(2);
175categories_from_constant_array!(3);
176categories_from_constant_array!(4);
177categories_from_constant_array!(5);
178categories_from_constant_array!(6);
179categories_from_constant_array!(7);
180categories_from_constant_array!(8);
181categories_from_constant_array!(9);
182categories_from_constant_array!(10);
183
184impl From<Vec<String>> for CategoriesT {
185 fn from(c: Vec<String>) -> CategoriesT {
186 CategoriesT::DynamicArray(c)
187 }
188}
189
190#[cfg(not(feature = "json_payload"))]
191pub type TracePayloadT = StrCow;
192
193#[cfg(feature = "json_payload")]
194pub type TracePayloadT = serde_json::Value;
195
196#[derive(Copy, Clone)]
198pub struct Config {
199 sample_limit_count: usize,
200}
201
202impl Config {
203 pub fn with_limit_bytes(size: usize) -> Self {
206 Self::with_limit_count(size / size_of::<Sample>())
207 }
208
209 pub fn with_limit_count(limit: usize) -> Self {
212 Self { sample_limit_count: limit }
213 }
214
215 pub fn default() -> Self {
217 Self::with_limit_bytes(1 * 1024 * 1024)
219 }
220
221 pub fn max_size_in_bytes(self) -> usize {
225 self.sample_limit_count * size_of::<Sample>()
226 }
227
228 pub fn max_samples(self) -> usize {
230 self.sample_limit_count
231 }
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
235pub enum SampleEventType {
236 DurationBegin,
237 DurationEnd,
238 CompleteDuration,
239 Instant,
240 AsyncStart,
241 AsyncInstant,
242 AsyncEnd,
243 FlowStart,
244 FlowInstant,
245 FlowEnd,
246 ObjectCreated,
247 ObjectSnapshot,
248 ObjectDestroyed,
249 Metadata,
250}
251
252impl SampleEventType {
253 #[inline]
256 fn into_chrome_id(self) -> char {
257 match self {
258 SampleEventType::DurationBegin => 'B',
259 SampleEventType::DurationEnd => 'E',
260 SampleEventType::CompleteDuration => 'X',
261 SampleEventType::Instant => 'i',
262 SampleEventType::AsyncStart => 'b',
263 SampleEventType::AsyncInstant => 'n',
264 SampleEventType::AsyncEnd => 'e',
265 SampleEventType::FlowStart => 's',
266 SampleEventType::FlowInstant => 't',
267 SampleEventType::FlowEnd => 'f',
268 SampleEventType::ObjectCreated => 'N',
269 SampleEventType::ObjectSnapshot => 'O',
270 SampleEventType::ObjectDestroyed => 'D',
271 SampleEventType::Metadata => 'M',
272 }
273 }
274
275 #[inline]
276 fn from_chrome_id(symbol: char) -> Self {
277 match symbol {
278 'B' => SampleEventType::DurationBegin,
279 'E' => SampleEventType::DurationEnd,
280 'X' => SampleEventType::CompleteDuration,
281 'i' => SampleEventType::Instant,
282 'b' => SampleEventType::AsyncStart,
283 'n' => SampleEventType::AsyncInstant,
284 'e' => SampleEventType::AsyncEnd,
285 's' => SampleEventType::FlowStart,
286 't' => SampleEventType::FlowInstant,
287 'f' => SampleEventType::FlowEnd,
288 'N' => SampleEventType::ObjectCreated,
289 'O' => SampleEventType::ObjectSnapshot,
290 'D' => SampleEventType::ObjectDestroyed,
291 'M' => SampleEventType::Metadata,
292 _ => panic!("Unexpected chrome sample type '{}'", symbol),
293 }
294 }
295}
296
297#[derive(Clone, Debug, PartialEq, Eq)]
298enum MetadataType {
299 ProcessName {
300 name: String,
301 },
302 #[allow(dead_code)]
303 ProcessLabels {
304 labels: String,
305 },
306 #[allow(dead_code)]
307 ProcessSortIndex {
308 sort_index: i32,
309 },
310 ThreadName {
311 name: String,
312 },
313 #[allow(dead_code)]
314 ThreadSortIndex {
315 sort_index: i32,
316 },
317}
318
319impl MetadataType {
320 fn sample_name(&self) -> &'static str {
321 match *self {
322 MetadataType::ProcessName { .. } => "process_name",
323 MetadataType::ProcessLabels { .. } => "process_labels",
324 MetadataType::ProcessSortIndex { .. } => "process_sort_index",
325 MetadataType::ThreadName { .. } => "thread_name",
326 MetadataType::ThreadSortIndex { .. } => "thread_sort_index",
327 }
328 }
329
330 fn consume(self) -> (Option<String>, Option<i32>) {
331 match self {
332 MetadataType::ProcessName { name } => (Some(name), None),
333 MetadataType::ThreadName { name } => (Some(name), None),
334 MetadataType::ProcessSortIndex { sort_index } => (None, Some(sort_index)),
335 MetadataType::ThreadSortIndex { sort_index } => (None, Some(sort_index)),
336 MetadataType::ProcessLabels { .. } => (None, None),
337 }
338 }
339}
340
341#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
342pub struct SampleArgs {
343 #[serde(rename = "xi_payload")]
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub payload: Option<TracePayloadT>,
348
349 #[serde(rename = "name")]
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub metadata_name: Option<StrCow>,
355
356 #[serde(rename = "sort_index")]
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub metadata_sort_index: Option<i32>,
360}
361
362#[inline]
363fn ns_to_us(ns: u64) -> u64 {
364 ns / 1000
365}
366
367fn serialize_event_type<S>(ph: &SampleEventType, s: S) -> Result<S::Ok, S::Error>
369where
370 S: serde::Serializer,
371{
372 s.serialize_char(ph.into_chrome_id())
373}
374
375fn deserialize_event_type<'de, D>(d: D) -> Result<SampleEventType, D::Error>
376where
377 D: serde::Deserializer<'de>,
378{
379 serde::Deserialize::deserialize(d).map(SampleEventType::from_chrome_id)
380}
381
382#[derive(Serialize, Deserialize, Clone, Debug)]
387pub struct Sample {
388 pub name: StrCow,
390 #[serde(rename = "cat")]
392 #[serde(skip_serializing_if = "Option::is_none")]
393 pub categories: Option<CategoriesT>,
394 #[serde(rename = "ts")]
396 pub timestamp_us: u64,
397 #[serde(rename = "ph")]
399 #[serde(serialize_with = "serialize_event_type")]
400 #[serde(deserialize_with = "deserialize_event_type")]
401 pub event_type: SampleEventType,
402 #[serde(rename = "dur")]
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub duration_us: Option<u64>,
405 pub pid: u64,
407 pub tid: u64,
410 #[serde(skip_serializing)]
411 pub thread_name: Option<StrCow>,
412 #[serde(skip_serializing_if = "Option::is_none")]
413 pub args: Option<SampleArgs>,
414}
415
416fn to_cow_str<S>(s: S) -> StrCow
417where
418 S: Into<StrCow>,
419{
420 s.into()
421}
422
423impl Sample {
424 fn thread_name() -> Option<StrCow> {
425 let thread = std::thread::current();
426 thread.name().map(|ref s| to_cow_str(s.to_string()))
427 }
428
429 pub fn new_duration_marker<S, C>(
432 name: S,
433 categories: C,
434 payload: Option<TracePayloadT>,
435 event_type: SampleEventType,
436 ) -> Self
437 where
438 S: Into<StrCow>,
439 C: Into<CategoriesT>,
440 {
441 Self {
442 name: name.into(),
443 categories: Some(categories.into()),
444 timestamp_us: ns_to_us(time::precise_time_ns()),
445 event_type,
446 duration_us: None,
447 tid: sys_tid::current_tid().unwrap(),
448 thread_name: Sample::thread_name(),
449 pid: sys_pid::current_pid(),
450 args: Some(SampleArgs { payload, metadata_name: None, metadata_sort_index: None }),
451 }
452 }
453
454 pub fn new_duration<S, C>(
456 name: S,
457 categories: C,
458 payload: Option<TracePayloadT>,
459 start_ns: u64,
460 duration_ns: u64,
461 ) -> Self
462 where
463 S: Into<StrCow>,
464 C: Into<CategoriesT>,
465 {
466 Self {
467 name: name.into(),
468 categories: Some(categories.into()),
469 timestamp_us: ns_to_us(start_ns),
470 event_type: SampleEventType::CompleteDuration,
471 duration_us: Some(ns_to_us(duration_ns)),
472 tid: sys_tid::current_tid().unwrap(),
473 thread_name: Sample::thread_name(),
474 pid: sys_pid::current_pid(),
475 args: Some(SampleArgs { payload, metadata_name: None, metadata_sort_index: None }),
476 }
477 }
478
479 pub fn new_instant<S, C>(name: S, categories: C, payload: Option<TracePayloadT>) -> Self
481 where
482 S: Into<StrCow>,
483 C: Into<CategoriesT>,
484 {
485 Self {
486 name: name.into(),
487 categories: Some(categories.into()),
488 timestamp_us: ns_to_us(time::precise_time_ns()),
489 event_type: SampleEventType::Instant,
490 duration_us: None,
491 tid: sys_tid::current_tid().unwrap(),
492 thread_name: Sample::thread_name(),
493 pid: sys_pid::current_pid(),
494 args: Some(SampleArgs { payload, metadata_name: None, metadata_sort_index: None }),
495 }
496 }
497
498 fn new_metadata(timestamp_ns: u64, meta: MetadataType, tid: u64) -> Self {
499 let sample_name = to_cow_str(meta.sample_name());
500 let (metadata_name, sort_index) = meta.consume();
501
502 Self {
503 name: sample_name,
504 categories: None,
505 timestamp_us: ns_to_us(timestamp_ns),
506 event_type: SampleEventType::Metadata,
507 duration_us: None,
508 tid,
509 thread_name: None,
510 pid: sys_pid::current_pid(),
511 args: Some(SampleArgs {
512 payload: None,
513 metadata_name: metadata_name.map(Cow::Owned),
514 metadata_sort_index: sort_index,
515 }),
516 }
517 }
518}
519
520impl PartialEq for Sample {
521 fn eq(&self, other: &Sample) -> bool {
522 self.timestamp_us == other.timestamp_us
523 && self.name == other.name
524 && self.categories == other.categories
525 && self.pid == other.pid
526 && self.tid == other.tid
527 && self.event_type == other.event_type
528 && self.args == other.args
529 }
530}
531
532impl Eq for Sample {}
533
534impl PartialOrd for Sample {
535 fn partial_cmp(&self, other: &Sample) -> Option<cmp::Ordering> {
536 Some(self.cmp(other))
537 }
538}
539
540impl Ord for Sample {
541 fn cmp(&self, other: &Sample) -> cmp::Ordering {
542 self.timestamp_us.cmp(&other.timestamp_us)
543 }
544}
545
546impl Hash for Sample {
547 fn hash<H: Hasher>(&self, state: &mut H) {
548 (self.pid, self.timestamp_us).hash(state);
549 }
550}
551
552#[must_use]
553pub struct SampleGuard<'a> {
554 sample: Option<Sample>,
555 trace: Option<&'a Trace>,
556}
557
558impl<'a> SampleGuard<'a> {
559 #[inline]
560 pub fn new_disabled() -> Self {
561 Self { sample: None, trace: None }
562 }
563
564 #[inline]
565 fn new<S, C>(trace: &'a Trace, name: S, categories: C, payload: Option<TracePayloadT>) -> Self
566 where
567 S: Into<StrCow>,
568 C: Into<CategoriesT>,
569 {
570 let guard = Self {
574 sample: Some(Sample::new_duration_marker(
575 name,
576 categories,
577 payload,
578 SampleEventType::DurationBegin,
579 )),
580 trace: Some(&trace),
581 };
582 trace.record(guard.sample.as_ref().unwrap().clone());
583 guard
584 }
585}
586
587impl<'a> Drop for SampleGuard<'a> {
588 fn drop(&mut self) {
589 if let Some(ref mut trace) = self.trace {
590 let mut sample = self.sample.take().unwrap();
591 sample.timestamp_us = ns_to_us(time::precise_time_ns());
592 sample.event_type = SampleEventType::DurationEnd;
593 trace.record(sample);
594 }
595 }
596}
597
598fn exe_name() -> Option<String> {
601 match std::env::current_exe() {
602 Ok(exe_name) => match exe_name.clone().file_name() {
603 Some(filename) => filename.to_str().map(ToString::to_string),
604 None => {
605 let full_path = exe_name.into_os_string();
606 let full_path_str = full_path.into_string();
607 match full_path_str {
608 Ok(s) => Some(s),
609 Err(e) => {
610 warn!("Failed to get string representation: {:?}", e);
611 None
612 }
613 }
614 }
615 },
616 Err(ref e) => {
617 warn!("Failed to get path to current exe: {:?}", e);
618 None
619 }
620 }
621}
622
623pub struct Trace {
625 enabled: AtomicBool,
626 samples: Mutex<FixedLifoDeque<Sample>>,
627}
628
629impl Trace {
630 pub fn disabled() -> Self {
631 Self { enabled: AtomicBool::new(false), samples: Mutex::new(FixedLifoDeque::new()) }
632 }
633
634 pub fn enabled(config: Config) -> Self {
635 Self {
636 enabled: AtomicBool::new(true),
637 samples: Mutex::new(FixedLifoDeque::with_limit(config.max_samples())),
638 }
639 }
640
641 pub fn disable(&self) {
642 let mut all_samples = self.samples.lock().unwrap();
643 all_samples.reset_limit(0);
644 self.enabled.store(false, AtomicOrdering::Relaxed);
645 }
646
647 #[inline]
648 pub fn enable(&self) {
649 self.enable_config(Config::default());
650 }
651
652 pub fn enable_config(&self, config: Config) {
653 let mut all_samples = self.samples.lock().unwrap();
654 all_samples.reset_limit(config.max_samples());
655 self.enabled.store(true, AtomicOrdering::Relaxed);
656 }
657
658 pub fn get_samples_count(&self) -> usize {
661 self.samples.lock().unwrap().len()
662 }
663
664 pub fn get_samples_limit(&self) -> usize {
666 self.samples.lock().unwrap().limit()
667 }
668
669 #[inline]
670 pub(crate) fn record(&self, sample: Sample) {
671 let mut all_samples = self.samples.lock().unwrap();
672 all_samples.push_back(sample);
673 }
674
675 pub fn is_enabled(&self) -> bool {
676 self.enabled.load(AtomicOrdering::Relaxed)
677 }
678
679 pub fn instant<S, C>(&self, name: S, categories: C)
680 where
681 S: Into<StrCow>,
682 C: Into<CategoriesT>,
683 {
684 if self.is_enabled() {
685 self.record(Sample::new_instant(name, categories, None));
686 }
687 }
688
689 pub fn instant_payload<S, C, P>(&self, name: S, categories: C, payload: P)
690 where
691 S: Into<StrCow>,
692 C: Into<CategoriesT>,
693 P: Into<TracePayloadT>,
694 {
695 if self.is_enabled() {
696 self.record(Sample::new_instant(name, categories, Some(payload.into())));
697 }
698 }
699
700 pub fn block<S, C>(&self, name: S, categories: C) -> SampleGuard
701 where
702 S: Into<StrCow>,
703 C: Into<CategoriesT>,
704 {
705 if !self.is_enabled() {
706 SampleGuard::new_disabled()
707 } else {
708 SampleGuard::new(&self, name, categories, None)
709 }
710 }
711
712 pub fn block_payload<S, C, P>(&self, name: S, categories: C, payload: P) -> SampleGuard
713 where
714 S: Into<StrCow>,
715 C: Into<CategoriesT>,
716 P: Into<TracePayloadT>,
717 {
718 if !self.is_enabled() {
719 SampleGuard::new_disabled()
720 } else {
721 SampleGuard::new(&self, name, categories, Some(payload.into()))
722 }
723 }
724
725 pub fn closure<S, C, F, R>(&self, name: S, categories: C, closure: F) -> R
726 where
727 S: Into<StrCow>,
728 C: Into<CategoriesT>,
729 F: FnOnce() -> R,
730 {
731 let start = time::precise_time_ns();
733 let result = closure();
734 let end = time::precise_time_ns();
735 if self.is_enabled() {
736 self.record(Sample::new_duration(name, categories, None, start, end - start));
737 }
738 result
739 }
740
741 pub fn closure_payload<S, C, P, F, R>(
742 &self,
743 name: S,
744 categories: C,
745 closure: F,
746 payload: P,
747 ) -> R
748 where
749 S: Into<StrCow>,
750 C: Into<CategoriesT>,
751 P: Into<TracePayloadT>,
752 F: FnOnce() -> R,
753 {
754 let start = time::precise_time_ns();
756 let result = closure();
757 let end = time::precise_time_ns();
758 if self.is_enabled() {
759 self.record(Sample::new_duration(
760 name,
761 categories,
762 Some(payload.into()),
763 start,
764 end - start,
765 ));
766 }
767 result
768 }
769
770 pub fn samples_cloned_unsorted(&self) -> Vec<Sample> {
771 let all_samples = self.samples.lock().unwrap();
772 if all_samples.is_empty() {
773 return Vec::with_capacity(0);
774 }
775
776 let mut as_vec = Vec::with_capacity(all_samples.len() + 10);
777 let first_sample_timestamp = all_samples.front().map_or(0, |ref s| s.timestamp_us);
778 let tid =
779 all_samples.front().map_or_else(|| sys_tid::current_tid().unwrap(), |ref s| s.tid);
780
781 if let Some(exe_name) = exe_name() {
782 as_vec.push(Sample::new_metadata(
783 first_sample_timestamp,
784 MetadataType::ProcessName { name: exe_name },
785 tid,
786 ));
787 }
788
789 let mut thread_names: HashMap<u64, StrCow> = HashMap::new();
790
791 for sample in all_samples.iter() {
792 if let Some(ref thread_name) = sample.thread_name {
793 let previous_name = thread_names.insert(sample.tid, thread_name.clone());
794 if previous_name.is_none() || previous_name.unwrap() != *thread_name {
795 as_vec.push(Sample::new_metadata(
796 first_sample_timestamp,
797 MetadataType::ThreadName { name: thread_name.to_string() },
798 sample.tid,
799 ));
800 }
801 }
802 }
803
804 as_vec.extend(all_samples.iter().cloned());
805 as_vec
806 }
807
808 #[inline]
809 pub fn samples_cloned_sorted(&self) -> Vec<Sample> {
810 let mut samples = self.samples_cloned_unsorted();
811 samples.sort_unstable();
812 samples
813 }
814
815 pub fn save<P: AsRef<Path>>(
816 &self,
817 path: P,
818 sort: bool,
819 ) -> Result<(), chrome_trace_dump::Error> {
820 let traces = if sort { samples_cloned_sorted() } else { samples_cloned_unsorted() };
821 let path: &Path = path.as_ref();
822
823 if path.exists() {
824 return Err(chrome_trace_dump::Error::already_exists());
825 }
826
827 let mut trace_file = fs::File::create(&path)?;
828
829 chrome_trace_dump::serialize(&traces, &mut trace_file)
830 }
831}
832
833lazy_static! {
834 static ref TRACE: Trace = Trace::disabled();
835}
836
837#[inline]
840pub fn enable_tracing() {
841 TRACE.enable();
842}
843
844#[inline]
847pub fn enable_tracing_with_config(config: Config) {
848 TRACE.enable_config(config);
849}
850
851#[inline]
853pub fn disable_tracing() {
854 TRACE.disable();
855}
856
857#[inline]
860pub fn is_enabled() -> bool {
861 TRACE.is_enabled()
862}
863
864#[inline]
887pub fn trace<S, C>(name: S, categories: C)
888where
889 S: Into<StrCow>,
890 C: Into<CategoriesT>,
891{
892 TRACE.instant(name, categories);
893}
894
895#[inline]
931pub fn trace_payload<S, C, P>(name: S, categories: C, payload: P)
932where
933 S: Into<StrCow>,
934 C: Into<CategoriesT>,
935 P: Into<TracePayloadT>,
936{
937 TRACE.instant_payload(name, categories, payload);
938}
939
940#[inline]
979pub fn trace_block<'a, S, C>(name: S, categories: C) -> SampleGuard<'a>
980where
981 S: Into<StrCow>,
982 C: Into<CategoriesT>,
983{
984 TRACE.block(name, categories)
985}
986
987#[inline]
990pub fn trace_block_payload<'a, S, C, P>(name: S, categories: C, payload: P) -> SampleGuard<'a>
991where
992 S: Into<StrCow>,
993 C: Into<CategoriesT>,
994 P: Into<TracePayloadT>,
995{
996 TRACE.block_payload(name, categories, payload)
997}
998
999#[inline]
1035pub fn trace_closure<S, C, F, R>(name: S, categories: C, closure: F) -> R
1036where
1037 S: Into<StrCow>,
1038 C: Into<CategoriesT>,
1039 F: FnOnce() -> R,
1040{
1041 TRACE.closure(name, categories, closure)
1042}
1043
1044#[inline]
1047pub fn trace_closure_payload<S, C, P, F, R>(name: S, categories: C, closure: F, payload: P) -> R
1048where
1049 S: Into<StrCow>,
1050 C: Into<CategoriesT>,
1051 P: Into<TracePayloadT>,
1052 F: FnOnce() -> R,
1053{
1054 TRACE.closure_payload(name, categories, closure, payload)
1055}
1056
1057#[inline]
1058pub fn samples_len() -> usize {
1059 TRACE.get_samples_count()
1060}
1061
1062#[inline]
1073pub fn samples_cloned_unsorted() -> Vec<Sample> {
1074 TRACE.samples_cloned_unsorted()
1075}
1076
1077#[inline]
1082pub fn samples_cloned_sorted() -> Vec<Sample> {
1083 TRACE.samples_cloned_sorted()
1084}
1085
1086#[inline]
1090pub fn save<P: AsRef<Path>>(path: P, sort: bool) -> Result<(), chrome_trace_dump::Error> {
1091 TRACE.save(path, sort)
1092}
1093
1094#[cfg(test)]
1095#[rustfmt::skip]
1096mod tests {
1097 use super::*;
1098 #[cfg(feature = "benchmarks")]
1099 use test::Bencher;
1100 #[cfg(feature = "benchmarks")]
1101 use test::black_box;
1102
1103 #[cfg(not(feature = "json_payload"))]
1104 fn to_payload(value: &'static str) -> &'static str {
1105 value
1106 }
1107
1108 #[cfg(feature = "json_payload")]
1109 fn to_payload(value: &'static str) -> TracePayloadT {
1110 json!({"test": value})
1111 }
1112
1113 #[test]
1114 fn test_samples_pulse() {
1115 let trace = Trace::enabled(Config::with_limit_count(10));
1116 for _i in 0..50 {
1117 trace.instant("test_samples_pulse", &["test"]);
1118 }
1119 }
1120
1121 #[test]
1122 fn test_samples_block() {
1123 let trace = Trace::enabled(Config::with_limit_count(10));
1124 for _i in 0..50 {
1125 let _ = trace.block("test_samples_block", &["test"]);
1126 }
1127 }
1128
1129 #[test]
1130 fn test_samples_closure() {
1131 let trace = Trace::enabled(Config::with_limit_count(10));
1132 for _i in 0..50 {
1133 trace.closure("test_samples_closure", &["test"], || {});
1134 }
1135 }
1136
1137 #[test]
1138 fn test_disable_drops_all_samples() {
1139 let trace = Trace::enabled(Config::with_limit_count(10));
1140 assert_eq!(trace.is_enabled(), true);
1141 trace.instant("1", &["test"]);
1142 trace.instant("2", &["test"]);
1143 trace.instant("3", &["test"]);
1144 trace.instant("4", &["test"]);
1145 trace.instant("5", &["test"]);
1146 assert_eq!(trace.get_samples_count(), 5);
1147 assert_eq!(trace.samples_cloned_unsorted().len(), 7);
1149 trace.disable();
1150 assert_eq!(trace.get_samples_count(), 0);
1151 }
1152
1153 #[test]
1154 fn test_get_samples() {
1155 let trace = Trace::enabled(Config::with_limit_count(20));
1156 assert_eq!(trace.samples_cloned_unsorted().len(), 0);
1157
1158 assert_eq!(trace.is_enabled(), true);
1159 assert_eq!(trace.get_samples_limit(), 20);
1160 assert_eq!(trace.samples_cloned_unsorted().len(), 0);
1161
1162 trace.closure_payload("x", &["test"], || (),
1163 to_payload("test_get_samples"));
1164 assert_eq!(trace.get_samples_count(), 1);
1165 assert_eq!(trace.samples_cloned_unsorted().len(), 3);
1167
1168 trace.closure_payload("y", &["test"], || {},
1169 to_payload("test_get_samples"));
1170 assert_eq!(trace.samples_cloned_unsorted().len(), 4);
1171
1172 trace.closure_payload("z", &["test"], || {},
1173 to_payload("test_get_samples"));
1174
1175 let snapshot = trace.samples_cloned_unsorted();
1176 assert_eq!(snapshot.len(), 5);
1177
1178 assert_eq!(snapshot[0].name, "process_name");
1179 assert_eq!(snapshot[0].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1180 assert_eq!(snapshot[1].name, "thread_name");
1181 assert_eq!(snapshot[1].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1182 assert_eq!(snapshot[2].name, "x");
1183 assert_eq!(snapshot[3].name, "y");
1184 assert_eq!(snapshot[4].name, "z");
1185 }
1186
1187 #[test]
1188 fn test_trace_disabled() {
1189 let trace = Trace::disabled();
1190 assert_eq!(trace.get_samples_limit(), 0);
1191 assert_eq!(trace.get_samples_count(), 0);
1192
1193 {
1194 trace.instant("something", &[]);
1195 let _x = trace.block("something", &[]);
1196 trace.closure("something", &[], || ());
1197 }
1198
1199 assert_eq!(trace.get_samples_count(), 0);
1200 }
1201
1202 #[test]
1203 fn test_get_samples_nested_trace() {
1204 let trace = Trace::enabled(Config::with_limit_count(11));
1205 assert_eq!(trace.is_enabled(), true);
1206 assert_eq!(trace.get_samples_limit(), 11);
1207
1208 trace.closure_payload("x", &["test"], || {
1216 trace.instant_payload("a", &["test"], to_payload("test_get_samples_nested_trace"));
1217 trace.closure_payload("y", &["test"], || {
1218 trace.instant_payload("b", &["test"], to_payload("test_get_samples_nested_trace"));
1219 }, to_payload("test_get_samples_nested_trace"));
1220 let _ = trace.block_payload("z", &["test"], to_payload("test_get_samples_nested_trace"));
1221 trace.instant_payload("c", &["test"], to_payload("test_get_samples_nested_trace"));
1222 }, to_payload("test_get_samples_nested_trace"));
1223
1224 let snapshot = trace.samples_cloned_unsorted();
1225 assert_eq!(snapshot.len(), 9);
1227
1228 assert_eq!(snapshot[0].name, "process_name");
1229 assert_eq!(snapshot[0].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1230 assert_eq!(snapshot[1].name, "thread_name");
1231 assert_eq!(snapshot[1].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1232 assert_eq!(snapshot[2].name, "a");
1233 assert_eq!(snapshot[3].name, "b");
1234 assert_eq!(snapshot[4].name, "y");
1235 assert_eq!(snapshot[5].name, "z");
1236 assert_eq!(snapshot[6].name, "z");
1237 assert_eq!(snapshot[7].name, "c");
1238 assert_eq!(snapshot[8].name, "x");
1239 }
1240
1241 #[test]
1242 fn test_get_sorted_samples() {
1243 let trace = Trace::enabled(Config::with_limit_count(10));
1244
1245 trace.closure_payload("x", &["test"], || {
1258 std::thread::sleep(std::time::Duration::new(0, 1000));
1259 trace.instant_payload("a", &["test"], to_payload("test_get_sorted_samples"));
1260 trace.closure_payload("y", &["test"], || {
1261 std::thread::sleep(std::time::Duration::new(0, 1000));
1262 trace.instant_payload("b", &["test"], to_payload("test_get_sorted_samples"));
1263 }, to_payload("test_get_sorted_samples"));
1264 let _ = trace.block_payload("z", &["test"], to_payload("test_get_sorted_samples"));
1265 trace.instant("c", &["test"]);
1266 }, to_payload("test_get_sorted_samples"));
1267
1268 let snapshot = trace.samples_cloned_sorted();
1269 assert_eq!(snapshot.len(), 9);
1271
1272 assert_eq!(snapshot[0].name, "process_name");
1273 assert_eq!(snapshot[0].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1274 assert_eq!(snapshot[1].name, "thread_name");
1275 assert_eq!(snapshot[1].args.as_ref().unwrap().metadata_name.as_ref().is_some(), true);
1276 assert_eq!(snapshot[2].name, "x");
1277 assert_eq!(snapshot[3].name, "a");
1278 assert_eq!(snapshot[4].name, "y");
1279 assert_eq!(snapshot[5].name, "b");
1280 assert_eq!(snapshot[6].name, "z");
1281 assert_eq!(snapshot[7].name, "z");
1282 assert_eq!(snapshot[8].name, "c");
1283 }
1284
1285 #[test]
1286 fn test_cross_process_samples() {
1287 let mut samples = vec![
1288 Sample::new_instant("local pid", &[], None),
1289 Sample::new_instant("remote pid", &[], None)];
1290 samples[0].pid = 1;
1291 samples[0].timestamp_us = 10;
1292
1293 samples[1].pid = 2;
1294 samples[1].timestamp_us = 5;
1295
1296 samples.sort();
1297
1298 assert_eq!(samples[0].name, "remote pid");
1299 assert_eq!(samples[1].name, "local pid");
1300 }
1301
1302 #[cfg(feature = "benchmarks")]
1303 #[bench]
1304 fn bench_trace_instant_disabled(b: &mut Bencher) {
1305 let trace = Trace::disabled();
1306
1307 b.iter(|| black_box(trace.instant("nothing", &["benchmark"])));
1308 }
1309
1310 #[cfg(feature = "benchmarks")]
1311 #[bench]
1312 fn bench_trace_instant(b: &mut Bencher) {
1313 let trace = Trace::enabled(Config::default());
1314 b.iter(|| black_box(trace.instant("something", &["benchmark"])));
1315 }
1316
1317 #[cfg(feature = "benchmarks")]
1318 #[bench]
1319 fn bench_trace_instant_with_payload(b: &mut Bencher) {
1320 let trace = Trace::enabled(Config::default());
1321 b.iter(|| black_box(trace.instant_payload(
1322 "something", &["benchmark"],
1323 to_payload("some description of the trace"))));
1324 }
1325
1326 #[cfg(feature = "benchmarks")]
1327 #[bench]
1328 fn bench_trace_block_disabled(b: &mut Bencher) {
1329 let trace = Trace::disabled();
1330 b.iter(|| black_box(trace.block("something", &["benchmark"])));
1331 }
1332
1333 #[cfg(feature = "benchmarks")]
1334 #[bench]
1335 fn bench_trace_block(b: &mut Bencher) {
1336 let trace = Trace::enabled(Config::default());
1337 b.iter(|| black_box(trace.block("something", &["benchmark"])));
1338 }
1339
1340
1341 #[cfg(feature = "benchmarks")]
1342 #[bench]
1343 fn bench_trace_block_payload(b: &mut Bencher) {
1344 let trace = Trace::enabled(Config::default());
1345 b.iter(|| {
1346 black_box(|| {
1347 let _ = trace.block_payload(
1348 "something", &["benchmark"],
1349 to_payload("some payload for the block"));
1350 });
1351 });
1352 }
1353
1354 #[cfg(feature = "benchmarks")]
1355 #[bench]
1356 fn bench_trace_closure_disabled(b: &mut Bencher) {
1357 let trace = Trace::disabled();
1358
1359 b.iter(|| black_box(trace.closure("something", &["benchmark"], || {})));
1360 }
1361
1362 #[cfg(feature = "benchmarks")]
1363 #[bench]
1364 fn bench_trace_closure(b: &mut Bencher) {
1365 let trace = Trace::enabled(Config::default());
1366 b.iter(|| black_box(trace.closure("something", &["benchmark"], || {})));
1367 }
1368
1369 #[cfg(feature = "benchmarks")]
1370 #[bench]
1371 fn bench_trace_closure_payload(b: &mut Bencher) {
1372 let trace = Trace::enabled(Config::default());
1373 b.iter(|| black_box(trace.closure_payload(
1374 "something", &["benchmark"], || {},
1375 to_payload("some description of the closure"))));
1376 }
1377
1378 #[cfg(feature = "benchmarks")]
1380 #[bench]
1381 fn bench_single_timestamp(b: &mut Bencher) {
1382 b.iter(|| black_box(time::precise_time_ns()));
1383 }
1384
1385 #[cfg(feature = "benchmarks")]
1388 #[bench]
1389 fn bench_two_timestamps(b: &mut Bencher) {
1390 b.iter(|| {
1391 black_box(time::precise_time_ns());
1392 black_box(time::precise_time_ns());
1393 });
1394 }
1395
1396 #[cfg(feature = "benchmarks")]
1397 #[bench]
1398 fn bench_get_tid(b: &mut Bencher) {
1399 b.iter(|| black_box(sys_tid::current_tid()));
1400 }
1401
1402 #[cfg(feature = "benchmarks")]
1403 #[bench]
1404 fn bench_get_pid(b: &mut Bencher) {
1405 b.iter(|| sys_pid::current_pid());
1406 }
1407}