khora_core/memory/mod.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Provides a public interface for querying engine-wide memory allocation statistics.
16//!
17//! This module defines a set of global atomic counters for detailed memory tracking.
18//! It forms a "contract" where a registered global allocator is responsible for
19//! incrementing these counters, and any part of the engine can read them in a
20//! thread-safe manner to monitor memory usage.
21//!
22//! The primary use case is for the `khora-telemetry` crate to collect these stats
23//! and feed them into the Dynamic Context Core (DCC) for adaptive decision-making.
24
25use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
26
27// --- Global Memory Counters ---
28
29/// Tracks the total number of bytes currently allocated by the registered global allocator.
30pub static CURRENTLY_ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
31
32/// Tracks the peak number of bytes ever allocated simultaneously during the application's lifetime.
33pub static PEAK_ALLOCATED_BYTES: AtomicU64 = AtomicU64::new(0);
34
35/// Tracks the total number of allocation calls made.
36pub static TOTAL_ALLOCATIONS: AtomicU64 = AtomicU64::new(0);
37
38/// Tracks the total number of deallocation calls made.
39pub static TOTAL_DEALLOCATIONS: AtomicU64 = AtomicU64::new(0);
40
41/// Tracks the total number of reallocation calls made.
42pub static TOTAL_REALLOCATIONS: AtomicU64 = AtomicU64::new(0);
43
44/// Tracks the cumulative total of bytes ever allocated over the application's lifetime.
45pub static BYTES_ALLOCATED_LIFETIME: AtomicU64 = AtomicU64::new(0);
46
47/// Tracks the cumulative total of bytes ever deallocated over the application's lifetime.
48pub static BYTES_DEALLOCATED_LIFETIME: AtomicU64 = AtomicU64::new(0);
49
50/// Tracks the number of "large" allocations (e.g., >= 1MB).
51pub static LARGE_ALLOCATIONS: AtomicU64 = AtomicU64::new(0);
52
53/// Tracks the cumulative total of bytes from "large" allocations.
54pub static LARGE_ALLOCATION_BYTES: AtomicU64 = AtomicU64::new(0);
55
56/// Tracks the number of "small" allocations (e.g., < 1KB).
57pub static SMALL_ALLOCATIONS: AtomicU64 = AtomicU64::new(0);
58
59/// Tracks the cumulative total of bytes from "small" allocations.
60pub static SMALL_ALLOCATION_BYTES: AtomicU64 = AtomicU64::new(0);
61
62// --- Data Structures for Reporting ---
63
64/// A snapshot of comprehensive memory allocation statistics, including derived metrics.
65#[derive(Debug, Clone, Copy, Default)]
66pub struct ExtendedMemoryStats {
67 // --- Current State ---
68 /// The total number of bytes currently in use.
69 pub current_allocated_bytes: usize,
70 /// The maximum number of bytes that were ever in use simultaneously.
71 pub peak_allocated_bytes: u64,
72
73 // --- Allocation Counters ---
74 /// The total number of times an allocation was requested.
75 pub total_allocations: u64,
76 /// The total number of times a deallocation was requested.
77 pub total_deallocations: u64,
78 /// The total number of times a reallocation was requested.
79 pub total_reallocations: u64,
80 /// The net number of active allocations (`total_allocations` - `total_deallocations`).
81 pub net_allocations: i64,
82
83 // --- Lifetime Totals ---
84 /// The cumulative sum of all bytes ever allocated.
85 pub bytes_allocated_lifetime: u64,
86 /// The cumulative sum of all bytes ever deallocated.
87 pub bytes_deallocated_lifetime: u64,
88 /// The net number of bytes allocated over the lifetime. Should be equal to `current_allocated_bytes`.
89 pub bytes_net_lifetime: i64,
90
91 // --- Size Category Tracking ---
92 /// The number of allocations classified as "large".
93 pub large_allocations: u64,
94 /// The total byte size of all "large" allocations.
95 pub large_allocation_bytes: u64,
96 /// The number of allocations classified as "small".
97 pub small_allocations: u64,
98 /// The total byte size of all "small" allocations.
99 pub small_allocation_bytes: u64,
100 /// The number of allocations not classified as small or large.
101 pub medium_allocations: u64,
102 /// The total byte size of all "medium" allocations.
103 pub medium_allocation_bytes: u64,
104
105 // --- Calculated Metrics ---
106 /// The average size of a single allocation (`bytes_allocated_lifetime` / `total_allocations`).
107 pub average_allocation_size: f64,
108 /// A rough measure of memory fragmentation (`1.0 - current / peak`).
109 pub fragmentation_ratio: f64,
110 /// The ratio of memory still in use compared to all memory ever allocated.
111 pub allocation_efficiency: f64,
112}
113
114impl ExtendedMemoryStats {
115 /// Populates the derived metrics based on the raw counter values.
116 pub fn calculate_derived_metrics(&mut self) {
117 if self.total_allocations > 0 {
118 self.average_allocation_size =
119 self.bytes_allocated_lifetime as f64 / self.total_allocations as f64;
120 }
121
122 if self.peak_allocated_bytes > 0 {
123 self.fragmentation_ratio =
124 1.0 - (self.current_allocated_bytes as f64 / self.peak_allocated_bytes as f64);
125 }
126
127 if self.bytes_allocated_lifetime > 0 {
128 self.allocation_efficiency =
129 self.current_allocated_bytes as f64 / self.bytes_allocated_lifetime as f64;
130 }
131
132 self.medium_allocations =
133 self.total_allocations - self.small_allocations - self.large_allocations;
134 self.medium_allocation_bytes = self.bytes_allocated_lifetime
135 - self.small_allocation_bytes
136 - self.large_allocation_bytes;
137 }
138}
139
140// --- Public API for Reading Stats ---
141
142/// Takes a snapshot of all global memory counters and returns them in a structured format.
143///
144/// This function is the primary entry point for querying memory statistics. It reads
145/// all counters atomically (using `Ordering::Relaxed`) and calculates several
146/// derived metrics.
147pub fn get_extended_memory_stats() -> ExtendedMemoryStats {
148 let current_allocated = CURRENTLY_ALLOCATED_BYTES.load(Ordering::Relaxed);
149 let peak_allocated = PEAK_ALLOCATED_BYTES.load(Ordering::Relaxed);
150 let total_allocs = TOTAL_ALLOCATIONS.load(Ordering::Relaxed);
151 let total_deallocs = TOTAL_DEALLOCATIONS.load(Ordering::Relaxed);
152 let total_reallocs = TOTAL_REALLOCATIONS.load(Ordering::Relaxed);
153 let bytes_alloc_lifetime = BYTES_ALLOCATED_LIFETIME.load(Ordering::Relaxed);
154 let bytes_dealloc_lifetime = BYTES_DEALLOCATED_LIFETIME.load(Ordering::Relaxed);
155 let large_allocs = LARGE_ALLOCATIONS.load(Ordering::Relaxed);
156 let large_alloc_bytes = LARGE_ALLOCATION_BYTES.load(Ordering::Relaxed);
157 let small_allocs = SMALL_ALLOCATIONS.load(Ordering::Relaxed);
158 let small_alloc_bytes = SMALL_ALLOCATION_BYTES.load(Ordering::Relaxed);
159
160 let mut stats = ExtendedMemoryStats {
161 current_allocated_bytes: current_allocated,
162 peak_allocated_bytes: peak_allocated,
163 total_allocations: total_allocs,
164 total_deallocations: total_deallocs,
165 total_reallocations: total_reallocs,
166 net_allocations: total_allocs as i64 - total_deallocs as i64,
167 bytes_allocated_lifetime: bytes_alloc_lifetime,
168 bytes_deallocated_lifetime: bytes_dealloc_lifetime,
169 bytes_net_lifetime: bytes_alloc_lifetime as i64 - bytes_dealloc_lifetime as i64,
170 large_allocations: large_allocs,
171 large_allocation_bytes: large_alloc_bytes,
172 small_allocations: small_allocs,
173 small_allocation_bytes: small_alloc_bytes,
174 ..Default::default()
175 };
176
177 stats.calculate_derived_metrics();
178 stats
179}
180
181/// Gets the total number of bytes currently allocated by the global allocator.
182///
183/// This is a lightweight alternative to `get_extended_memory_stats` for when only
184/// the current usage is needed.
185pub fn get_currently_allocated_bytes() -> usize {
186 CURRENTLY_ALLOCATED_BYTES.load(Ordering::Relaxed)
187}