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