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