use alloc::rc::Rc;
use core::cell::Cell;
#[derive(Debug, Copy, Clone)]
pub struct Pacing {
pub sleep_factor: f64,
pub min_sleep: usize,
pub mark_factor: f64,
pub trace_factor: f64,
pub keep_factor: f64,
pub drop_factor: f64,
pub free_factor: f64,
}
impl Pacing {
pub const DEFAULT: Pacing = Pacing {
sleep_factor: 0.5,
min_sleep: 4096,
mark_factor: 0.1,
trace_factor: 0.4,
keep_factor: 0.05,
drop_factor: 0.2,
free_factor: 0.3,
};
pub const STOP_THE_WORLD: Pacing = Pacing {
sleep_factor: 1.0,
min_sleep: 4096,
mark_factor: 0.0,
trace_factor: 0.0,
keep_factor: 0.0,
drop_factor: 0.0,
free_factor: 0.0,
};
}
impl Default for Pacing {
#[inline]
fn default() -> Pacing {
Self::DEFAULT
}
}
#[derive(Debug, Default)]
struct MetricsInner {
pacing: Cell<Pacing>,
total_gcs: Cell<usize>,
total_gc_bytes: Cell<usize>,
wakeup_amount: Cell<f64>,
artificial_debt: Cell<f64>,
allocated_gc_bytes: Cell<usize>,
dropped_gc_bytes: Cell<usize>,
freed_gc_bytes: Cell<usize>,
marked_gcs: Cell<usize>,
marked_gc_bytes: Cell<usize>,
traced_gcs: Cell<usize>,
traced_gc_bytes: Cell<usize>,
remembered_gcs: Cell<usize>,
remembered_gc_bytes: Cell<usize>,
}
#[derive(Clone)]
pub struct Metrics(Rc<MetricsInner>);
impl Metrics {
pub(crate) fn new() -> Self {
Self(Default::default())
}
#[cfg(feature = "tracing")]
pub(crate) fn arena_id(&self) -> tracing::field::DebugValue<*const ()> {
tracing::field::debug(Rc::as_ptr(&self.0) as *const ())
}
#[inline]
pub fn set_pacing(&self, pacing: Pacing) {
self.0.pacing.set(pacing);
}
#[inline]
pub fn total_gc_count(&self) -> usize {
self.0.total_gcs.get()
}
#[inline]
pub fn total_gc_allocation(&self) -> usize {
self.0.total_gc_bytes.get()
}
#[inline]
pub fn adjust_debt(&self, bytes: f64) {
cell_update(&self.0.artificial_debt, |d| d + bytes);
}
#[inline]
pub fn allocation_debt(&self) -> f64 {
let total_gcs = self.0.total_gcs.get();
if total_gcs == 0 {
return 0.0;
}
let allocated_bytes = self.0.allocated_gc_bytes.get() as f64;
let cycle_debits =
allocated_bytes - self.0.wakeup_amount.get() + self.0.artificial_debt.get();
if cycle_debits <= 0.0 {
return 0.0;
}
let pacing = self.0.pacing.get();
let cycle_credits = self.0.marked_gc_bytes.get() as f64 * pacing.mark_factor
+ self.0.traced_gc_bytes.get() as f64 * pacing.trace_factor
+ self.0.remembered_gc_bytes.get() as f64 * pacing.keep_factor
+ self.0.dropped_gc_bytes.get() as f64 * pacing.drop_factor
+ self.0.freed_gc_bytes.get() as f64 * pacing.free_factor;
(cycle_debits - cycle_credits).max(0.0)
}
pub(crate) fn finish_cycle(&self, reset_debt: bool) {
let pacing = self.0.pacing.get();
let remembered_size = self.0.remembered_gc_bytes.get();
let wakeup_amount =
(remembered_size as f64 * pacing.sleep_factor).max(pacing.min_sleep as f64);
let artificial_debt = if reset_debt {
0.0
} else {
self.allocation_debt()
};
self.0.wakeup_amount.set(wakeup_amount);
self.0.artificial_debt.set(artificial_debt);
self.0.allocated_gc_bytes.set(0);
self.0.dropped_gc_bytes.set(0);
self.0.freed_gc_bytes.set(0);
self.0.marked_gcs.set(0);
self.0.marked_gc_bytes.set(0);
self.0.traced_gcs.set(0);
self.0.traced_gc_bytes.set(0);
self.0.remembered_gcs.set(0);
self.0.remembered_gc_bytes.set(0);
}
#[inline]
pub(crate) fn mark_gc_allocated(&self, bytes: usize) {
cell_update(&self.0.total_gcs, |c| c + 1);
cell_update(&self.0.total_gc_bytes, |b| b + bytes);
cell_update(&self.0.allocated_gc_bytes, |b| b.saturating_add(bytes));
}
#[inline]
pub(crate) fn mark_gc_dropped(&self, bytes: usize) {
cell_update(&self.0.dropped_gc_bytes, |b| b.saturating_add(bytes));
}
#[inline]
pub(crate) fn mark_gc_freed(&self, bytes: usize) {
cell_update(&self.0.total_gcs, |c| c - 1);
cell_update(&self.0.total_gc_bytes, |b| b - bytes);
cell_update(&self.0.freed_gc_bytes, |b| b.saturating_add(bytes));
}
#[inline]
pub(crate) fn mark_gc_marked(&self, bytes: usize) {
cell_update(&self.0.marked_gcs, |c| c + 1);
cell_update(&self.0.marked_gc_bytes, |b| b + bytes);
}
#[inline]
pub(crate) fn mark_gc_traced(&self, bytes: usize) {
cell_update(&self.0.traced_gcs, |c| c + 1);
cell_update(&self.0.traced_gc_bytes, |b| b + bytes);
}
#[inline]
pub(crate) fn mark_gc_untraced(&self, bytes: usize) {
cell_update(&self.0.traced_gcs, |c| c - 1);
cell_update(&self.0.traced_gc_bytes, |b| b - bytes);
}
#[inline]
pub(crate) fn mark_gc_remembered(&self, bytes: usize) {
cell_update(&self.0.remembered_gcs, |c| c + 1);
cell_update(&self.0.remembered_gc_bytes, |b| b + bytes);
}
}
#[inline]
fn cell_update<T: Copy>(c: &Cell<T>, f: impl FnOnce(T) -> T) {
c.set(f(c.get()))
}