#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use chronix::prelude::*;
use chronix::{fields, tags};
struct Counting;
static LIVE: AtomicUsize = AtomicUsize::new(0);
#[allow(unsafe_code)]
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
LIVE.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
LIVE.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOC: Counting = Counting;
fn live() -> usize {
LIVE.load(Ordering::Relaxed)
}
static MEASURING: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn measuring() -> std::sync::MutexGuard<'static, ()> {
MEASURING
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[test]
fn opening_a_database_does_not_allocate_megabytes() {
let _guard = measuring();
let dir = tempfile::tempdir().unwrap();
let cfg = ChronixConfig::builder()
.data_dir(dir.path())
.build()
.unwrap();
assert_eq!(cfg.cdc_capacity, 65_536, "the default is a server's ring");
let before = live();
let db = Chronix::open(cfg).unwrap();
let at_open = live().saturating_sub(before);
assert!(
at_open < 2 * 1024 * 1024,
"opening allocated {:.1} MiB; the CDC ring is the usual cause — it must \
stay lazy",
at_open as f64 / (1024.0 * 1024.0)
);
db.close().unwrap();
}
#[test]
fn the_cdc_ring_is_allocated_on_first_subscription() {
let _guard = measuring();
let dir = tempfile::tempdir().unwrap();
let db = Chronix::open(
ChronixConfig::builder()
.data_dir(dir.path())
.cdc_capacity(65_536)
.build()
.unwrap(),
)
.unwrap();
let before = live();
let sub = db.event_bus().subscribe();
let ring = live().saturating_sub(before);
assert!(
ring > 1024 * 1024,
"a 65 536-event ring is megabytes; got {ring} bytes — is the capacity \
being honoured?"
);
drop(sub);
db.close().unwrap();
}
#[test]
fn the_cdc_capacity_bounds_the_ring() {
let _guard = measuring();
let mut sizes = Vec::new();
for capacity in [1_024, 16_384] {
let dir = tempfile::tempdir().unwrap();
let db = Chronix::open(
ChronixConfig::builder()
.data_dir(dir.path())
.cdc_capacity(capacity)
.build()
.unwrap(),
)
.unwrap();
let before = live();
let sub = db.event_bus().subscribe();
sizes.push(live().saturating_sub(before));
drop(sub);
db.close().unwrap();
}
assert!(
sizes[1] > sizes[0] * 4,
"a 16x capacity should be a far larger ring: {sizes:?}"
);
}
#[test]
fn the_small_preset_sizes_the_bus_for_a_gateway() {
let dir = tempfile::tempdir().unwrap();
let cfg = ChronixConfig::small(dir.path());
assert!(
cfg.cdc_capacity <= 8_192,
"the small preset must not carry a server's ring: {}",
cfg.cdc_capacity
);
}
#[test]
fn the_resident_heap_is_reported_term_by_term() {
let _guard = measuring();
let dir = tempfile::tempdir().unwrap();
let db = Chronix::open(ChronixConfig::small(dir.path())).unwrap();
let key = SeriesKey::new("cpu", tags! { "host" => "h1" }).unwrap();
let points: Vec<Point> = (0..5_000)
.map(|i| {
Point::new(
key.clone(),
fields! { "usage" => f64::from(i) },
i64::from(i) * 1_000_000_000,
)
.unwrap()
})
.collect();
assert!(db.insert_batch(&points).unwrap().is_complete());
let s = db.statistics();
assert_eq!(
s.resident_memory_bytes(),
s.memtable_memory_bytes
+ s.interner_memory_bytes
+ s.wal_buffer_bytes
+ s.catalog_memory_bytes,
"the sum must be the sum"
);
assert!(s.memtable_memory_bytes > 0, "rows are in the memtable");
assert!(
s.interner_memory_bytes > 0,
"the interner holds `cpu`, `host` and `h1`"
);
assert!(s.wal_buffer_bytes > 0, "the WAL writer has a buffer");
db.flush().unwrap();
let s = db.statistics();
assert!(
s.catalog_memory_bytes > 0,
"a flushed segment has a catalog entry"
);
db.close().unwrap();
}