Skip to main content

nodedb_mem/
metrics.rs

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