use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
pub trait NonCacheReporter: Send + Sync {
fn name(&self) -> &str;
fn current_bytes(&self) -> u64;
}
pub struct AtomicReporter {
name: String,
gauge: Arc<AtomicU64>,
}
impl AtomicReporter {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
gauge: Arc::new(AtomicU64::new(0)),
}
}
#[must_use]
pub fn gauge(&self) -> MemoryGauge {
MemoryGauge {
inner: Arc::clone(&self.gauge),
}
}
}
impl NonCacheReporter for AtomicReporter {
fn name(&self) -> &str {
&self.name
}
fn current_bytes(&self) -> u64 {
self.gauge.load(Ordering::Relaxed)
}
}
#[derive(Clone, Debug)]
pub struct MemoryGauge {
inner: Arc<AtomicU64>,
}
impl MemoryGauge {
#[must_use]
pub fn disconnected() -> Self {
Self {
inner: Arc::new(AtomicU64::new(0)),
}
}
pub fn set(&self, bytes: u64) {
self.inner.store(bytes, Ordering::Relaxed);
}
pub fn add(&self, bytes: u64) {
self.inner.fetch_add(bytes, Ordering::Relaxed);
}
pub fn sub(&self, bytes: u64) {
let mut current = self.inner.load(Ordering::Relaxed);
loop {
let next = current.saturating_sub(bytes);
match self.inner.compare_exchange_weak(
current,
next,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => return,
Err(observed) => current = observed,
}
}
}
#[must_use]
pub fn current(&self) -> u64 {
self.inner.load(Ordering::Relaxed)
}
#[must_use]
pub fn guard(&self, bytes: u64) -> GaugeGuard {
self.add(bytes);
GaugeGuard {
gauge: self.clone(),
bytes,
}
}
}
#[derive(Debug)]
pub struct GaugeGuard {
gauge: MemoryGauge,
bytes: u64,
}
impl Drop for GaugeGuard {
fn drop(&mut self) {
self.gauge.sub(self.bytes);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::{AtomicReporter, NonCacheReporter};
#[test]
fn gauge_updates_are_visible_through_reporter() {
let reporter = AtomicReporter::new("reasoner");
let gauge = reporter.gauge();
assert_eq!(reporter.current_bytes(), 0);
gauge.add(1000);
assert_eq!(reporter.current_bytes(), 1000);
gauge.set(4096);
assert_eq!(reporter.current_bytes(), 4096);
gauge.sub(100);
assert_eq!(reporter.current_bytes(), 3996);
gauge.sub(u64::MAX);
assert_eq!(reporter.current_bytes(), 0);
}
#[test]
fn gauge_guard_adds_then_subtracts_on_drop() {
let reporter = AtomicReporter::new("executor");
let gauge = reporter.gauge();
{
let _g = gauge.guard(1024);
assert_eq!(reporter.current_bytes(), 1024);
let _g2 = gauge.guard(512);
assert_eq!(reporter.current_bytes(), 1536);
}
assert_eq!(reporter.current_bytes(), 0);
}
#[test]
fn reporter_name_is_exposed() {
let reporter = AtomicReporter::new("reasoner");
let dyn_reporter: Arc<dyn NonCacheReporter> = Arc::new(reporter);
assert_eq!(dyn_reporter.name(), "reasoner");
}
}