#[cfg(feature = "trace")]
mod imp {
use std::sync::Mutex;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Instant;
const CAPACITY: usize = 8192;
#[derive(Clone, Copy)]
pub struct Record {
pub at: std::time::Duration,
pub thread: u32,
pub target: &'static str,
pub event: &'static str,
pub a: u64,
pub b: u64,
}
struct State {
records: Vec<Record>,
dropped: usize,
}
static ARMED: AtomicU8 = AtomicU8::new(ARMED_UNKNOWN);
const ARMED_UNKNOWN: u8 = 0;
const ARMED_OFF: u8 = 1;
const ARMED_ON: u8 = 2;
fn state() -> &'static Mutex<State> {
static STATE: std::sync::OnceLock<Mutex<State>> = std::sync::OnceLock::new();
STATE.get_or_init(|| {
Mutex::new(State {
records: Vec::with_capacity(CAPACITY),
dropped: 0,
})
})
}
fn started() -> Instant {
static STARTED: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
*STARTED.get_or_init(Instant::now)
}
fn filters() -> &'static Vec<String> {
static FILTERS: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
FILTERS.get_or_init(|| {
std::env::var("WINDOWS_THREADPOOL_TRACE")
.unwrap_or_default()
.split(',')
.map(|piece| piece.trim().to_owned())
.filter(|piece| !piece.is_empty())
.collect()
})
}
#[inline(always)]
pub fn enabled() -> bool {
match ARMED.load(Ordering::Relaxed) {
ARMED_ON => true,
ARMED_OFF => false,
_ => {
let on = !filters().is_empty();
ARMED.store(if on { ARMED_ON } else { ARMED_OFF }, Ordering::Relaxed);
on
}
}
}
#[inline(always)]
pub fn wants(target: &str) -> bool {
enabled()
&& filters()
.iter()
.any(|filter| filter == "*" || target.contains(filter.as_str()))
}
#[inline]
pub fn record(target: &'static str, event: &'static str, a: u64, b: u64) {
if !wants(target) {
return;
}
let at = started().elapsed();
let thread = unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() };
let Ok(mut state) = state().lock() else {
return;
};
if state.records.len() == CAPACITY {
state.records.remove(0);
state.dropped += 1;
}
state.records.push(Record {
at,
thread,
target,
event,
a,
b,
});
}
pub fn dump() -> String {
let Ok(state) = state().lock() else {
return "<trace lock poisoned>".to_owned();
};
let mut out = String::with_capacity(state.records.len() * 64);
if state.dropped > 0 {
out.push_str(&format!(
" ... {} earlier record(s) dropped; raise CAPACITY to keep them\n",
state.dropped
));
}
for record in &state.records {
out.push_str(&format!(
" {:>12.6}s t{:<6} {:<22} {:<28} {:>6} {:>6}\n",
record.at.as_secs_f64(),
record.thread,
record.target,
record.event,
record.a,
record.b
));
}
out
}
pub fn clear() {
if let Ok(mut state) = state().lock() {
state.records.clear();
state.dropped = 0;
}
}
}
#[cfg(not(feature = "trace"))]
mod imp {
#[inline(always)]
pub fn enabled() -> bool {
false
}
#[inline(always)]
pub fn wants(_target: &str) -> bool {
false
}
#[inline(always)]
pub fn record(_target: &'static str, _event: &'static str, _a: u64, _b: u64) {}
pub fn dump() -> String {
"<built without the `trace` feature>".to_owned()
}
pub fn clear() {}
}
pub use imp::{clear, dump, enabled, record, wants};
#[macro_export]
macro_rules! trace_record {
($target:expr, $event:expr) => {
$crate::trace::record($target, $event, 0, 0)
};
($target:expr, $event:expr, $a:expr) => {
if $crate::trace::wants($target) {
$crate::trace::record($target, $event, $a as u64, 0);
}
};
($target:expr, $event:expr, $a:expr, $b:expr) => {
if $crate::trace::wants($target) {
$crate::trace::record($target, $event, $a as u64, $b as u64);
}
};
}
#[cfg(test)]
mod tests;