use std::cell::Cell;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[derive(Clone, Copy)]
pub(crate) enum StmtClass {
InsertSingle,
InsertMulti,
Copy,
Update,
Delete,
Other,
}
#[derive(Clone, Copy)]
pub(crate) enum Category {
Data,
Version,
IndexKey,
}
const CLASS_COUNT: usize = 6;
const CLASS_NAMES: [&str; CLASS_COUNT] = ["insert_single", "insert_multi", "copy", "update", "delete", "other"];
struct ClassCounters {
data_bytes: AtomicU64,
version_bytes: AtomicU64,
index_key_bytes: AtomicU64,
rows: AtomicU64,
}
impl ClassCounters {
const fn new() -> Self {
Self {
data_bytes: AtomicU64::new(0),
version_bytes: AtomicU64::new(0),
index_key_bytes: AtomicU64::new(0),
rows: AtomicU64::new(0),
}
}
fn counter(&self, cat: Category) -> &AtomicU64 {
match cat {
Category::Data => &self.data_bytes,
Category::Version => &self.version_bytes,
Category::IndexKey => &self.index_key_bytes,
}
}
}
static ENABLED: AtomicBool = AtomicBool::new(false);
static COUNTERS: [ClassCounters; CLASS_COUNT] = [
ClassCounters::new(),
ClassCounters::new(),
ClassCounters::new(),
ClassCounters::new(),
ClassCounters::new(),
ClassCounters::new(),
];
thread_local! {
static CURRENT: Cell<StmtClass> = const { Cell::new(StmtClass::Other) };
}
fn class_index(class: StmtClass) -> usize {
match class {
StmtClass::InsertSingle => 0,
StmtClass::InsertMulti => 1,
StmtClass::Copy => 2,
StmtClass::Update => 3,
StmtClass::Delete => 4,
StmtClass::Other => 5,
}
}
pub(crate) fn set_enabled(on: bool) {
ENABLED.store(on, Ordering::Relaxed);
}
#[inline]
pub(crate) fn enabled() -> bool {
ENABLED.load(Ordering::Relaxed)
}
#[inline]
pub(crate) fn add(cat: Category, bytes: u64) {
let ci = CURRENT.with(|c| class_index(c.get()));
if let Some(counters) = COUNTERS.get(ci) {
counters.counter(cat).fetch_add(bytes, Ordering::Relaxed);
}
}
#[inline]
pub(crate) fn add_row() {
let ci = CURRENT.with(|c| class_index(c.get()));
if let Some(counters) = COUNTERS.get(ci) {
counters.rows.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) struct ClassGuard {
prev: StmtClass,
active: bool,
}
impl Drop for ClassGuard {
fn drop(&mut self) {
if self.active {
let prev = self.prev;
CURRENT.with(|c| c.set(prev));
}
}
}
pub(crate) fn stmt_scope(class: StmtClass) -> ClassGuard {
if !enabled() {
return ClassGuard {
prev: StmtClass::Other,
active: false,
};
}
let prev = CURRENT.with(|c| {
let p = c.get();
c.set(class);
p
});
ClassGuard { prev, active: true }
}
pub(crate) struct ClassStat {
pub class: &'static str,
pub data_bytes: u64,
pub version_bytes: u64,
pub index_key_bytes: u64,
pub rows: u64,
}
pub(crate) fn snapshot() -> Vec<ClassStat> {
COUNTERS
.iter()
.zip(CLASS_NAMES)
.map(|(counters, class)| ClassStat {
class,
data_bytes: counters.data_bytes.load(Ordering::Relaxed),
version_bytes: counters.version_bytes.load(Ordering::Relaxed),
index_key_bytes: counters.index_key_bytes.load(Ordering::Relaxed),
rows: counters.rows.load(Ordering::Relaxed),
})
.collect()
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
mod tests {
use super::{add, add_row, snapshot, Category};
#[test]
fn write_volume_counts_by_category_on_the_current_class() {
let names: Vec<&str> = snapshot().iter().map(|s| s.class).collect();
assert_eq!(names, vec!["insert_single", "insert_multi", "copy", "update", "delete", "other"]);
let before = snapshot();
add_row();
add(Category::Data, 40);
add(Category::Version, 48);
add(Category::IndexKey, 7);
let after = snapshot();
assert_eq!(after[5].data_bytes, before[5].data_bytes + 40, "data bytes on `other`");
assert_eq!(after[5].version_bytes, before[5].version_bytes + 48, "version bytes on `other`");
assert_eq!(after[5].index_key_bytes, before[5].index_key_bytes + 7, "index-key bytes on `other`");
assert_eq!(after[5].rows, before[5].rows + 1, "row event on `other`");
let base_ver = snapshot()[5].version_bytes;
add(Category::Data, 11);
assert_eq!(snapshot()[5].version_bytes, base_ver, "Data write leaves Version untouched");
}
}