#[cfg(not(target_arch = "wasm32"))]
use std::cell::RefCell;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Phase {
Read,
Parse,
Reason,
ParseDictionary,
ParseEncode,
ParseChunk,
FrequentObjects,
Dictionary,
Encode,
CardDerive,
Pyramid,
TextIndex,
LabelsSchema,
DictionarySection,
IndexPermutations,
NamedGraphs,
Serialize,
StarterQueries,
Write,
ContentHash,
}
impl Phase {
pub const ALL: [Phase; 20] = [
Phase::Read,
Phase::Parse,
Phase::Reason,
Phase::ParseDictionary,
Phase::ParseEncode,
Phase::ParseChunk,
Phase::FrequentObjects,
Phase::Dictionary,
Phase::Encode,
Phase::CardDerive,
Phase::Pyramid,
Phase::TextIndex,
Phase::LabelsSchema,
Phase::DictionarySection,
Phase::IndexPermutations,
Phase::NamedGraphs,
Phase::Serialize,
Phase::StarterQueries,
Phase::Write,
Phase::ContentHash,
];
pub fn as_str(self) -> &'static str {
match self {
Phase::Read => "read",
Phase::Parse => "parse",
Phase::Reason => "reason",
Phase::ParseDictionary => "parse+dictionary",
Phase::ParseEncode => "parse+encode",
Phase::ParseChunk => "parse+chunk",
Phase::FrequentObjects => "frequent_objects",
Phase::Dictionary => "dictionary",
Phase::Encode => "encode",
Phase::CardDerive => "card_derive",
Phase::Pyramid => "pyramid",
Phase::TextIndex => "text_index",
Phase::LabelsSchema => "labels+schema",
Phase::DictionarySection => "dictionary_section",
Phase::IndexPermutations => "index_permutations",
Phase::NamedGraphs => "named_graphs",
Phase::Serialize => "serialize",
Phase::StarterQueries => "starter_queries",
Phase::Write => "write",
Phase::ContentHash => "content_hash",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Trace {
pub phases: Vec<(Phase, Duration)>,
pub counters: Vec<(&'static str, u64)>,
}
impl Trace {
pub fn counter(&self, name: &str) -> Option<u64> {
self.counters
.iter()
.find(|(n, _)| *n == name)
.map(|&(_, v)| v)
}
}
#[cfg(not(target_arch = "wasm32"))]
struct State {
last: std::time::Instant,
trace: Trace,
}
#[cfg(not(target_arch = "wasm32"))]
thread_local! {
static ACTIVE: RefCell<Option<State>> = const { RefCell::new(None) };
}
pub struct Collector {
_not_send: std::marker::PhantomData<*const ()>,
}
impl Collector {
pub fn start() -> Self {
#[cfg(not(target_arch = "wasm32"))]
ACTIVE.with(|a| {
*a.borrow_mut() = Some(State {
last: std::time::Instant::now(),
trace: Trace::default(),
})
});
Collector {
_not_send: std::marker::PhantomData,
}
}
pub fn snapshot(&self) -> Trace {
#[cfg(not(target_arch = "wasm32"))]
{
ACTIVE.with(|a| {
a.borrow()
.as_ref()
.map(|s| s.trace.clone())
.unwrap_or_default()
})
}
#[cfg(target_arch = "wasm32")]
Trace::default()
}
}
impl Drop for Collector {
fn drop(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
ACTIVE.with(|a| *a.borrow_mut() = None);
}
}
pub fn lap(phase: Phase) {
#[cfg(not(target_arch = "wasm32"))]
ACTIVE.with(|a| {
if let Some(s) = a.borrow_mut().as_mut() {
let now = std::time::Instant::now();
let d = now.duration_since(s.last);
s.last = now;
match s.trace.phases.iter_mut().find(|(p, _)| *p == phase) {
Some((_, total)) => *total += d,
None => s.trace.phases.push((phase, d)),
}
}
});
#[cfg(target_arch = "wasm32")]
let _ = phase;
}
pub fn skip() {
#[cfg(not(target_arch = "wasm32"))]
ACTIVE.with(|a| {
if let Some(s) = a.borrow_mut().as_mut() {
s.last = std::time::Instant::now();
}
});
}
fn update(name: &'static str, f: impl FnOnce(u64) -> u64) {
#[cfg(not(target_arch = "wasm32"))]
ACTIVE.with(|a| {
if let Some(s) = a.borrow_mut().as_mut() {
let c = &mut s.trace.counters;
match c.binary_search_by(|(n, _)| (*n).cmp(name)) {
Ok(i) => c[i].1 = f(c[i].1),
Err(i) => c.insert(i, (name, f(0))),
}
}
});
#[cfg(target_arch = "wasm32")]
let _ = (name, f);
}
pub fn add(name: &'static str, v: u64) {
update(name, |c| c.saturating_add(v));
}
pub fn max(name: &'static str, v: u64) {
update(name, |c| c.max(v));
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn note_merge(inputs: usize) {
add("merge_passes", 1);
add("sort_runs", inputs as u64);
}
pub fn active() -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
ACTIVE.with(|a| a.borrow().is_some())
}
#[cfg(target_arch = "wasm32")]
false
}
pub fn enabled_features() -> Vec<&'static str> {
let mut f = Vec::new();
if cfg!(feature = "compression") {
f.push("compression");
}
if cfg!(feature = "parallel") {
f.push("parallel");
}
if cfg!(feature = "rdf12-turtle") {
f.push("rdf12-turtle");
}
if cfg!(feature = "unicode-regex") {
f.push("unicode-regex");
}
f
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
#[test]
fn laps_partition_and_accumulate_only_under_a_collector() {
lap(Phase::Parse);
add("chunks", 3);
assert!(!active());
let c = Collector::start();
assert!(active());
lap(Phase::Parse);
lap(Phase::Encode);
lap(Phase::Parse);
add("sort_runs", 2);
add("sort_runs", 5);
max("scratch_peak_bytes", 10);
max("scratch_peak_bytes", 4);
let t = c.snapshot();
let names: Vec<_> = t.phases.iter().map(|(p, _)| p.as_str()).collect();
assert_eq!(
names,
["parse", "encode"],
"first-end order, repeats summed"
);
assert_eq!(t.counter("sort_runs"), Some(7));
assert_eq!(t.counter("scratch_peak_bytes"), Some(10));
assert_eq!(t.counter("chunks"), None, "nothing before the collector");
drop(c);
assert!(!active());
lap(Phase::Write);
}
#[test]
fn phase_names_are_distinct() {
let mut names: Vec<_> = Phase::ALL.iter().map(|p| p.as_str()).collect();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), Phase::ALL.len());
}
}