#![allow(dead_code)]
use crate::error::{Error, Result};
#[allow(unused_imports)]
pub use crate::foundation::instrumented_vec::{
InstrumentedVec, ProfiledVec, ProfiledVecExt, VecStats,
};
pub const JPEG_MAX_DIMENSION: u32 = 65500;
pub const DEFAULT_MAX_PIXELS: u64 = 100_000_000;
pub const MAX_SCANS: usize = 256;
pub const MAX_ICC_PROFILE_SIZE: usize = 16 * 1024 * 1024;
pub const DEFAULT_MAX_MEMORY: u64 = 512 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct MemoryTracker {
pub allocated: usize,
pub limit: usize,
}
impl MemoryTracker {
#[must_use]
pub fn new(limit: usize) -> Self {
Self {
allocated: 0,
limit,
}
}
#[must_use]
pub fn with_default_limit() -> Self {
Self::new(DEFAULT_MAX_MEMORY as usize)
}
#[must_use]
pub fn unlimited() -> Self {
Self::new(usize::MAX)
}
pub fn try_alloc(&mut self, bytes: usize, context: &'static str) -> Result<()> {
let new_total = self
.allocated
.checked_add(bytes)
.ok_or_else(|| Error::size_overflow(context))?;
if new_total > self.limit {
return Err(Error::allocation_failed(bytes, context));
}
self.allocated = new_total;
Ok(())
}
pub fn free(&mut self, bytes: usize) {
self.allocated = self.allocated.saturating_sub(bytes);
}
#[must_use]
pub fn remaining(&self) -> usize {
self.limit.saturating_sub(self.allocated)
}
#[must_use]
pub fn current(&self) -> usize {
self.allocated
}
pub fn reset(&mut self) {
self.allocated = 0;
}
}
impl Default for MemoryTracker {
fn default() -> Self {
Self::with_default_limit()
}
}
#[derive(Debug, Clone)]
pub struct AllocationInfo {
pub bytes: usize,
pub type_name: &'static str,
pub element_size: usize,
pub count: usize,
pub context: &'static str,
pub location: &'static std::panic::Location<'static>,
}
impl AllocationInfo {
#[must_use]
pub fn summary(&self) -> String {
format!(
"{:>10} | {:<30} | {:<40} | {}:{}",
format_bytes(self.bytes),
self.context,
if self.count > 1 {
format!("{}[{}] × {}", self.type_name, self.count, self.element_size)
} else {
format!("{} ({}B)", self.type_name, self.element_size)
},
self.location
.file()
.rsplit('/')
.next()
.unwrap_or(self.location.file()),
self.location.line()
)
}
}
#[derive(Debug, Clone, Default)]
pub struct EncodeStats {
pub count: usize,
pub total_bytes: usize,
pub peak_bytes: usize,
current_bytes: usize,
pub by_context: Vec<(&'static str, usize)>,
pub allocations: Vec<AllocationInfo>,
pub detailed: bool,
}
impl EncodeStats {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_detailed_tracking() -> Self {
Self {
detailed: true,
..Self::default()
}
}
#[inline]
pub fn record_alloc(&mut self, bytes: usize) {
self.count += 1;
self.total_bytes += bytes;
self.current_bytes += bytes;
if self.current_bytes > self.peak_bytes {
self.peak_bytes = self.current_bytes;
}
}
#[inline]
pub fn record_alloc_named(&mut self, bytes: usize, context: &'static str) {
self.record_alloc(bytes);
self.by_context.push((context, bytes));
}
#[inline]
#[track_caller]
pub fn record_alloc_typed<T>(&mut self, count: usize, context: &'static str) {
let element_size = core::mem::size_of::<T>();
let bytes = count * element_size;
self.record_alloc(bytes);
self.by_context.push((context, bytes));
if self.detailed {
self.allocations.push(AllocationInfo {
bytes,
type_name: std::any::type_name::<T>(),
element_size,
count,
context,
location: std::panic::Location::caller(),
});
}
}
#[inline]
#[track_caller]
pub fn record_alloc_explicit(
&mut self,
bytes: usize,
type_name: &'static str,
element_size: usize,
count: usize,
context: &'static str,
) {
self.record_alloc(bytes);
self.by_context.push((context, bytes));
if self.detailed {
self.allocations.push(AllocationInfo {
bytes,
type_name,
element_size,
count,
context,
location: std::panic::Location::caller(),
});
}
}
#[inline]
pub fn record_dealloc(&mut self, bytes: usize) {
self.current_bytes = self.current_bytes.saturating_sub(bytes);
}
pub fn reset(&mut self) {
self.count = 0;
self.total_bytes = 0;
self.peak_bytes = 0;
self.current_bytes = 0;
self.by_context.clear();
self.allocations.clear();
}
pub fn merge(&mut self, other: &EncodeStats) {
self.count += other.count;
self.total_bytes += other.total_bytes;
self.by_context.extend(other.by_context.iter().cloned());
self.allocations.extend(other.allocations.iter().cloned());
if other.peak_bytes > self.peak_bytes {
self.peak_bytes = other.peak_bytes;
}
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"{} allocations, {} total, {} peak",
self.count,
format_bytes(self.total_bytes),
format_bytes(self.peak_bytes)
)
}
#[must_use]
pub fn detailed_report(&self) -> String {
if !self.detailed || self.allocations.is_empty() {
return "Detailed tracking not enabled or no allocations recorded.".to_string();
}
let mut lines = Vec::with_capacity(self.allocations.len() + 4);
lines.push(format!(
"{:>10} | {:<30} | {:<40} | Location",
"Size", "Context", "Type"
));
lines.push("-".repeat(100));
let mut sorted: Vec<_> = self.allocations.iter().collect();
sorted.sort_by_key(|a| std::cmp::Reverse(a.bytes));
for info in sorted {
lines.push(info.summary());
}
lines.push("-".repeat(100));
lines.push(format!(
"{:>10} | {} allocations, {} peak",
format_bytes(self.total_bytes),
self.count,
format_bytes(self.peak_bytes)
));
lines.join("\n")
}
#[must_use]
pub fn by_context_summary(&self) -> String {
use std::collections::HashMap;
let mut by_ctx: HashMap<&'static str, usize> = HashMap::new();
for (ctx, bytes) in &self.by_context {
*by_ctx.entry(*ctx).or_default() += bytes;
}
let mut sorted: Vec<_> = by_ctx.into_iter().collect();
sorted.sort_by_key(|a| std::cmp::Reverse(a.1));
let mut lines = Vec::with_capacity(sorted.len() + 2);
lines.push(format!("{:>12} | Context", "Total Size"));
lines.push("-".repeat(50));
for (ctx, bytes) in &sorted {
lines.push(format!("{:>12} | {}", format_bytes(*bytes), ctx));
}
lines.join("\n")
}
}
fn format_bytes(bytes: usize) -> String {
if bytes >= 1024 * 1024 {
format!("{:.2} MB", bytes as f64 / (1024.0 * 1024.0))
} else if bytes >= 1024 {
format!("{:.2} KB", bytes as f64 / 1024.0)
} else {
format!("{} B", bytes)
}
}
#[inline]
#[track_caller]
pub fn try_alloc_vec_tracked<T: Default + Clone>(
count: usize,
context: &'static str,
stats: &mut EncodeStats,
) -> Result<Vec<T>> {
let byte_size = count
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, T::default());
stats.record_alloc_typed::<T>(count, context);
Ok(v)
}
#[inline]
#[track_caller]
pub fn try_alloc_zeroed_f32_tracked(
count: usize,
context: &'static str,
stats: &mut EncodeStats,
) -> Result<Vec<f32>> {
let byte_size = count
.checked_mul(4)
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, 0.0f32);
stats.record_alloc_typed::<f32>(count, context);
Ok(v)
}
#[inline]
#[track_caller]
pub fn try_with_capacity_tracked<T>(
capacity: usize,
context: &'static str,
stats: &mut EncodeStats,
) -> Result<Vec<T>> {
let byte_size = capacity
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(capacity)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
stats.record_alloc_typed::<T>(capacity, context);
Ok(v)
}
#[inline]
#[track_caller]
pub fn try_alloc_dct_blocks_tracked(
count: usize,
context: &'static str,
stats: &mut EncodeStats,
) -> Result<Vec<[i16; 64]>> {
let byte_size = count
.checked_mul(64 * 2) .ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, [0i16; 64]);
stats.record_alloc_typed::<[i16; 64]>(count, context);
Ok(v)
}
#[inline]
pub fn checked_size(width: usize, height: usize, bytes_per_pixel: usize) -> Result<usize> {
width
.checked_mul(height)
.and_then(|pixels| pixels.checked_mul(bytes_per_pixel))
.ok_or_else(|| Error::size_overflow("calculating buffer size"))
}
#[inline]
pub fn checked_size_2d(dim1: usize, dim2: usize) -> Result<usize> {
dim1.checked_mul(dim2)
.ok_or_else(|| Error::size_overflow("calculating 2D size"))
}
pub fn validate_dimensions(width: u32, height: u32, max_pixels: u64) -> Result<()> {
if width == 0 || height == 0 {
return Err(Error::invalid_dimensions(
width,
height,
"dimensions cannot be zero",
));
}
if width > JPEG_MAX_DIMENSION || height > JPEG_MAX_DIMENSION {
return Err(Error::invalid_dimensions(
width,
height,
"exceeds JPEG_MAX_DIMENSION (65500)",
));
}
let total_pixels = (width as u64)
.checked_mul(height as u64)
.ok_or_else(|| Error::size_overflow("calculating total pixels"))?;
if total_pixels > max_pixels {
return Err(Error::image_too_large(total_pixels, max_pixels));
}
Ok(())
}
#[inline]
pub fn try_alloc_vec<T: Default + Clone>(count: usize, context: &'static str) -> Result<Vec<T>> {
let byte_size = count
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, T::default());
Ok(v)
}
#[inline]
pub fn try_alloc_zeroed(count: usize, context: &'static str) -> Result<Vec<u8>> {
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(count, context))?;
v.resize(count, 0u8);
Ok(v)
}
#[inline]
pub fn try_alloc_zeroed_f32(count: usize, context: &'static str) -> Result<Vec<f32>> {
let byte_size = count
.checked_mul(4)
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, 0.0f32);
Ok(v)
}
#[inline]
pub fn try_with_capacity<T>(capacity: usize, context: &'static str) -> Result<Vec<T>> {
let byte_size = capacity
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::with_capacity(capacity);
v.try_reserve_exact(capacity)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
Ok(v)
}
#[inline]
pub fn try_alloc_maybeuninit<T: Default + Clone>(
count: usize,
context: &'static str,
) -> Result<Vec<T>> {
let byte_size = count
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, T::default());
Ok(v)
}
#[inline]
pub fn try_alloc_dct_blocks(count: usize, context: &'static str) -> Result<Vec<[i16; 64]>> {
let byte_size = count
.checked_mul(64 * 2) .ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, [0i16; 64]);
Ok(v)
}
#[inline]
pub fn try_alloc_filled<T: Clone>(count: usize, value: T, context: &'static str) -> Result<Vec<T>> {
let byte_size = count
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(count)
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.resize(count, value);
Ok(v)
}
#[inline]
pub fn try_clone_slice<T: Clone>(slice: &[T], context: &'static str) -> Result<Vec<T>> {
let byte_size = slice
.len()
.checked_mul(core::mem::size_of::<T>())
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(slice.len())
.map_err(|_| Error::allocation_failed(byte_size, context))?;
v.extend_from_slice(slice);
Ok(v)
}
#[inline]
pub fn try_gray_to_rgb(data: &[u8], context: &'static str) -> Result<Vec<u8>> {
let len = data
.len()
.checked_mul(3)
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(len)
.map_err(|_| Error::allocation_failed(len, context))?;
for &byte in data {
v.push(byte);
v.push(byte);
v.push(byte);
}
Ok(v)
}
#[inline]
pub fn try_rgba_to_rgb(data: &[u8], context: &'static str) -> Result<Vec<u8>> {
let num_pixels = data.len() / 4;
let len = num_pixels
.checked_mul(3)
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(len)
.map_err(|_| Error::allocation_failed(len, context))?;
for chunk in data.chunks_exact(4) {
v.push(chunk[0]);
v.push(chunk[1]);
v.push(chunk[2]);
}
Ok(v)
}
#[inline]
pub fn try_bgr_to_rgb(data: &[u8], context: &'static str) -> Result<Vec<u8>> {
let mut v = Vec::new();
v.try_reserve_exact(data.len())
.map_err(|_| Error::allocation_failed(data.len(), context))?;
for chunk in data.chunks_exact(3) {
v.push(chunk[2]);
v.push(chunk[1]);
v.push(chunk[0]);
}
Ok(v)
}
#[inline]
pub fn try_bgra_to_rgb(data: &[u8], context: &'static str) -> Result<Vec<u8>> {
let num_pixels = data.len() / 4;
let len = num_pixels
.checked_mul(3)
.ok_or_else(|| Error::size_overflow(context))?;
let mut v = Vec::new();
v.try_reserve_exact(len)
.map_err(|_| Error::allocation_failed(len, context))?;
for chunk in data.chunks_exact(4) {
v.push(chunk[2]);
v.push(chunk[1]);
v.push(chunk[0]);
}
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checked_size() {
assert!(checked_size(100, 100, 3).is_ok());
assert_eq!(checked_size(100, 100, 3).unwrap(), 30000);
assert!(checked_size(usize::MAX, 2, 1).is_err());
}
#[test]
fn test_validate_dimensions() {
assert!(validate_dimensions(1920, 1080, DEFAULT_MAX_PIXELS).is_ok());
assert!(validate_dimensions(0, 100, DEFAULT_MAX_PIXELS).is_err());
assert!(validate_dimensions(100, 0, DEFAULT_MAX_PIXELS).is_err());
assert!(validate_dimensions(70000, 100, DEFAULT_MAX_PIXELS).is_err());
assert!(validate_dimensions(20000, 20000, 100_000_000).is_err()); }
#[test]
fn test_try_alloc_vec() {
let v: Vec<u8> = try_alloc_vec(1000, "test").unwrap();
assert_eq!(v.len(), 1000);
assert!(v.iter().all(|&x| x == 0));
}
#[test]
fn test_try_alloc_zeroed() {
let v = try_alloc_zeroed(1000, "test").unwrap();
assert_eq!(v.len(), 1000);
assert!(v.iter().all(|&x| x == 0));
}
#[test]
fn test_try_with_capacity() {
let v: Vec<u8> = try_with_capacity(1000, "test").unwrap();
assert_eq!(v.capacity(), 1000);
assert_eq!(v.len(), 0);
}
#[test]
fn test_try_alloc_filled() {
let v: Vec<u8> = try_alloc_filled(1000, 128u8, "test").unwrap();
assert_eq!(v.len(), 1000);
assert!(v.iter().all(|&x| x == 128));
}
#[test]
fn test_memory_tracker_basic() {
let mut tracker = MemoryTracker::new(1000);
assert_eq!(tracker.remaining(), 1000);
assert_eq!(tracker.current(), 0);
tracker.try_alloc(400, "test1").unwrap();
assert_eq!(tracker.current(), 400);
assert_eq!(tracker.remaining(), 600);
tracker.try_alloc(300, "test2").unwrap();
assert_eq!(tracker.current(), 700);
assert_eq!(tracker.remaining(), 300);
}
#[test]
fn test_memory_tracker_limit() {
let mut tracker = MemoryTracker::new(1000);
tracker.try_alloc(500, "test1").unwrap();
tracker.try_alloc(500, "test2").unwrap();
assert_eq!(tracker.current(), 1000);
assert_eq!(tracker.remaining(), 0);
let result = tracker.try_alloc(1, "test3");
assert!(result.is_err());
}
#[test]
fn test_memory_tracker_free() {
let mut tracker = MemoryTracker::new(1000);
tracker.try_alloc(800, "test").unwrap();
tracker.free(300);
assert_eq!(tracker.current(), 500);
assert_eq!(tracker.remaining(), 500);
tracker.try_alloc(400, "test2").unwrap();
assert_eq!(tracker.current(), 900);
}
#[test]
fn test_memory_tracker_reset() {
let mut tracker = MemoryTracker::new(1000);
tracker.try_alloc(800, "test").unwrap();
tracker.reset();
assert_eq!(tracker.current(), 0);
assert_eq!(tracker.remaining(), 1000);
}
#[test]
fn test_memory_tracker_overflow() {
let mut tracker = MemoryTracker::new(usize::MAX);
tracker.try_alloc(usize::MAX - 10, "test1").unwrap();
let result = tracker.try_alloc(100, "test2");
assert!(result.is_err());
}
#[test]
fn test_encode_stats_basic() {
let mut stats = EncodeStats::new();
assert_eq!(stats.count, 0);
assert_eq!(stats.total_bytes, 0);
assert_eq!(stats.peak_bytes, 0);
stats.record_alloc(1000);
assert_eq!(stats.count, 1);
assert_eq!(stats.total_bytes, 1000);
assert_eq!(stats.peak_bytes, 1000);
stats.record_alloc(500);
assert_eq!(stats.count, 2);
assert_eq!(stats.total_bytes, 1500);
assert_eq!(stats.peak_bytes, 1500);
}
#[test]
fn test_encode_stats_peak_tracking() {
let mut stats = EncodeStats::new();
stats.record_alloc(1000);
assert_eq!(stats.peak_bytes, 1000);
stats.record_dealloc(400);
assert_eq!(stats.peak_bytes, 1000);
stats.record_alloc(200);
assert_eq!(stats.peak_bytes, 1000);
stats.record_alloc(500); assert_eq!(stats.peak_bytes, 1300); }
#[test]
fn test_encode_stats_tracked_alloc() {
let mut stats = EncodeStats::new();
let v: Vec<f32> = try_alloc_zeroed_f32_tracked(100, "test", &mut stats).unwrap();
assert_eq!(v.len(), 100);
assert_eq!(stats.count, 1);
assert_eq!(stats.total_bytes, 400);
let v2: Vec<[i16; 64]> = try_alloc_dct_blocks_tracked(10, "blocks", &mut stats).unwrap();
assert_eq!(v2.len(), 10);
assert_eq!(stats.count, 2);
assert_eq!(stats.total_bytes, 400 + 1280); }
#[test]
fn test_encode_stats_summary() {
let mut stats = EncodeStats::new();
stats.record_alloc(1024 * 1024); stats.record_alloc(512 * 1024);
let summary = stats.summary();
assert!(summary.contains("2 allocations"));
assert!(summary.contains("MB")); }
}