use std::cell::RefCell;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PerfLevel {
#[default]
Disable,
EnableCount,
EnableTime,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PerfContextSnapshot {
pub get_count: u64,
pub get_from_memtable_time_nanos: u64,
pub get_from_output_files_time_nanos: u64,
pub write_count: u64,
pub write_wal_time_nanos: u64,
pub write_memtable_time_nanos: u64,
pub block_cache_lookup_count: u64,
pub block_cache_hit_count: u64,
pub bloom_check_count: u64,
pub bloom_useful_count: u64,
}
pub struct PerfContext;
thread_local! {
static PERF_LEVEL: RefCell<PerfLevel> = const { RefCell::new(PerfLevel::Disable) };
static PERF_STATE: RefCell<PerfContextSnapshot> =
const { RefCell::new(PerfContextSnapshot::new_const()) };
}
impl PerfContextSnapshot {
const fn new_const() -> Self {
Self {
get_count: 0,
get_from_memtable_time_nanos: 0,
get_from_output_files_time_nanos: 0,
write_count: 0,
write_wal_time_nanos: 0,
write_memtable_time_nanos: 0,
block_cache_lookup_count: 0,
block_cache_hit_count: 0,
bloom_check_count: 0,
bloom_useful_count: 0,
}
}
}
impl PerfContext {
pub fn set_level(level: PerfLevel) -> PerfLevel {
PERF_LEVEL.with(|cell| std::mem::replace(&mut *cell.borrow_mut(), level))
}
pub fn level() -> PerfLevel {
PERF_LEVEL.with(|cell| *cell.borrow())
}
pub fn reset() {
PERF_STATE.with(|cell| *cell.borrow_mut() = PerfContextSnapshot::default());
}
pub fn capture() -> PerfContextSnapshot {
PERF_STATE.with(|cell| *cell.borrow())
}
}
#[inline]
pub(crate) fn is_enabled() -> bool {
PERF_LEVEL.with(|cell| *cell.borrow()) != PerfLevel::Disable
}
#[inline]
pub(crate) fn is_timed() -> bool {
PERF_LEVEL.with(|cell| *cell.borrow()) == PerfLevel::EnableTime
}
#[inline]
pub(crate) fn record_get_call() {
if !is_enabled() {
return;
}
PERF_STATE.with(|cell| cell.borrow_mut().get_count += 1);
}
#[inline]
pub(crate) fn record_write_call() {
if !is_enabled() {
return;
}
PERF_STATE.with(|cell| cell.borrow_mut().write_count += 1);
}
#[inline]
pub(crate) fn record_block_cache_lookup(hit: bool) {
if !is_enabled() {
return;
}
PERF_STATE.with(|cell| {
let mut s = cell.borrow_mut();
s.block_cache_lookup_count += 1;
if hit {
s.block_cache_hit_count += 1;
}
});
}
#[inline]
pub(crate) fn record_bloom_check(useful: bool) {
if !is_enabled() {
return;
}
PERF_STATE.with(|cell| {
let mut s = cell.borrow_mut();
s.bloom_check_count += 1;
if useful {
s.bloom_useful_count += 1;
}
});
}
#[must_use]
pub(crate) struct PerfTimer {
start: Option<u64>,
which: PerfTimerField,
}
#[derive(Clone, Copy)]
pub(crate) enum PerfTimerField {
GetFromMemtable,
GetFromOutputFiles,
WriteWal,
WriteMemtable,
}
impl PerfTimer {
#[inline]
pub(crate) fn new(which: PerfTimerField) -> Self {
let start = if is_timed() {
crate::env::platform_nanos()
} else {
None
};
Self { start, which }
}
}
impl Drop for PerfTimer {
#[inline]
fn drop(&mut self) {
let Some(start) = self.start else {
return;
};
let Some(now) = crate::env::platform_nanos() else {
return;
};
let nanos = now.saturating_sub(start);
PERF_STATE.with(|cell| {
let mut s = cell.borrow_mut();
match self.which {
PerfTimerField::GetFromMemtable => s.get_from_memtable_time_nanos += nanos,
PerfTimerField::GetFromOutputFiles => s.get_from_output_files_time_nanos += nanos,
PerfTimerField::WriteWal => s.write_wal_time_nanos += nanos,
PerfTimerField::WriteMemtable => s.write_memtable_time_nanos += nanos,
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disabled_counters_stay_zero() {
PerfContext::set_level(PerfLevel::Disable);
PerfContext::reset();
record_get_call();
record_write_call();
record_block_cache_lookup(true);
record_bloom_check(false);
{
let _t = PerfTimer::new(PerfTimerField::GetFromMemtable);
std::thread::sleep(std::time::Duration::from_millis(1));
}
let snap = PerfContext::capture();
assert_eq!(snap.get_count, 0);
assert_eq!(snap.write_count, 0);
assert_eq!(snap.block_cache_lookup_count, 0);
assert_eq!(snap.bloom_check_count, 0);
assert_eq!(snap.get_from_memtable_time_nanos, 0);
}
#[test]
fn enable_count_bumps_counters_without_timing() {
PerfContext::set_level(PerfLevel::EnableCount);
PerfContext::reset();
record_get_call();
record_get_call();
record_block_cache_lookup(true);
record_block_cache_lookup(false);
{
let _t = PerfTimer::new(PerfTimerField::GetFromMemtable);
std::thread::sleep(std::time::Duration::from_millis(1));
}
let snap = PerfContext::capture();
assert_eq!(snap.get_count, 2);
assert_eq!(snap.block_cache_lookup_count, 2);
assert_eq!(snap.block_cache_hit_count, 1);
assert_eq!(snap.get_from_memtable_time_nanos, 0);
PerfContext::set_level(PerfLevel::Disable);
}
#[test]
fn enable_time_populates_time_fields() {
PerfContext::set_level(PerfLevel::EnableTime);
PerfContext::reset();
{
let _t = PerfTimer::new(PerfTimerField::WriteWal);
std::thread::sleep(std::time::Duration::from_millis(5));
}
let snap = PerfContext::capture();
assert!(
snap.write_wal_time_nanos >= 1_000_000,
"expected at least 1 ms of WAL time, got {}",
snap.write_wal_time_nanos
);
PerfContext::set_level(PerfLevel::Disable);
}
#[test]
fn level_round_trip() {
let prev = PerfContext::set_level(PerfLevel::EnableCount);
assert_eq!(PerfContext::level(), PerfLevel::EnableCount);
PerfContext::set_level(prev);
assert_eq!(PerfContext::level(), prev);
}
#[test]
fn reset_clears_every_field() {
PerfContext::set_level(PerfLevel::EnableCount);
PerfContext::reset();
record_get_call();
record_bloom_check(true);
PerfContext::reset();
let snap = PerfContext::capture();
assert_eq!(snap.get_count, 0);
assert_eq!(snap.bloom_check_count, 0);
PerfContext::set_level(PerfLevel::Disable);
}
}