mod analytics;
mod collector;
mod event;
#[cfg(feature = "gpu")]
mod gpu;
mod profiler;
mod reporter;
mod snapshot;
#[cfg(test)]
mod test_utils;
pub use analytics::{
AllocationPattern, LeakDetectionConfig, LeakDetectionResult, MemoryAnalytics,
MemoryEfficiencyMetrics, MemoryIssue, MemoryPatternAnalysis, OptimizationRecommendation,
};
pub use collector::{
AllocationStats, ComponentMemoryStats, MemoryMetricsCollector, MemoryMetricsConfig,
MemoryReport,
};
pub use event::{MemoryEvent, MemoryEventType};
pub use profiler::{
MemoryProfiler, MemoryProfilerConfig, PerformanceImpactAnalysis, ProfilingResult,
ProfilingSession, ProfilingSummary, RiskAssessment,
};
pub use reporter::{format_bytes, format_duration};
pub use snapshot::{
clear_snapshots, compare_snapshots, global_snapshot_manager, load_all_snapshots,
save_all_snapshots, take_snapshot, ComponentStatsDiff, MemorySnapshot, SnapshotComponentStats,
SnapshotDiff, SnapshotManager, SnapshotReport,
};
#[cfg(feature = "memory_visualization")]
pub use reporter::ChartFormat;
#[cfg(feature = "gpu")]
pub use gpu::{setup_gpu_memory_tracking, TrackedGpuBuffer, TrackedGpuContext};
use crate::memory::{BufferPool, ChunkProcessor, ChunkProcessor2D};
use ::ndarray::{ArrayBase, Data, Dimension, IxDyn, ViewRepr};
use once_cell::sync::Lazy;
use std::marker::PhantomData;
use std::mem;
use std::sync::Arc;
static GLOBAL_METRICS_COLLECTOR: Lazy<Arc<MemoryMetricsCollector>> =
Lazy::new(|| Arc::new(MemoryMetricsCollector::new(MemoryMetricsConfig::default())));
#[allow(dead_code)]
pub fn global_metrics_collector() -> Arc<MemoryMetricsCollector> {
GLOBAL_METRICS_COLLECTOR.clone()
}
#[allow(dead_code)]
pub fn track_allocation(component: impl Into<String>, size: usize, address: usize) {
let event = MemoryEvent::new(MemoryEventType::Allocation, component, size, address);
GLOBAL_METRICS_COLLECTOR.record_event(event);
}
#[allow(dead_code)]
pub fn track_deallocation(component: impl Into<String>, size: usize, address: usize) {
let event = MemoryEvent::new(MemoryEventType::Deallocation, component, size, address);
GLOBAL_METRICS_COLLECTOR.record_event(event);
}
#[allow(dead_code)]
pub fn track_resize(
component: impl Into<String>,
old_size: usize,
new_size: usize,
address: usize,
) {
let event = MemoryEvent::new(MemoryEventType::Resize, component, new_size, address)
.with_metadata("old_size", old_size.to_string());
GLOBAL_METRICS_COLLECTOR.record_event(event);
}
#[allow(dead_code)]
pub fn generate_memory_report() -> MemoryReport {
GLOBAL_METRICS_COLLECTOR.generate_report()
}
#[allow(dead_code)]
pub fn format_memory_report() -> String {
GLOBAL_METRICS_COLLECTOR.generate_report().format()
}
#[allow(dead_code)]
pub fn reset_memory_metrics() {
GLOBAL_METRICS_COLLECTOR.reset();
}
pub struct TrackedBufferPool<T: Clone + Default> {
inner: BufferPool<T>,
component_name: String,
phantom: PhantomData<T>,
}
impl<T: Clone + Default> TrackedBufferPool<T> {
pub fn new(component_name: impl Into<String>) -> Self {
Self {
inner: BufferPool::new(),
component_name: component_name.into(),
phantom: PhantomData,
}
}
pub fn acquire_vec(&mut self, capacity: usize) -> Vec<T> {
let vec = self.inner.acquire_vec(capacity);
let size = capacity * mem::size_of::<T>();
track_allocation(&self.component_name, size, &vec as *const _ as usize);
vec
}
pub fn release_vec(&mut self, vec: Vec<T>) {
let size = vec.capacity() * mem::size_of::<T>();
track_deallocation(&self.component_name, size, &vec as *const _ as usize);
self.inner.release_vec(vec);
}
pub fn acquire_array(&mut self, size: usize) -> crate::ndarray::Array1<T> {
let array = self.inner.acquire_array(size);
let mem_size = size * mem::size_of::<T>();
track_allocation(&self.component_name, mem_size, array.as_ptr() as usize);
array
}
pub fn release_array(&mut self, array: crate::ndarray::Array1<T>) {
let size = array.len() * mem::size_of::<T>();
track_deallocation(&self.component_name, size, array.as_ptr() as usize);
self.inner.release_array(array);
}
pub fn clear(&mut self) {
self.inner.clear();
}
}
pub struct TrackedChunkProcessor<'a, A, S, D>
where
S: Data<Elem = A>,
D: Dimension,
{
inner: ChunkProcessor<'a, A, S, D>,
component_name: String,
}
impl<'a, A, S, D> TrackedChunkProcessor<'a, A, S, D>
where
S: Data<Elem = A>,
D: Dimension,
{
pub fn new(
component_name: impl Into<String>,
array: &'a ArrayBase<S, D>,
chunkshape: &[usize],
) -> Self {
Self {
inner: ChunkProcessor::new(array, array.raw_dim()),
component_name: component_name.into(),
}
}
pub fn process_chunks_dyn<F>(&mut self, mut f: F)
where
F: FnMut(&ArrayBase<ViewRepr<&A>, IxDyn>, IxDyn),
{
let component_name = self.component_name.clone();
let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, IxDyn>, coords: IxDyn| {
let size = chunk.len() * mem::size_of::<A>();
let address = chunk.as_ptr() as usize;
track_allocation(&component_name, size, address);
f(chunk, coords);
track_deallocation(&component_name, size, address);
};
self.inner.process_chunks_dyn(tracked_f);
}
pub fn num_chunks(&self) -> usize {
self.inner.num_chunks()
}
}
pub struct TrackedChunkProcessor2D<'a, A, S>
where
S: Data<Elem = A>,
{
inner: ChunkProcessor2D<'a, A, S>,
component_name: String,
}
impl<'a, A, S> TrackedChunkProcessor2D<'a, A, S>
where
S: Data<Elem = A>,
{
pub fn new(
array: &'a ArrayBase<S, crate::ndarray::Ix2>,
chunkshape: (usize, usize),
component_name: impl Into<String>,
) -> Self {
Self {
inner: ChunkProcessor2D::new(array, chunkshape),
component_name: component_name.into(),
}
}
pub fn process_chunks<F>(&mut self, mut f: F)
where
F: FnMut(&ArrayBase<ViewRepr<&A>, crate::ndarray::Ix2>, (usize, usize)),
{
let component_name = self.component_name.clone();
let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, crate::ndarray::Ix2>,
coords: (usize, usize)| {
let size = chunk.len() * mem::size_of::<A>();
let address = chunk.as_ptr() as usize;
track_allocation(&component_name, size, address);
f(chunk, coords);
track_deallocation(&component_name, size, address);
};
self.inner.process_chunks(tracked_f);
}
}
#[cfg(test)]
mod tests {
use super::test_utils::MEMORY_METRICS_TEST_MUTEX;
use super::*;
#[test]
fn test_global_memory_metrics() {
let lock = MEMORY_METRICS_TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
reset_memory_metrics();
track_allocation("TestComponent", 1024, 0x1000);
track_allocation("TestComponent", 2048, 0x2000);
track_allocation("OtherComponent", 4096, 0x3000);
track_deallocation("TestComponent", 1024, 0x1000);
let report = generate_memory_report();
assert_eq!(report.total_current_usage, 6144); assert_eq!(report.total_allocation_count, 3);
let test_comp = report
.component_stats
.get("TestComponent")
.expect("Operation failed");
assert_eq!(test_comp.current_usage, 2048);
assert_eq!(test_comp.allocation_count, 2);
reset_memory_metrics();
let report = generate_memory_report();
assert_eq!(report.total_current_usage, 0);
}
}