use super::*;
use std::sync::atomic::AtomicBool;
#[test]
fn timed_is_noop_when_no_sink_installed() {
assert!(current().is_none());
let ran = AtomicBool::new(false);
let out = timed(ReadPhase::Io, || {
ran.store(true, Ordering::Relaxed);
7
});
assert_eq!(out, 7);
assert!(ran.load(Ordering::Relaxed));
assert!(current().is_none(), "no sink leaked onto the thread");
}
#[test]
fn timed_accumulates_into_the_installed_bucket_only() {
let sink = Arc::new(ReadPhaseTimings::default());
let _g = install(Some(sink.clone()));
timed(ReadPhase::Decompress, || {
std::thread::sleep(std::time::Duration::from_millis(2));
});
assert!(sink.nanos(ReadPhase::Decompress) > 0);
for other in [ReadPhase::Io, ReadPhase::Decode, ReadPhase::Merge] {
assert_eq!(sink.nanos(other), 0, "only the timed phase accumulates");
assert!(
sink.snapshot(other).is_none(),
"a phase no timed region ever covered must report NOT ENTERED, which is \
what lets emission report it as ABSENT"
);
}
assert_eq!(
sink.snapshot(ReadPhase::Decompress),
Some(sink.nanos(ReadPhase::Decompress)),
"the timed phase reports as ENTERED, carrying its accumulated duration"
);
}
#[test]
fn a_phase_that_ran_and_measured_zero_is_not_a_phase_that_never_ran() {
let sink = ReadPhaseTimings::default();
sink.add_nanos(ReadPhase::Merge, 0);
assert_eq!(sink.nanos(ReadPhase::Merge), 0);
assert!(
sink.snapshot(ReadPhase::Merge) == Some(0),
"completing a timed region IS the evidence the phase ran, whatever it measured"
);
assert!(
sink.snapshot(ReadPhase::Io).is_none(),
"a phase nothing timed stays unentered — the two zeros are distinguishable"
);
}
#[test]
fn guard_restores_the_previous_sink_on_drop() {
assert!(current().is_none());
{
let _g = install(Some(Arc::new(ReadPhaseTimings::default())));
assert!(current().is_some());
assert!(sink_active());
{
let _nested = install(None);
assert!(!sink_active(), "a nested None install deactivates");
}
assert!(sink_active(), "the outer sink is restored");
}
assert!(
current().is_none() && !sink_active(),
"dropping the guard uninstalls the sink (no leak across scans)"
);
}
#[test]
fn sink_propagates_across_a_spawned_thread() {
let sink = Arc::new(ReadPhaseTimings::default());
let _g = install(Some(sink.clone()));
let captured = current();
std::thread::spawn(move || {
let _child = install(captured);
timed(ReadPhase::Io, || {
std::thread::sleep(std::time::Duration::from_millis(2))
});
})
.join()
.expect("child thread joins");
assert!(
sink.nanos(ReadPhase::Io) > 0,
"the child thread's io must land in the SAME Arc the parent's meter reads"
);
}
#[test]
fn scoped_is_a_noop_without_a_sink() {
assert!(current().is_none());
assert!(
scoped(ReadPhase::Merge).is_none(),
"no sink means no timer is even constructed (no Instant::now)"
);
}
#[test]
fn a_timer_records_into_the_sink_it_captured_not_the_one_installed_at_drop() {
let sink = Arc::new(ReadPhaseTimings::default());
let guard = install(Some(sink.clone()));
let timer = scoped(ReadPhase::Decode);
assert!(timer.is_some(), "a metered thread builds a timer");
drop(guard);
assert!(current().is_none());
std::thread::sleep(std::time::Duration::from_millis(2));
drop(timer);
assert!(
sink.nanos(ReadPhase::Decode) > 0,
"the timer must record into the sink it CAPTURED, not into whatever is \
installed when it happens to drop"
);
}
#[test]
fn add_nanos_saturates_rather_than_wrapping() {
let sink = ReadPhaseTimings::default();
sink.add_nanos(ReadPhase::Decode, u64::MAX);
sink.add_nanos(ReadPhase::Decode, 10);
assert_eq!(
sink.nanos(ReadPhase::Decode),
u64::MAX,
"a saturating add keeps a huge total huge; a wrap would read as 'fast'"
);
}
#[cfg(all(feature = "write-support", not(feature = "tombstones")))]
#[test]
fn merge_timing_subtracts_the_recv_wait_accrued_inside_it() {
let sink = Arc::new(ReadPhaseTimings::default());
let _g = install(Some(sink.clone()));
let wall = std::time::Instant::now();
timed_merge_excluding_recv_wait(|| {
let waited = std::time::Instant::now();
std::thread::sleep(std::time::Duration::from_millis(20));
super::super::stream_subphase::add_pull_wait_nanos(
super::super::stream_subphase::elapsed_nanos(waited),
);
});
let wall = elapsed_nanos(wall);
let merge = sink.nanos(ReadPhase::Merge);
assert!(
merge < wall,
"recv-wait must be excluded: merge={merge}ns is not below the step's own \
wall time {wall}ns"
);
}
#[cfg(all(feature = "write-support", not(feature = "tombstones")))]
#[test]
fn merge_timing_never_underflows_when_the_wait_exceeds_the_wall_time() {
let sink = Arc::new(ReadPhaseTimings::default());
let _g = install(Some(sink.clone()));
timed_merge_excluding_recv_wait(|| {
super::super::stream_subphase::add_pull_wait_nanos(u64::MAX);
});
assert_eq!(sink.nanos(ReadPhase::Merge), 0);
assert!(
sink.snapshot(ReadPhase::Merge) == Some(0),
"a merge step whose recv-wait consumed its whole wall time still RAN; \
reporting it as absent states there was nothing to merge, which is false"
);
}
#[test]
fn an_unmetered_seam_never_builds_a_timer() {
assert!(!sink_active());
assert!(scoped(ReadPhase::Io).is_none());
let mut runs = 0;
timed(ReadPhase::Decode, || runs += 1);
#[cfg(all(feature = "write-support", not(feature = "tombstones")))]
timed_merge_excluding_recv_wait(|| runs += 1);
#[cfg(not(all(feature = "write-support", not(feature = "tombstones"))))]
{
runs += 1; }
assert_eq!(runs, 2, "the closure runs on the unmetered fast path too");
}
#[test]
fn a_concurrent_snapshot_never_sees_an_entry_bit_without_its_duration() {
use std::sync::atomic::AtomicBool;
const PRODUCERS: usize = 4;
const ROUNDS: usize = 2_000;
for _ in 0..64 {
let sink = Arc::new(ReadPhaseTimings::default());
let stop = Arc::new(AtomicBool::new(false));
let producers: Vec<_> = (0..PRODUCERS)
.map(|_| {
let sink = sink.clone();
std::thread::spawn(move || {
for _ in 0..ROUNDS {
sink.add_nanos(ReadPhase::Decode, 1);
}
})
})
.collect();
let reader = {
let sink = sink.clone();
let stop = stop.clone();
std::thread::spawn(move || {
let mut seen_entered = false;
loop {
if let Some(nanos) = sink.snapshot(ReadPhase::Decode) {
assert!(
nanos > 0,
"snapshot observed the entry bit for a phase whose \
accumulated duration was not yet visible: emission \
would publish a fabricated 0.0 for work that took \
time (issue #1707, roborev job 149)"
);
seen_entered = true;
}
if seen_entered && stop.load(Ordering::Relaxed) {
break;
}
}
seen_entered
})
};
for t in producers {
t.join().expect("producer");
}
stop.store(true, Ordering::Relaxed);
let seen_entered = reader.join().expect("reader");
assert!(
seen_entered,
"the reader must actually have observed the phase as entered, or this \
case asserted nothing"
);
assert_eq!(
sink.snapshot(ReadPhase::Decode),
Some((PRODUCERS * ROUNDS) as u64),
"and once quiesced the total is exact"
);
}
}
#[test]
fn the_entry_bit_is_published_after_the_counter_with_release_acquire() {
const SRC: &str = include_str!("read_phase.rs");
let body = |name: &str| -> &'static str {
let start = SRC
.find(name)
.unwrap_or_else(|| panic!("{name} not found in read_phase.rs"));
let rest = &SRC[start..];
let end = rest
.find("\n }\n")
.unwrap_or_else(|| panic!("no end of body for {name}"));
&rest[..end]
};
let add = body("pub fn add_nanos(");
let counter_at = add
.find(".fetch_update(")
.expect("add_nanos must update the counter");
let publish_at = add
.find("self.entered.fetch_or(phase.bit(), Ordering::Release)")
.expect(
"add_nanos must publish the entry bit with RELEASE ordering — a Relaxed \
fetch_or orders nothing, so a reader can see the bit without the \
counter update that preceded it",
);
assert!(
counter_at < publish_at,
"the COUNTER must be updated BEFORE the entry bit is published: with the \
bit first, a concurrent snapshot pairs a fresh entry bit with the old \
counter and emits a fabricated 0.0"
);
let snap = body("pub fn snapshot(");
let acquire_at = snap.find("self.entered.load(Ordering::Acquire)").expect(
"snapshot must load the entry bit with ACQUIRE ordering — it is the \
matching half of add_nanos's Release store on the SAME atomic",
);
let read_at = snap
.find("self.nanos(phase)")
.expect("snapshot must read the counter");
assert!(
acquire_at < read_at,
"the entry bit must be ACQUIRE-loaded BEFORE the counter is read, or the \
acquire establishes nothing about the counter load that preceded it"
);
}
#[cfg(feature = "observability-testing")]
mod phase_emission_tests {
use super::*;
use crate::observability::read_metrics::ReadOpMeter;
use crate::observability::{catalog, testing};
#[test]
#[serial_test::serial(read_metrics)]
fn a_phase_that_ran_and_measured_zero_is_published_as_a_zero_sample() {
let mc = testing::metrics_capture();
mc.reset();
{
let mut meter = ReadOpMeter::start(None);
let sink = meter
.phase_sink()
.expect("an installed capture makes the meter live, not inert");
sink.add_nanos(ReadPhase::Merge, 0);
meter.finish();
}
let metrics = mc.flush_and_collect();
let entry = metrics.find(catalog::READ_PHASE_MERGE).unwrap_or_else(|| {
panic!(
"a merge that RAN must publish a sample even when it measured zero — \
skipping it tells the operator there was nothing to merge, which is \
the opposite of the truth; collected: {:?}",
metrics
.entries()
.iter()
.map(|m| m.name.as_str())
.collect::<Vec<_>>()
)
});
assert_eq!(
entry
.points
.iter()
.map(|p| p.count.unwrap_or(0))
.sum::<u64>(),
1,
"exactly ONE sample, and its presence — not its value — is what proves \
the phase ran; points: {:?}",
entry.points
);
assert_eq!(
entry.points.iter().map(|p| p.value).sum::<f64>(),
0.0,
"and its value is an honest zero: the phase ran and measured zero; \
points: {:?}",
entry.points
);
}
#[test]
#[serial_test::serial(read_metrics)]
fn a_phase_that_never_ran_is_still_absent() {
let mc = testing::metrics_capture();
mc.reset();
{
let mut meter = ReadOpMeter::start(None);
let sink = meter.phase_sink().expect("live meter");
sink.add_nanos(ReadPhase::Decode, 5_000);
meter.finish();
}
let metrics = mc.flush_and_collect();
assert!(
metrics.contains(catalog::READ_PHASE_DECODE),
"the phase that ran is published; collected: {:?}",
metrics
.entries()
.iter()
.map(|m| m.name.as_str())
.collect::<Vec<_>>()
);
for absent in [
catalog::READ_PHASE_IO,
catalog::READ_PHASE_DECOMPRESS,
catalog::READ_PHASE_MERGE,
] {
assert!(
!metrics.contains(absent),
"{absent} must be ABSENT — no timed region for it ever ran, and \
absence is how an operator learns the SSTable was uncompressed / \
the scan was single-generation"
);
}
}
}