puffin/
global_profiler.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use once_cell::sync::Lazy;
4
5use crate::{
6    fetch_add_scope_id, Error, FrameData, FrameIndex, FrameSinkId, ScopeCollection, ScopeDetails,
7    ScopeId, StreamInfo, StreamInfoRef, ThreadInfo,
8};
9
10/// Add these to [`GlobalProfiler`] with [`GlobalProfiler::add_sink()`].
11pub type FrameSink = Box<dyn Fn(Arc<FrameData>) + Send>;
12
13/// Singleton. Collects profiling data from multiple threads
14/// and passes them on to different [`FrameSink`]s.
15pub struct GlobalProfiler {
16    current_frame_index: FrameIndex,
17    current_frame: BTreeMap<ThreadInfo, StreamInfo>,
18
19    next_sink_id: FrameSinkId,
20    sinks: std::collections::HashMap<FrameSinkId, FrameSink>,
21    // When true will propagate a full snapshot from `scope_collection` to every sink.
22    propagate_all_scope_details: bool,
23    // The new scopes' details, or also the first time macro or external library detected a scope.
24    new_scopes: Vec<Arc<ScopeDetails>>,
25    // Store an absolute collection of scope details such that sinks can request a total state by setting `propagate_all_scope_details`.
26    // This should not be mutable accessible to external applications as frame views store there own copy.
27    scope_collection: ScopeCollection,
28}
29
30impl Default for GlobalProfiler {
31    fn default() -> Self {
32        Self {
33            current_frame_index: 0,
34            current_frame: Default::default(),
35            next_sink_id: FrameSinkId(1),
36            sinks: Default::default(),
37            propagate_all_scope_details: Default::default(),
38            new_scopes: Default::default(),
39            scope_collection: Default::default(),
40        }
41    }
42}
43
44impl GlobalProfiler {
45    /// Access to the global profiler singleton.
46    pub fn lock() -> parking_lot::MutexGuard<'static, Self> {
47        static GLOBAL_PROFILER: Lazy<parking_lot::Mutex<GlobalProfiler>> =
48            Lazy::new(Default::default);
49        GLOBAL_PROFILER.lock()
50    }
51
52    /// You need to call this once at the start of every frame.
53    ///
54    /// It is fine to call this from within a profile scope.
55    ///
56    /// This takes all completed profiling scopes from all threads,
57    /// and sends it to the sinks.
58    pub fn new_frame(&mut self) {
59        let current_frame_index = self.current_frame_index;
60        self.current_frame_index += 1;
61
62        let mut scope_deltas = Vec::with_capacity(self.new_scopes.len());
63
64        // Firstly add the new registered scopes.
65        for scope_detail in self.new_scopes.drain(..) {
66            scope_deltas.push(scope_detail);
67        }
68
69        let current_frame_scope = std::mem::take(&mut self.current_frame);
70
71        // Secondly add a full snapshot of all scopes if requested.
72        // Could potentially do this per sink.
73        let propagate_full_delta = std::mem::take(&mut self.propagate_all_scope_details);
74
75        if propagate_full_delta {
76            scope_deltas.extend(self.scope_collection.scopes_by_id().values().cloned());
77        }
78
79        let new_frame = match FrameData::new(
80            current_frame_index,
81            current_frame_scope,
82            scope_deltas,
83            propagate_full_delta,
84        ) {
85            Ok(new_frame) => Arc::new(new_frame),
86            Err(Error::Empty) => {
87                return; // don't warn about empty frames, just ignore them
88            }
89            Err(err) => {
90                eprintln!("puffin ERROR: Bad frame: {err:?}");
91                return;
92            }
93        };
94
95        self.add_frame(new_frame);
96    }
97
98    /// Manually add frame data.
99    pub fn add_frame(&mut self, new_frame: Arc<FrameData>) {
100        for delta in &new_frame.scope_delta {
101            self.scope_collection.insert(delta.clone());
102        }
103
104        for sink in self.sinks.values() {
105            sink(new_frame.clone());
106        }
107    }
108
109    /// Inserts user scopes into puffin.
110    /// Returns the scope id for every inserted scope in the same order as input slice.
111    ///
112    /// Scopes details should only be registered once for each scope and need be inserted before being reported to puffin.
113    /// This function is relevant when you're registering measurement not performed using the puffin profiler macros.
114    /// Scope id is always supposed to be `None` as it will be set by puffin.
115    pub fn register_user_scopes(&mut self, scopes: &[ScopeDetails]) -> Vec<ScopeId> {
116        let mut new_scopes = Vec::with_capacity(scopes.len());
117        for scope_detail in scopes {
118            let new_scope_id = fetch_add_scope_id();
119            let scope = self.scope_collection.insert(Arc::new(
120                (*scope_detail).clone().with_scope_id(new_scope_id),
121            ));
122            new_scopes.push(scope);
123        }
124        let new_scope_ids = new_scopes.iter().filter_map(|x| x.scope_id).collect();
125        self.new_scopes.extend(new_scopes);
126        new_scope_ids
127    }
128
129    /// Reports some profiling data. Called from [`crate::ThreadProfiler`].
130    pub fn report(
131        &mut self,
132        info: ThreadInfo,
133        scope_details: &[ScopeDetails],
134        stream_scope_times: &StreamInfoRef<'_>,
135    ) {
136        if !scope_details.is_empty() {
137            // Here we can run slightly heavy logic as its only ran once for each scope.
138            self.new_scopes
139                .extend(scope_details.iter().map(|x| Arc::new(x.clone())));
140        }
141
142        self.current_frame
143            .entry(info)
144            .or_default()
145            .extend(stream_scope_times);
146    }
147
148    /// Reports user scopes to puffin profiler.
149    /// Every scope reported should first be registered by [`Self::register_user_scopes`].
150    pub fn report_user_scopes(&mut self, info: ThreadInfo, stream_scope_times: &StreamInfoRef<'_>) {
151        self.current_frame
152            .entry(info)
153            .or_default()
154            .extend(stream_scope_times);
155    }
156
157    /// Tells [`GlobalProfiler`] to call this function with each new finished frame.
158    ///
159    /// The returned [`FrameSinkId`] can be used to remove the sink with [`Self::remove_sink()`].
160    /// If the sink is registered later in the application make sure to call [`Self::emit_scope_snapshot()`] to send a snapshot of all scopes.
161    pub fn add_sink(&mut self, sink: FrameSink) -> FrameSinkId {
162        let id = self.next_sink_id;
163        self.next_sink_id.0 += 1;
164        self.sinks.insert(id, sink);
165        id
166    }
167
168    /// Removes a sink from the global profiler.
169    pub fn remove_sink(&mut self, id: FrameSinkId) -> Option<FrameSink> {
170        self.sinks.remove(&id)
171    }
172
173    /// Sends a snapshot of all scopes to all sinks via the frame data.
174    /// This is useful for if a sink is initialized after scopes are registered.
175    pub fn emit_scope_snapshot(&mut self) {
176        self.propagate_all_scope_details = true;
177    }
178}