xi_trace/
lib.rs

1// Copyright 2018 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14#![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/// How tracing should be configured.
197#[derive(Copy, Clone)]
198pub struct Config {
199    sample_limit_count: usize,
200}
201
202impl Config {
203    /// The maximum number of bytes the tracing data should take up.  This limit
204    /// won't be exceeded by the underlying storage itself (i.e. rounds down).
205    pub fn with_limit_bytes(size: usize) -> Self {
206        Self::with_limit_count(size / size_of::<Sample>())
207    }
208
209    /// The maximum number of entries the tracing data should allow.  Total
210    /// storage allocated will be limit * size_of<Sample>
211    pub fn with_limit_count(limit: usize) -> Self {
212        Self { sample_limit_count: limit }
213    }
214
215    /// The default amount of storage to allocate for tracing.  Currently 1 MB.
216    pub fn default() -> Self {
217        // 1 MB
218        Self::with_limit_bytes(1 * 1024 * 1024)
219    }
220
221    /// The maximum amount of space the tracing data will take up.  This does
222    /// not account for any overhead of storing the data itself (i.e. pointer to
223    /// the heap, counters, etc); just the data itself.
224    pub fn max_size_in_bytes(self) -> usize {
225        self.sample_limit_count * size_of::<Sample>()
226    }
227
228    /// The maximum number of samples that should be stored.
229    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    // TODO(vlovich): Replace all of this with serde flatten + rename once
254    // https://github.com/serde-rs/serde/issues/1189 is fixed.
255    #[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    /// An arbitrary payload to associate with the sample.  The type is
344    /// controlled by features (default string).
345    #[serde(rename = "xi_payload")]
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub payload: Option<TracePayloadT>,
348
349    /// The name to associate with the pid/tid.  Whether it's associated with
350    /// the pid or the tid depends on the name of the event
351    /// via process_name/thread_name respectively.
352    #[serde(rename = "name")]
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub metadata_name: Option<StrCow>,
355
356    /// Sorting priority between processes/threads in the view.
357    #[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
367//NOTE: serde requires this to take a reference
368fn 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/// Stores the relevant data about a sample for later serialization.
383/// The payload associated with any sample is by default a string but may be
384/// configured via the `json_payload` feature (there is an
385/// associated performance hit across the board for turning it on).
386#[derive(Serialize, Deserialize, Clone, Debug)]
387pub struct Sample {
388    /// The name of the event to be shown.
389    pub name: StrCow,
390    /// List of categories the event applies to.
391    #[serde(rename = "cat")]
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub categories: Option<CategoriesT>,
394    /// When was the sample started.
395    #[serde(rename = "ts")]
396    pub timestamp_us: u64,
397    /// What kind of sample this is.
398    #[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    /// The process the sample was captured in.
406    pub pid: u64,
407    /// The thread the sample was captured on.  Omitted for Metadata events that
408    /// want to set the process name (if provided then sets the thread name).
409    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    /// Constructs a Begin or End sample.  Should not be used directly.  Instead
430    /// should be constructed via SampleGuard.
431    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    /// Constructs a Duration sample.  For use via xi_trace::closure.
455    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    /// Constructs an instantaneous sample.
480    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        // TODO(vlovich): optimize this path to use the Complete event type
571        // rather than emitting an explicit start/stop to reduce the size of
572        // the generated JSON.
573        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
598/// Returns the file name of the EXE if possible, otherwise the full path, or
599/// None if an irrecoverable error occured.
600fn 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
623/// Stores the tracing data.
624pub 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    /// Generally racy since the underlying storage might be mutated in a separate thread.
659    /// Exposed for unit tests.
660    pub fn get_samples_count(&self) -> usize {
661        self.samples.lock().unwrap().len()
662    }
663
664    /// Exposed for unit tests only.
665    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        // TODO: simplify this through the use of scopeguard crate
732        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        // TODO: simplify this through the use of scopeguard crate
755        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/// Enable tracing with the default configuration.  See Config::default.
838/// Tracing is disabled initially on program launch.
839#[inline]
840pub fn enable_tracing() {
841    TRACE.enable();
842}
843
844/// Enable tracing with a specific configuration. Tracing is disabled initially
845/// on program launch.
846#[inline]
847pub fn enable_tracing_with_config(config: Config) {
848    TRACE.enable_config(config);
849}
850
851/// Disable tracing.  This clears all trace data (& frees the memory).
852#[inline]
853pub fn disable_tracing() {
854    TRACE.disable();
855}
856
857/// Is tracing enabled.  Technically doesn't guarantee any samples will be
858/// stored as tracing could still be enabled but set with a limit of 0.
859#[inline]
860pub fn is_enabled() -> bool {
861    TRACE.is_enabled()
862}
863
864/// Create an instantaneous sample without any payload.  This is the lowest
865/// overhead tracing routine available.
866///
867/// # Performance
868/// The `json_payload` feature makes this ~1.3-~1.5x slower.
869/// See `trace_payload` for a more complete discussion.
870///
871/// # Arguments
872///
873/// * `name` - A string that provides some meaningful name to this sample.
874/// Usage of static strings is encouraged for best performance to avoid copies.
875/// However, anything that can be converted into a Cow string can be passed as
876/// an argument.
877///
878/// * `categories` - A static array of static strings that tags the samples in
879/// some way.
880///
881/// # Examples
882///
883/// ```
884/// xi_trace::trace("something happened", &["rpc", "response"]);
885/// ```
886#[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/// Create an instantaneous sample with a payload.  The type the payload
896/// conforms to is currently determined by the feature this library is compiled
897/// with.  By default, the type is string-like just like name.  If compiled with
898/// the `json_payload` then a `serde_json::Value` is expected and  the library
899/// acquires a dependency on the `serde_json` crate.
900///
901/// # Performance
902/// A static string has the lowest overhead as no copies are necessary, roughly
903/// equivalent performance to a regular trace.  A string that needs to be copied
904/// first can make it ~1.7x slower than a regular trace.
905///
906/// When compiling with `json_payload`, this is ~2.1x slower than a string that
907/// needs to be copied (or ~4.5x slower than a static string)
908///
909/// # Arguments
910///
911/// * `name` - A string that provides some meaningful name to this sample.
912/// Usage of static strings is encouraged for best performance to avoid copies.
913/// However, anything that can be converted into a Cow string can be passed as
914/// an argument.
915///
916/// * `categories` - A static array of static strings that tags the samples in
917/// some way.
918///
919/// # Examples
920///
921/// ```
922/// xi_trace::trace_payload("something happened", &["rpc", "response"], "a note about this");
923/// ```
924///
925/// With `json_payload` feature:
926///
927/// ```rust,ignore
928/// xi_trace::trace_payload("my event", &["rpc", "response"], json!({"key": "value"}));
929/// ```
930#[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/// Creates a duration sample.  The sample is finalized (end_ns set) when the
941/// returned value is dropped.  `trace_closure` may be prettier to read.
942///
943/// # Performance
944/// See `trace_payload` for a more complete discussion.
945///
946/// # Arguments
947///
948/// * `name` - A string that provides some meaningful name to this sample.
949/// Usage of static strings is encouraged for best performance to avoid copies.
950/// However, anything that can be converted into a Cow string can be passed as
951/// an argument.
952///
953/// * `categories` - A static array of static strings that tags the samples in
954/// some way.
955///
956/// # Returns
957/// A guard that when dropped will update the Sample with the timestamp & then
958/// record it.
959///
960/// # Examples
961///
962/// ```
963/// fn something_expensive() {
964/// }
965///
966/// fn something_else_expensive() {
967/// }
968///
969/// let trace_guard = xi_trace::trace_block("something_expensive", &["rpc", "request"]);
970/// something_expensive();
971/// std::mem::drop(trace_guard); // finalize explicitly if
972///
973/// {
974///     let _guard = xi_trace::trace_block("something_else_expensive", &["rpc", "response"]);
975///     something_else_expensive();
976/// }
977/// ```
978#[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/// See `trace_block` for how the block works and `trace_payload` for a
988/// discussion on payload.
989#[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/// Creates a duration sample that measures how long the closure took to execute.
1000///
1001/// # Performance
1002/// See `trace_payload` for a more complete discussion.
1003///
1004/// # Arguments
1005///
1006/// * `name` - A string that provides some meaningful name to this sample.
1007/// Usage of static strings is encouraged for best performance to avoid copies.
1008/// However, anything that can be converted into a Cow string can be passed as
1009/// an argument.
1010///
1011/// * `categories` - A static array of static strings that tags the samples in
1012/// some way.
1013///
1014/// # Returns
1015/// The result of the closure.
1016///
1017/// # Examples
1018///
1019/// ```
1020/// fn something_expensive() -> u32 {
1021///     0
1022/// }
1023///
1024/// fn something_else_expensive(value: u32) {
1025/// }
1026///
1027/// let result = xi_trace::trace_closure("something_expensive", &["rpc", "request"], || {
1028///     something_expensive()
1029/// });
1030/// xi_trace::trace_closure("something_else_expensive", &["rpc", "response"], || {
1031///     something_else_expensive(result);
1032/// });
1033/// ```
1034#[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/// See `trace_closure` for how the closure works and `trace_payload` for a
1045/// discussion on payload.
1046#[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/// Returns all the samples collected so far.  There is no guarantee that the
1063/// samples are ordered chronologically for several reasons:
1064///
1065/// 1. Samples that span sections of code may be inserted on end instead of
1066/// beginning.
1067/// 2. Performance optimizations might have per-thread buffers.  Keeping all
1068/// that sorted would be prohibitively expensive.
1069/// 3. You may not care about them always being sorted if you're merging samples
1070/// from multiple distributed sources (i.e. you want to sort the merged result
1071/// rather than just this processe's samples).
1072#[inline]
1073pub fn samples_cloned_unsorted() -> Vec<Sample> {
1074    TRACE.samples_cloned_unsorted()
1075}
1076
1077/// Returns all the samples collected so far ordered chronologically by
1078/// creation.  Roughly corresponds to start_ns but instead there's a
1079/// monotonically increasing single global integer (when tracing) per creation
1080/// of Sample that determines order.
1081#[inline]
1082pub fn samples_cloned_sorted() -> Vec<Sample> {
1083    TRACE.samples_cloned_sorted()
1084}
1085
1086/// Save tracing data to to supplied path, using the Trace Viewer format. Trace file can be opened
1087/// using the Chrome browser by visiting the URL `about:tracing`. If `sorted_chronologically` is
1088/// true then sort output traces chronologically by each trace's time of creation.
1089#[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        // 1 for exe name & 1 for the thread name
1148        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        // +2 for exe & thread name.
1166        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        // current recording mechanism should see:
1209        // a, b, y, z, c, x
1210        // even though the actual sampling order (from timestamp of
1211        // creation) is:
1212        // x, a, y, b, z, c
1213        // This might be an over-specified test as it will
1214        // probably change as the recording internals change.
1215        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        // +2 for exe & thread name
1226        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        // current recording mechanism should see:
1246        // a, b, y, z, c, x
1247        // even though the actual sampling order (from timestamp of
1248        // creation) is:
1249        // x, a, y, b, z, c
1250        // This might be an over-specified test as it will
1251        // probably change as the recording internals change.
1252
1253        // NOTE: 1 us sleeps are inserted as the first line of a closure to
1254        // ensure that when the samples are sorted by time they come out in a
1255        // stable order since the resolution of timestamps is 1us.
1256        // NOTE 2: from_micros is currently in unstable so using new
1257        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        // +2 for exe & thread name.
1270        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    // this is the cost contributed by the timestamp to trace()
1379    #[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    // this is the cost contributed by the timestamp to
1386    // trace_block()/trace_closure
1387    #[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}