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}