#[cfg(not(test))]
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct MarkSnapshot {
pub calls: u64,
pub gate_skips: u64,
pub perpattern_work: u64,
pub hs_served: u64,
pub regexset_served: u64,
}
impl MarkSnapshot {
pub fn served_total(&self) -> u64 {
self.hs_served + self.regexset_served
}
pub fn is_consistent(&self) -> bool {
self.gate_skips + self.served_total() == self.calls
&& self.served_total() == self.perpattern_work
}
pub fn gate_skip_pct(&self) -> f64 {
pct(self.gate_skips, self.calls)
}
pub fn perpattern_pct(&self) -> f64 {
pct(self.perpattern_work, self.calls)
}
pub fn hs_served_pct(&self) -> f64 {
pct(self.hs_served, self.perpattern_work)
}
pub fn regexset_served_pct(&self) -> f64 {
pct(self.regexset_served, self.perpattern_work)
}
}
pub(super) fn pct(part: u64, whole: u64) -> f64 {
if whole > 0 {
100.0 * part as f64 / whole as f64
} else {
0.0
}
}
pub(crate) fn format_mark_decomposition(s: &MarkSnapshot) -> String {
format!(
"mark: calls={} gate-skip={} ({:.1}%) per-pattern={} ({:.1}%) \
[hs={} ({:.1}%) regexset={} ({:.1}%)]",
s.calls,
s.gate_skips,
s.gate_skip_pct(),
s.perpattern_work,
s.perpattern_pct(),
s.hs_served,
s.hs_served_pct(),
s.regexset_served,
s.regexset_served_pct(),
)
}
#[cfg(not(test))]
pub(crate) static MARK_CALLS: AtomicU64 = AtomicU64::new(0);
#[cfg(not(test))]
pub(crate) static MARK_GATE_SKIPS: AtomicU64 = AtomicU64::new(0);
#[cfg(not(test))]
pub(crate) static MARK_PERPATTERN_WORK: AtomicU64 = AtomicU64::new(0);
#[cfg(not(test))]
pub(crate) static MARK_HS_SERVED: AtomicU64 = AtomicU64::new(0);
#[cfg(not(test))]
pub(crate) static MARK_REGEXSET_SERVED: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
#[derive(Clone, Copy, Default)]
struct MarkStats {
calls: u64,
gate_skips: u64,
perpattern_work: u64,
hs_served: u64,
regexset_served: u64,
}
#[cfg(test)]
thread_local! {
static MARK_STATS: std::cell::Cell<MarkStats> = const { std::cell::Cell::new(MarkStats {
calls: 0,
gate_skips: 0,
perpattern_work: 0,
hs_served: 0,
regexset_served: 0,
}) };
}
#[cfg(test)]
fn with_mark_stats(f: impl FnOnce(&mut MarkStats)) {
MARK_STATS.with(|cell| {
let mut snapshot = cell.get();
f(&mut snapshot);
cell.set(snapshot);
});
}
#[inline]
pub(crate) fn record_mark_call() {
#[cfg(test)]
with_mark_stats(|s| s.calls += 1);
#[cfg(not(test))]
MARK_CALLS.fetch_add(1, Relaxed);
}
#[inline]
pub(crate) fn record_mark_gate_skip() {
#[cfg(test)]
with_mark_stats(|s| s.gate_skips += 1);
#[cfg(not(test))]
MARK_GATE_SKIPS.fetch_add(1, Relaxed);
}
#[inline]
pub(crate) fn record_mark_perpattern_work() {
#[cfg(test)]
with_mark_stats(|s| s.perpattern_work += 1);
#[cfg(not(test))]
MARK_PERPATTERN_WORK.fetch_add(1, Relaxed);
}
#[cfg(feature = "simd")]
#[inline]
pub(crate) fn record_mark_hs_served() {
#[cfg(test)]
with_mark_stats(|s| s.hs_served += 1);
#[cfg(not(test))]
MARK_HS_SERVED.fetch_add(1, Relaxed);
}
#[inline]
pub(crate) fn record_mark_regexset_served() {
#[cfg(test)]
with_mark_stats(|s| s.regexset_served += 1);
#[cfg(not(test))]
MARK_REGEXSET_SERVED.fetch_add(1, Relaxed);
}
pub(crate) fn phase2_mark_stats() -> MarkSnapshot {
#[cfg(test)]
{
let s = MARK_STATS.with(std::cell::Cell::get);
MarkSnapshot {
calls: s.calls,
gate_skips: s.gate_skips,
perpattern_work: s.perpattern_work,
hs_served: s.hs_served,
regexset_served: s.regexset_served,
}
}
#[cfg(not(test))]
MarkSnapshot {
calls: MARK_CALLS.load(Relaxed),
gate_skips: MARK_GATE_SKIPS.load(Relaxed),
perpattern_work: MARK_PERPATTERN_WORK.load(Relaxed),
hs_served: MARK_HS_SERVED.load(Relaxed),
regexset_served: MARK_REGEXSET_SERVED.load(Relaxed),
}
}
#[cfg(test)]
pub(crate) fn phase2_mark_stats_reset() {
MARK_STATS.with(|cell| cell.set(MarkStats::default()));
}
#[cfg(not(test))]
pub(crate) fn phase2_mark_stats_reset() {
MARK_CALLS.store(0, Relaxed);
MARK_GATE_SKIPS.store(0, Relaxed);
MARK_PERPATTERN_WORK.store(0, Relaxed);
MARK_HS_SERVED.store(0, Relaxed);
MARK_REGEXSET_SERVED.store(0, Relaxed);
}
#[cfg(test)]
mod pct_owner_tests {
use super::pct;
#[test]
fn pct_is_percentage_of_whole() {
assert_eq!(pct(1, 4), 25.0);
assert_eq!(pct(900, 1000), 90.0);
assert_eq!(pct(3, 3), 100.0);
}
#[test]
fn pct_is_zero_when_whole_is_zero_no_div_by_zero() {
assert_eq!(pct(5, 0), 0.0);
assert_eq!(pct(0, 0), 0.0);
}
}