Skip to main content

nodedb_mem/
metrics.rs

1//! Metrics export for the memory governor.
2//!
3//! Provides jemalloc introspection to report actual RSS, mapped memory,
4//! and arena statistics alongside the governor's logical budget tracking.
5
6/// System memory statistics from jemalloc.
7#[derive(Debug, Clone)]
8pub struct SystemMemoryStats {
9    /// Resident Set Size — actual physical memory used.
10    pub rss_bytes: usize,
11
12    /// Total bytes allocated by the application.
13    pub allocated_bytes: usize,
14
15    /// Total bytes in active pages (mapped and potentially dirty).
16    pub active_bytes: usize,
17
18    /// Total bytes mapped by the allocator (may exceed active).
19    pub mapped_bytes: usize,
20
21    /// Total bytes retained in the allocator's caches.
22    pub retained_bytes: usize,
23
24    /// Fragmentation ratio: `(active - allocated) / active`.
25    ///
26    /// Measures how much memory the allocator has claimed from the OS
27    /// (active pages) but isn't actually used by application objects.
28    /// This gap comes from jemalloc internal metadata, free-list
29    /// fragmentation, and thread cache overhead.
30    ///
31    /// Healthy: < 0.15 (15%). Warning: 0.15–0.25. Critical: > 0.25.
32    /// A sustained ratio above 0.25 indicates severe fragmentation —
33    /// the process is consuming significantly more RSS than its live
34    /// data warrants, and OOM risk increases under memory pressure.
35    pub fragmentation_ratio: f64,
36}
37
38impl SystemMemoryStats {
39    /// Query jemalloc for current system memory statistics.
40    ///
41    /// Returns `None` if jemalloc introspection is unavailable.
42    pub fn query() -> Option<Self> {
43        // Trigger a stats epoch refresh.
44        let _ = tikv_jemalloc_ctl::epoch::advance();
45
46        let allocated = tikv_jemalloc_ctl::stats::allocated::read().ok()?;
47        let active = tikv_jemalloc_ctl::stats::active::read().ok()?;
48        let mapped = tikv_jemalloc_ctl::stats::mapped::read().ok()?;
49        let retained = tikv_jemalloc_ctl::stats::retained::read().ok()?;
50        let resident = tikv_jemalloc_ctl::stats::resident::read().ok()?;
51
52        // Fragmentation: how much of jemalloc's active memory is wasted.
53        // active = pages the allocator has obtained from the OS.
54        // allocated = bytes the application is actually using.
55        // The difference is internal fragmentation + free-list overhead.
56        let fragmentation_ratio = if active > 0 {
57            (active.saturating_sub(allocated)) as f64 / active as f64
58        } else {
59            0.0
60        };
61
62        Some(Self {
63            rss_bytes: resident,
64            allocated_bytes: allocated,
65            active_bytes: active,
66            mapped_bytes: mapped,
67            retained_bytes: retained,
68            fragmentation_ratio,
69        })
70    }
71
72    /// Returns `true` if fragmentation exceeds the warning threshold (25%).
73    pub fn is_fragmentation_critical(&self) -> bool {
74        self.fragmentation_ratio > 0.25
75    }
76}