blob: 7e0979cb0fa54aa6da16623070809dd6cdc81b66 [file]
// Copyright 2022 CeresDB Project Authors. Licensed under Apache-2.0.
//! Alloc tracker
use std::sync::atomic::{AtomicUsize, Ordering};
/// Collect memory usage from tracker, useful for extending the tracker
pub trait Collector {
/// Called when `bytes` bytes memory is allocated and tracked by the tracker
fn on_allocate(&self, bytes: usize);
/// Called when `bytes` bytes memory is freed and tracked by the tracker
fn on_free(&self, bytes: usize);
}
/// A tracker to track memory in used
// TODO(yingwen): Impl a thread local or local tracker that are not thread safe,
// and collect statistics into the thread safe one for better performance
pub struct Tracker<T: Collector> {
collector: T,
bytes_allocated: AtomicUsize,
}
impl<T: Collector> Tracker<T> {
pub fn new(collector: T) -> Self {
Self {
collector,
bytes_allocated: AtomicUsize::new(0),
}
}
/// Increase consumption of this tracker by bytes
pub fn consume(&self, bytes: usize) {
self.bytes_allocated.fetch_add(bytes, Ordering::Relaxed);
self.collector.on_allocate(bytes);
}
/// Decrease consumption of this tracker by bytes
///
/// The caller should guarantee the released bytes wont larger than bytes
/// already consumed
pub fn release(&self, bytes: usize) {
self.bytes_allocated.fetch_sub(bytes, Ordering::Relaxed);
self.collector.on_free(bytes);
}
/// Bytes allocated
pub fn bytes_allocated(&self) -> usize {
self.bytes_allocated.load(Ordering::Relaxed)
}
}
impl<T: Collector> Drop for Tracker<T> {
fn drop(&mut self) {
let bytes = *self.bytes_allocated.get_mut();
self.collector.on_free(bytes);
}
}
/// The noop collector does nothing on alloc and free
struct NoopCollector;
impl Collector for NoopCollector {
fn on_allocate(&self, _bytes: usize) {}
fn on_free(&self, _bytes: usize) {}
}
/// A simple tracker hides the collector api
pub struct SimpleTracker(Tracker<NoopCollector>);
impl Default for SimpleTracker {
fn default() -> Self {
Self(Tracker::new(NoopCollector))
}
}
impl SimpleTracker {
/// Increase consumption of this tracker by bytes
#[inline]
pub fn consume(&self, bytes: usize) {
self.0.consume(bytes);
}
/// Decrease consumption of this tracker by bytes
///
/// The caller should guarantee the released bytes wont larger than bytes
/// already consumed
#[inline]
pub fn release(&self, bytes: usize) {
self.0.release(bytes);
}
/// Bytes allocated
pub fn bytes_allocated(&self) -> usize {
self.0.bytes_allocated()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_tracker() {
let tracker = SimpleTracker::default();
tracker.consume(256);
assert_eq!(256, tracker.bytes_allocated());
tracker.release(100);
assert_eq!(156, tracker.bytes_allocated());
}
#[test]
fn test_collector() {
use std::sync::atomic::AtomicBool;
struct MockCollector {
allocated: AtomicBool,
freed: AtomicBool,
}
impl MockCollector {
fn new() -> Self {
Self {
allocated: AtomicBool::new(false),
freed: AtomicBool::new(false),
}
}
}
impl Drop for MockCollector {
fn drop(&mut self) {
assert!(*self.allocated.get_mut());
assert!(*self.freed.get_mut());
}
}
impl Collector for MockCollector {
fn on_allocate(&self, bytes: usize) {
assert_eq!(800, bytes);
self.allocated.store(true, Ordering::Relaxed);
}
fn on_free(&self, bytes: usize) {
if self.freed.load(Ordering::Relaxed) {
assert_eq!(440, bytes);
} else {
assert_eq!(360, bytes);
}
self.freed.store(true, Ordering::Relaxed);
}
}
let tracker = Tracker::new(MockCollector::new());
tracker.consume(800);
tracker.release(360);
}
}