1#[cfg(not(target_arch = "wasm32"))]
19use std::cell::RefCell;
20use std::time::Duration;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub enum Phase {
26 Read,
28 Parse,
30 Reason,
32 ParseDictionary,
35 ParseEncode,
37 ParseChunk,
39 Dictionary,
42 Encode,
45 CardDerive,
47 Pyramid,
49 TextIndex,
51 LabelsSchema,
53 DictionarySection,
55 IndexPermutations,
58 NamedGraphs,
60 Serialize,
62 StarterQueries,
64 Write,
66 ContentHash,
71}
72
73impl Phase {
74 pub const ALL: [Phase; 19] = [
76 Phase::Read,
77 Phase::Parse,
78 Phase::Reason,
79 Phase::ParseDictionary,
80 Phase::ParseEncode,
81 Phase::ParseChunk,
82 Phase::Dictionary,
83 Phase::Encode,
84 Phase::CardDerive,
85 Phase::Pyramid,
86 Phase::TextIndex,
87 Phase::LabelsSchema,
88 Phase::DictionarySection,
89 Phase::IndexPermutations,
90 Phase::NamedGraphs,
91 Phase::Serialize,
92 Phase::StarterQueries,
93 Phase::Write,
94 Phase::ContentHash,
95 ];
96
97 pub fn as_str(self) -> &'static str {
99 match self {
100 Phase::Read => "read",
101 Phase::Parse => "parse",
102 Phase::Reason => "reason",
103 Phase::ParseDictionary => "parse+dictionary",
104 Phase::ParseEncode => "parse+encode",
105 Phase::ParseChunk => "parse+chunk",
106 Phase::Dictionary => "dictionary",
107 Phase::Encode => "encode",
108 Phase::CardDerive => "card_derive",
109 Phase::Pyramid => "pyramid",
110 Phase::TextIndex => "text_index",
111 Phase::LabelsSchema => "labels+schema",
112 Phase::DictionarySection => "dictionary_section",
113 Phase::IndexPermutations => "index_permutations",
114 Phase::NamedGraphs => "named_graphs",
115 Phase::Serialize => "serialize",
116 Phase::StarterQueries => "starter_queries",
117 Phase::Write => "write",
118 Phase::ContentHash => "content_hash",
119 }
120 }
121}
122
123#[derive(Debug, Clone, Default, PartialEq, Eq)]
125pub struct Trace {
126 pub phases: Vec<(Phase, Duration)>,
129 pub counters: Vec<(&'static str, u64)>,
132}
133
134impl Trace {
135 pub fn counter(&self, name: &str) -> Option<u64> {
137 self.counters
138 .iter()
139 .find(|(n, _)| *n == name)
140 .map(|&(_, v)| v)
141 }
142}
143
144#[cfg(not(target_arch = "wasm32"))]
145struct State {
146 last: std::time::Instant,
147 trace: Trace,
148}
149
150#[cfg(not(target_arch = "wasm32"))]
151thread_local! {
152 static ACTIVE: RefCell<Option<State>> = const { RefCell::new(None) };
153}
154
155pub struct Collector {
158 _not_send: std::marker::PhantomData<*const ()>,
159}
160
161impl Collector {
162 pub fn start() -> Self {
164 #[cfg(not(target_arch = "wasm32"))]
165 ACTIVE.with(|a| {
166 *a.borrow_mut() = Some(State {
167 last: std::time::Instant::now(),
168 trace: Trace::default(),
169 })
170 });
171 Collector {
172 _not_send: std::marker::PhantomData,
173 }
174 }
175
176 pub fn snapshot(&self) -> Trace {
178 #[cfg(not(target_arch = "wasm32"))]
179 {
180 ACTIVE.with(|a| {
181 a.borrow()
182 .as_ref()
183 .map(|s| s.trace.clone())
184 .unwrap_or_default()
185 })
186 }
187 #[cfg(target_arch = "wasm32")]
188 Trace::default()
189 }
190}
191
192impl Drop for Collector {
193 fn drop(&mut self) {
194 #[cfg(not(target_arch = "wasm32"))]
195 ACTIVE.with(|a| *a.borrow_mut() = None);
196 }
197}
198
199pub fn lap(phase: Phase) {
202 #[cfg(not(target_arch = "wasm32"))]
203 ACTIVE.with(|a| {
204 if let Some(s) = a.borrow_mut().as_mut() {
205 let now = std::time::Instant::now();
206 let d = now.duration_since(s.last);
207 s.last = now;
208 match s.trace.phases.iter_mut().find(|(p, _)| *p == phase) {
209 Some((_, total)) => *total += d,
210 None => s.trace.phases.push((phase, d)),
211 }
212 }
213 });
214 #[cfg(target_arch = "wasm32")]
215 let _ = phase;
216}
217
218pub fn skip() {
221 #[cfg(not(target_arch = "wasm32"))]
222 ACTIVE.with(|a| {
223 if let Some(s) = a.borrow_mut().as_mut() {
224 s.last = std::time::Instant::now();
225 }
226 });
227}
228
229fn update(name: &'static str, f: impl FnOnce(u64) -> u64) {
230 #[cfg(not(target_arch = "wasm32"))]
231 ACTIVE.with(|a| {
232 if let Some(s) = a.borrow_mut().as_mut() {
233 let c = &mut s.trace.counters;
234 match c.binary_search_by(|(n, _)| (*n).cmp(name)) {
235 Ok(i) => c[i].1 = f(c[i].1),
236 Err(i) => c.insert(i, (name, f(0))),
237 }
238 }
239 });
240 #[cfg(target_arch = "wasm32")]
241 let _ = (name, f);
242}
243
244pub fn add(name: &'static str, v: u64) {
246 update(name, |c| c.saturating_add(v));
247}
248
249pub fn max(name: &'static str, v: u64) {
251 update(name, |c| c.max(v));
252}
253
254#[cfg(not(target_arch = "wasm32"))]
257pub(crate) fn note_merge(inputs: usize) {
258 add("merge_passes", 1);
259 add("sort_runs", inputs as u64);
260}
261
262pub fn active() -> bool {
265 #[cfg(not(target_arch = "wasm32"))]
266 {
267 ACTIVE.with(|a| a.borrow().is_some())
268 }
269 #[cfg(target_arch = "wasm32")]
270 false
271}
272
273pub fn enabled_features() -> Vec<&'static str> {
276 let mut f = Vec::new();
277 if cfg!(feature = "compression") {
278 f.push("compression");
279 }
280 if cfg!(feature = "parallel") {
281 f.push("parallel");
282 }
283 if cfg!(feature = "rdf12-turtle") {
284 f.push("rdf12-turtle");
285 }
286 if cfg!(feature = "unicode-regex") {
287 f.push("unicode-regex");
288 }
289 f
290}
291
292#[cfg(all(test, not(target_arch = "wasm32")))]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn laps_partition_and_accumulate_only_under_a_collector() {
298 lap(Phase::Parse);
299 add("chunks", 3);
300 assert!(!active());
301 let c = Collector::start();
302 assert!(active());
303 lap(Phase::Parse);
304 lap(Phase::Encode);
305 lap(Phase::Parse);
306 add("sort_runs", 2);
307 add("sort_runs", 5);
308 max("scratch_peak_bytes", 10);
309 max("scratch_peak_bytes", 4);
310 let t = c.snapshot();
311 let names: Vec<_> = t.phases.iter().map(|(p, _)| p.as_str()).collect();
312 assert_eq!(
313 names,
314 ["parse", "encode"],
315 "first-end order, repeats summed"
316 );
317 assert_eq!(t.counter("sort_runs"), Some(7));
318 assert_eq!(t.counter("scratch_peak_bytes"), Some(10));
319 assert_eq!(t.counter("chunks"), None, "nothing before the collector");
320 drop(c);
321 assert!(!active());
322 lap(Phase::Write);
323 }
324
325 #[test]
326 fn phase_names_are_distinct() {
327 let mut names: Vec<_> = Phase::ALL.iter().map(|p| p.as_str()).collect();
328 names.sort_unstable();
329 names.dedup();
330 assert_eq!(names.len(), Phase::ALL.len());
331 }
332}