use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Instant;
#[derive(Clone, Copy)]
pub(crate) enum Phase {
Decode,
TypeConvert,
CheckConstraint,
Prepare,
ValidateBatch,
FkConstraint,
BatchBuild,
Commit,
ArtMaintain,
Total,
}
const PHASE_COUNT: usize = 10;
const PHASE_NAMES: [&str; PHASE_COUNT] = [
"decode",
"type_convert",
"check_constraint",
"prepare",
"validate_batch",
"fk_constraint",
"batch_build",
"commit",
"art_maintain",
"total",
];
struct PhaseCounters {
nanos: AtomicU64,
calls: AtomicU64,
rows: AtomicU64,
}
impl PhaseCounters {
const fn new() -> Self {
Self {
nanos: AtomicU64::new(0),
calls: AtomicU64::new(0),
rows: AtomicU64::new(0),
}
}
}
static ENABLED: AtomicBool = AtomicBool::new(false);
static COUNTERS: [PhaseCounters; PHASE_COUNT] = [
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
PhaseCounters::new(),
];
fn phase_index(phase: Phase) -> usize {
match phase {
Phase::Decode => 0,
Phase::TypeConvert => 1,
Phase::CheckConstraint => 2,
Phase::Prepare => 3,
Phase::ValidateBatch => 4,
Phase::FkConstraint => 5,
Phase::BatchBuild => 6,
Phase::Commit => 7,
Phase::ArtMaintain => 8,
Phase::Total => 9,
}
}
pub(crate) fn set_enabled(on: bool) {
ENABLED.store(on, Ordering::Relaxed);
}
#[inline]
pub(crate) fn enabled() -> bool {
ENABLED.load(Ordering::Relaxed)
}
#[inline]
fn record(phase: Phase, nanos: u64, rows: u64) {
if let Some(counters) = COUNTERS.get(phase_index(phase)) {
counters.nanos.fetch_add(nanos, Ordering::Relaxed);
counters.calls.fetch_add(1, Ordering::Relaxed);
counters.rows.fetch_add(rows, Ordering::Relaxed);
}
}
pub(crate) struct PhaseTimer {
phase: Phase,
rows: u64,
start: Option<Instant>,
}
impl Drop for PhaseTimer {
fn drop(&mut self) {
if let Some(start) = self.start {
record(self.phase, start.elapsed().as_nanos() as u64, self.rows);
}
}
}
#[inline]
pub(crate) fn time(phase: Phase, rows: u64) -> PhaseTimer {
if !enabled() {
return PhaseTimer {
phase,
rows: 0,
start: None,
};
}
PhaseTimer {
phase,
rows,
start: Some(Instant::now()),
}
}
pub(crate) struct PhaseStat {
pub phase: &'static str,
pub total_nanos: u64,
pub calls: u64,
pub rows: u64,
}
pub(crate) fn snapshot() -> Vec<PhaseStat> {
COUNTERS
.iter()
.zip(PHASE_NAMES)
.map(|(counters, phase)| PhaseStat {
phase,
total_nanos: counters.nanos.load(Ordering::Relaxed),
calls: counters.calls.load(Ordering::Relaxed),
rows: counters.rows.load(Ordering::Relaxed),
})
.collect()
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
mod tests {
use super::{record, snapshot, Phase};
#[test]
fn copy_phase_stats_accumulate_per_phase() {
let names: Vec<&str> = snapshot().iter().map(|s| s.phase).collect();
assert_eq!(
names,
vec![
"decode",
"type_convert",
"check_constraint",
"prepare",
"validate_batch",
"fk_constraint",
"batch_build",
"commit",
"art_maintain",
"total",
]
);
let before = snapshot();
record(Phase::ArtMaintain, 500, 100);
let after = snapshot();
assert_eq!(
after[8].total_nanos,
before[8].total_nanos + 500,
"nanos on art_maintain"
);
assert_eq!(after[8].calls, before[8].calls + 1, "calls on art_maintain");
assert_eq!(after[8].rows, before[8].rows + 100, "rows on art_maintain");
assert_eq!(
after[9].total_nanos, before[9].total_nanos,
"total untouched by art_maintain write"
);
}
}