#[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 {
let mut line = 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(),
);
let batch_runs = super::GATE_BATCH_RUNS.load(std::sync::atomic::Ordering::Relaxed);
let batch_skips = super::GATE_BATCH_SKIPS.load(std::sync::atomic::Ordering::Relaxed);
let batch_total = batch_runs.saturating_add(batch_skips);
if batch_total > 0 {
line.push_str(&format!(
" batches: run={batch_runs} skip={batch_skips} ({:.1}% skipped)",
pct(batch_skips, batch_total)
));
}
line
}
#[inline]
pub(crate) fn record_mark_call() {
keyhog_profile::add_counter(keyhog_profile::CounterId::Phase2PrefilterMarkCalls, 1);
}
#[inline]
pub(crate) fn record_mark_gate_skip() {
keyhog_profile::add_counter(keyhog_profile::CounterId::Phase2PrefilterGateSkips, 1);
}
#[inline]
pub(crate) fn record_mark_perpattern_work() {
keyhog_profile::add_counter(keyhog_profile::CounterId::Phase2PrefilterPerPatternWork, 1);
}
#[cfg(feature = "simd")]
#[inline]
pub(crate) fn record_mark_hs_served() {
keyhog_profile::add_counter(keyhog_profile::CounterId::Phase2PrefilterHsServed, 1);
}
#[inline]
pub(crate) fn record_mark_regexset_served() {
keyhog_profile::add_counter(keyhog_profile::CounterId::Phase2PrefilterRegexsetServed, 1);
}
pub(crate) fn mark_snapshot_from_typed(
metrics: &[keyhog_profile::TypedMetricRecordV2],
) -> MarkSnapshot {
let value = |counter: keyhog_profile::CounterId| {
metrics
.iter()
.find(|record| record.metric_id == counter.metric_id())
.map_or(0, |record| record.value)
};
use keyhog_profile::CounterId;
MarkSnapshot {
calls: value(CounterId::Phase2PrefilterMarkCalls),
gate_skips: value(CounterId::Phase2PrefilterGateSkips),
perpattern_work: value(CounterId::Phase2PrefilterPerPatternWork),
hs_served: value(CounterId::Phase2PrefilterHsServed),
regexset_served: value(CounterId::Phase2PrefilterRegexsetServed),
}
}
#[cfg(test)]
pub(crate) fn take_mark_stats() -> MarkSnapshot {
mark_snapshot_from_typed(&keyhog_profile::take_typed_metrics())
}
#[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);
}
}