1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use std::{collections::BTreeMap, sync::Arc};

use once_cell::sync::Lazy;

use crate::{
    fetch_add_scope_id, Error, FrameData, FrameIndex, FrameSinkId, ScopeCollection, ScopeDetails,
    ScopeId, StreamInfo, StreamInfoRef, ThreadInfo,
};

/// Add these to [`GlobalProfiler`] with [`GlobalProfiler::add_sink()`].
pub type FrameSink = Box<dyn Fn(Arc<FrameData>) + Send>;

/// Singleton. Collects profiling data from multiple threads
/// and passes them on to different [`FrameSink`]s.
pub struct GlobalProfiler {
    current_frame_index: FrameIndex,
    current_frame: BTreeMap<ThreadInfo, StreamInfo>,

    next_sink_id: FrameSinkId,
    sinks: std::collections::HashMap<FrameSinkId, FrameSink>,
    // When true will propagate a full snapshot from `scope_collection` to every sink.
    propagate_all_scope_details: bool,
    // The new scopes' details, or also the first time macro or external library detected a scope.
    new_scopes: Vec<Arc<ScopeDetails>>,
    // Store an absolute collection of scope details such that sinks can request a total state by setting `propagate_all_scope_details`.
    // This should not be mutable accessible to external applications as frame views store there own copy.
    scope_collection: ScopeCollection,
}

impl Default for GlobalProfiler {
    fn default() -> Self {
        Self {
            current_frame_index: 0,
            current_frame: Default::default(),
            next_sink_id: FrameSinkId(1),
            sinks: Default::default(),
            propagate_all_scope_details: Default::default(),
            new_scopes: Default::default(),
            scope_collection: Default::default(),
        }
    }
}

impl GlobalProfiler {
    /// Access to the global profiler singleton.
    pub fn lock() -> parking_lot::MutexGuard<'static, Self> {
        static GLOBAL_PROFILER: Lazy<parking_lot::Mutex<GlobalProfiler>> =
            Lazy::new(Default::default);
        GLOBAL_PROFILER.lock()
    }

    /// You need to call this once at the start of every frame.
    ///
    /// It is fine to call this from within a profile scope.
    ///
    /// This takes all completed profiling scopes from all threads,
    /// and sends it to the sinks.
    pub fn new_frame(&mut self) {
        let current_frame_index = self.current_frame_index;
        self.current_frame_index += 1;

        let mut scope_deltas = Vec::with_capacity(self.new_scopes.len());

        // Firstly add the new registered scopes.
        for scope_detail in self.new_scopes.drain(..) {
            scope_deltas.push(scope_detail);
        }

        let current_frame_scope = std::mem::take(&mut self.current_frame);

        // Secondly add a full snapshot of all scopes if requested.
        // Could potentially do this per sink.
        let propagate_full_delta = std::mem::take(&mut self.propagate_all_scope_details);

        if propagate_full_delta {
            scope_deltas.extend(self.scope_collection.scopes_by_id().values().cloned());
        }

        let new_frame = match FrameData::new(
            current_frame_index,
            current_frame_scope,
            scope_deltas,
            propagate_full_delta,
        ) {
            Ok(new_frame) => Arc::new(new_frame),
            Err(Error::Empty) => {
                return; // don't warn about empty frames, just ignore them
            }
            Err(err) => {
                eprintln!("puffin ERROR: Bad frame: {err:?}");
                return;
            }
        };

        self.add_frame(new_frame);
    }

    /// Manually add frame data.
    pub fn add_frame(&mut self, new_frame: Arc<FrameData>) {
        for delta in &new_frame.scope_delta {
            self.scope_collection.insert(delta.clone());
        }

        for sink in self.sinks.values() {
            sink(new_frame.clone());
        }
    }

    /// Inserts user scopes into puffin.
    /// Returns the scope id for every inserted scope in the same order as input slice.
    ///
    /// Scopes details should only be registered once for each scope and need be inserted before being reported to puffin.
    /// This function is relevant when you're registering measurement not performed using the puffin profiler macros.
    /// Scope id is always supposed to be `None` as it will be set by puffin.
    pub fn register_user_scopes(&mut self, scopes: &[ScopeDetails]) -> Vec<ScopeId> {
        let mut new_scopes = Vec::with_capacity(scopes.len());
        for scope_detail in scopes {
            let new_scope_id = fetch_add_scope_id();
            let scope = self.scope_collection.insert(Arc::new(
                (*scope_detail).clone().with_scope_id(new_scope_id),
            ));
            new_scopes.push(scope);
        }
        let new_scope_ids = new_scopes.iter().filter_map(|x| x.scope_id).collect();
        self.new_scopes.extend(new_scopes);
        new_scope_ids
    }

    /// Reports some profiling data. Called from [`ThreadProfiler`].
    pub(crate) fn report(
        &mut self,
        info: ThreadInfo,
        scope_details: &[ScopeDetails],
        stream_scope_times: &StreamInfoRef<'_>,
    ) {
        if !scope_details.is_empty() {
            // Here we can run slightly heavy logic as its only ran once for each scope.
            self.new_scopes
                .extend(scope_details.iter().map(|x| Arc::new(x.clone())));
        }

        self.current_frame
            .entry(info)
            .or_default()
            .extend(stream_scope_times);
    }

    /// Reports user scopes to puffin profiler.
    /// Every scope reported should first be registered by [`Self::register_user_scopes`].
    pub fn report_user_scopes(&mut self, info: ThreadInfo, stream_scope_times: &StreamInfoRef<'_>) {
        self.current_frame
            .entry(info)
            .or_default()
            .extend(stream_scope_times);
    }

    /// Tells [`GlobalProfiler`] to call this function with each new finished frame.
    ///
    /// The returned [`FrameSinkId`] can be used to remove the sink with [`Self::remove_sink()`].
    /// If the sink is registered later in the application make sure to call [`Self::emit_scope_snapshot()`] to send a snapshot of all scopes.
    pub fn add_sink(&mut self, sink: FrameSink) -> FrameSinkId {
        let id = self.next_sink_id;
        self.next_sink_id.0 += 1;
        self.sinks.insert(id, sink);
        id
    }

    /// Removes a sink from the global profiler.
    pub fn remove_sink(&mut self, id: FrameSinkId) -> Option<FrameSink> {
        self.sinks.remove(&id)
    }

    /// Sends a snapshot of all scopes to all sinks via the frame data.
    /// This is useful for if a sink is initialized after scopes are registered.
    pub fn emit_scope_snapshot(&mut self) {
        self.propagate_all_scope_details = true;
    }
}