cycle_ptr 0.1.1

Smart pointers, with cycles
Documentation
//! Statistics of GC runs are exposed in tasks.

use std::fmt;
use std::time::Duration;

/// Timings of GC operation.
pub struct GcStats {
    /// Number of unreachable elements.
    /// This is the number of objects that is destroyed during this GC run.
    pub unreachable: usize,
    /// Number of objects that is retained.
    /// This is the number of objects, that the GC observed and found reachable.
    pub retained: usize,

    /// Time spent waiting for the GC lock to be available.
    /// Only multi-thread GC will ever record time in here.
    ///
    /// In order to prevent multiple GCs from running at the same time,
    /// the task exclusion lock ensures only one can run at any given moment.
    ///
    /// If this time is high, it probably means there's a lot of overlapping GC requests.
    /// To reduce, you can place use [DeferGc][crate::DeferGc] in places where this happens.
    /// Or you could make GCs be managed on a thread (using [GcTask::install_callback][crate::sync::GcTask::install_callback]).
    pub task_lock: Duration,
    /// Duration of the mark-sweep operation.
    /// This operation separates all reachable objects from the unreachable objects.
    pub mark_sweep: Duration,
    /// Time spent doing the secondary mark-sweep,
    /// where [Weak][crate::Weak] and [sync::Weak][crate::sync::Weak] are locked out of promotions.
    ///
    /// This only applies to multi-thread operations, if weak pointers are enabled.
    ///
    /// If there is a long time spent here (relative to [mark_sweep][GcStats::mark_sweep] duration),
    /// it could mean there are a lot of weak pointer promotions happening.
    /// Or maybe a single weak pointer got promoted, that causes a large number of dependent objects to suddenly become live.
    pub mark_sweep_with_weak_lockout: Duration,
    /// Time spent running destructors.
    /// This time is spent outside the task lock, so another GC on the same generation can start and not cause issues.
    ///
    /// If a lot of time is spent here, it could mean that there are many objects being destroyed.
    ///
    /// Note that if the destructors start additional GCs,
    /// those GCs will be counted against the [destructors][GcStats::destructors] duration.
    /// If you want timings without those additional GCs,
    /// you should use [GcTask::install_callback][crate::GcTask::install_callback]
    /// or [sync::GcTask::install_callback][crate::sync::GcTask::install_callback]
    /// to move those GC runs outside the destruction, by placing tasks on a job queue.
    pub destructors: Duration,
}

impl GcStats {
    /// Total time spent in the garbage collector.
    #[inline]
    pub fn total_time(&self) -> Duration {
        self.task_lock + self.mark_sweep + self.mark_sweep_with_weak_lockout + self.destructors
    }

    /// The size of the generation, before the GC operation started.
    #[inline]
    pub const fn object_count_before_gc(&self) -> usize {
        self.unreachable + self.retained
    }
}

impl fmt::Debug for GcStats {
    #[inline(never)]
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.debug_struct("GcStats")
            .field("object_count_before_gc", &self.object_count_before_gc())
            .field("unreachable", &self.unreachable)
            .field("retained", &self.retained)
            .field("total_time", &self.total_time())
            .field("task_lock", &self.task_lock)
            .field("mark_sweep", &self.mark_sweep)
            .field(
                "mark_sweep_with_weak_lockout",
                &self.mark_sweep_with_weak_lockout,
            )
            .field("destructors", &self.destructors)
            .finish()
    }
}