use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::time::Instant;
pub struct PlatformAllocator {
original_alloc: AtomicPtr<u8>,
original_dealloc: AtomicPtr<u8>,
config: HookConfig,
stats: HookStats,
}
impl PlatformAllocator {
pub fn original_alloc(&self) -> *mut u8 {
self.original_alloc.load(Ordering::SeqCst)
}
pub fn original_dealloc(&self) -> *mut u8 {
self.original_dealloc.load(Ordering::SeqCst)
}
}
#[derive(Debug, Clone)]
pub struct HookConfig {
pub track_allocations: bool,
pub track_deallocations: bool,
pub min_tracked_size: usize,
pub max_tracked_size: usize,
pub sample_rate: f64,
}
#[derive(Debug)]
struct HookStats {
total_allocations: AtomicUsize,
total_deallocations: AtomicUsize,
total_bytes_allocated: AtomicUsize,
total_bytes_deallocated: AtomicUsize,
total_hook_time: AtomicUsize,
}
#[derive(Debug, Clone)]
pub struct HookResult {
pub should_proceed: bool,
pub should_track: bool,
pub metadata: Option<AllocationMetadata>,
}
#[derive(Debug, Clone)]
pub struct AllocationInfo {
pub ptr: *mut u8,
pub size: usize,
pub align: usize,
pub timestamp: Instant,
pub thread_id: ThreadId,
pub stack_trace: Option<Vec<usize>>,
}
#[derive(Debug, Clone)]
pub struct AllocationMetadata {
pub type_name: Option<String>,
pub source_location: Option<SourceLocation>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ThreadId(pub u64);
pub type AllocationHook = fn(&AllocationInfo) -> HookResult;
pub type DeallocationHook = fn(*mut u8, usize) -> bool;
impl PlatformAllocator {
pub fn new() -> Self {
Self {
original_alloc: AtomicPtr::new(std::ptr::null_mut()),
original_dealloc: AtomicPtr::new(std::ptr::null_mut()),
config: HookConfig::default(),
stats: HookStats::new(),
}
}
pub fn install_hooks(&mut self) -> Result<(), HookError> {
#[cfg(target_os = "linux")]
{
self.install_linux_hooks()
}
#[cfg(target_os = "windows")]
{
self.install_windows_hooks()
}
#[cfg(target_os = "macos")]
{
self.install_macos_hooks()
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
{
Err(HookError::UnsupportedPlatform)
}
}
pub fn remove_hooks(&mut self) -> Result<(), HookError> {
#[cfg(target_os = "linux")]
{
self.remove_linux_hooks()
}
#[cfg(target_os = "windows")]
{
self.remove_windows_hooks()
}
#[cfg(target_os = "macos")]
{
self.remove_macos_hooks()
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
{
Err(HookError::UnsupportedPlatform)
}
}
pub fn get_statistics(&self) -> AllocationStatistics {
AllocationStatistics {
total_allocations: self.stats.total_allocations.load(Ordering::Relaxed),
total_deallocations: self.stats.total_deallocations.load(Ordering::Relaxed),
total_bytes_allocated: self.stats.total_bytes_allocated.load(Ordering::Relaxed),
total_bytes_deallocated: self.stats.total_bytes_deallocated.load(Ordering::Relaxed),
current_allocations: self
.stats
.total_allocations
.load(Ordering::Relaxed)
.saturating_sub(self.stats.total_deallocations.load(Ordering::Relaxed)),
current_bytes: self
.stats
.total_bytes_allocated
.load(Ordering::Relaxed)
.saturating_sub(self.stats.total_bytes_deallocated.load(Ordering::Relaxed)),
average_hook_overhead: self.calculate_average_overhead(),
}
}
pub fn update_config(&mut self, config: HookConfig) {
self.config = config;
}
#[cfg(target_os = "linux")]
fn install_linux_hooks(&mut self) -> Result<(), HookError> {
tracing::warn!("Linux hooks are not yet implemented. Using GlobalAlloc trait instead.");
Ok(())
}
#[cfg(target_os = "linux")]
fn remove_linux_hooks(&mut self) -> Result<(), HookError> {
tracing::info!("Linux hooks cleanup: No action needed (using GlobalAlloc trait)");
Ok(())
}
#[cfg(target_os = "windows")]
fn install_windows_hooks(&mut self) -> Result<(), HookError> {
tracing::warn!("Windows hooks are not yet implemented. Using GlobalAlloc trait instead.");
Ok(())
}
#[cfg(target_os = "windows")]
fn remove_windows_hooks(&mut self) -> Result<(), HookError> {
tracing::info!("Windows hooks cleanup: No action needed (using GlobalAlloc trait)");
Ok(())
}
#[cfg(target_os = "macos")]
fn install_macos_hooks(&mut self) -> Result<(), HookError> {
tracing::warn!("macOS hooks are not yet implemented. Using GlobalAlloc trait instead.");
Ok(())
}
#[cfg(target_os = "macos")]
fn remove_macos_hooks(&mut self) -> Result<(), HookError> {
tracing::info!("macOS hooks cleanup: No action needed (using GlobalAlloc trait)");
Ok(())
}
fn calculate_average_overhead(&self) -> f64 {
let total_time = self.stats.total_hook_time.load(Ordering::Relaxed);
let total_calls = self.stats.total_allocations.load(Ordering::Relaxed)
+ self.stats.total_deallocations.load(Ordering::Relaxed);
if total_calls > 0 {
total_time as f64 / total_calls as f64
} else {
0.0
}
}
pub fn handle_allocation(&self, info: &AllocationInfo) -> HookResult {
let start_time = Instant::now();
self.stats.total_allocations.fetch_add(1, Ordering::Relaxed);
self.stats
.total_bytes_allocated
.fetch_add(info.size, Ordering::Relaxed);
let should_track = self.should_track_allocation(info);
let overhead = start_time.elapsed().as_nanos() as usize;
self.stats
.total_hook_time
.fetch_add(overhead, Ordering::Relaxed);
HookResult {
should_proceed: true,
should_track,
metadata: self.extract_metadata(info),
}
}
pub fn handle_deallocation(&self, _ptr: *mut u8, size: usize) -> bool {
let start_time = Instant::now();
self.stats
.total_deallocations
.fetch_add(1, Ordering::Relaxed);
self.stats
.total_bytes_deallocated
.fetch_add(size, Ordering::Relaxed);
let overhead = start_time.elapsed().as_nanos() as usize;
self.stats
.total_hook_time
.fetch_add(overhead, Ordering::Relaxed);
true
}
fn should_track_allocation(&self, info: &AllocationInfo) -> bool {
if info.size < self.config.min_tracked_size || info.size > self.config.max_tracked_size {
return false;
}
if self.config.sample_rate < 1.0 {
let sample_decision = rand::random::<f64>();
if sample_decision >= self.config.sample_rate {
return false;
}
}
true
}
fn extract_metadata(&self, _info: &AllocationInfo) -> Option<AllocationMetadata> {
None
}
}
impl HookStats {
fn new() -> Self {
Self {
total_allocations: AtomicUsize::new(0),
total_deallocations: AtomicUsize::new(0),
total_bytes_allocated: AtomicUsize::new(0),
total_bytes_deallocated: AtomicUsize::new(0),
total_hook_time: AtomicUsize::new(0),
}
}
}
#[derive(Debug, Clone)]
pub struct AllocationStatistics {
pub total_allocations: usize,
pub total_deallocations: usize,
pub total_bytes_allocated: usize,
pub total_bytes_deallocated: usize,
pub current_allocations: usize,
pub current_bytes: usize,
pub average_hook_overhead: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HookError {
UnsupportedPlatform,
PermissionDenied,
AlreadyInstalled,
NotInstalled,
SystemError(String),
}
impl Default for HookConfig {
fn default() -> Self {
Self {
track_allocations: true,
track_deallocations: true,
min_tracked_size: 1,
max_tracked_size: usize::MAX,
sample_rate: 1.0,
}
}
}
impl Default for PlatformAllocator {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for HookError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HookError::UnsupportedPlatform => {
write!(f, "Platform not supported for allocation hooking")
}
HookError::PermissionDenied => write!(f, "Permission denied for hook installation"),
HookError::AlreadyInstalled => write!(f, "Allocation hooks already installed"),
HookError::NotInstalled => write!(f, "Allocation hooks not installed"),
HookError::SystemError(msg) => write!(f, "System error: {}", msg),
}
}
}
impl std::error::Error for HookError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_allocator_creation() {
let allocator = PlatformAllocator::new();
let stats = allocator.get_statistics();
assert_eq!(stats.total_allocations, 0);
assert_eq!(stats.total_deallocations, 0);
assert_eq!(stats.current_allocations, 0);
}
#[test]
fn test_hook_config() {
let config = HookConfig::default();
assert!(config.track_allocations);
assert!(config.track_deallocations);
assert_eq!(config.min_tracked_size, 1);
assert_eq!(config.sample_rate, 1.0);
}
#[test]
fn test_allocation_info() {
let info = AllocationInfo {
ptr: std::ptr::null_mut(),
size: 1024,
align: 8,
timestamp: Instant::now(),
thread_id: ThreadId(1),
stack_trace: None,
};
assert_eq!(info.size, 1024);
assert_eq!(info.align, 8);
}
#[test]
fn test_hook_statistics() {
let allocator = PlatformAllocator::new();
let info = AllocationInfo {
ptr: 0x1000 as *mut u8,
size: 100,
align: 8,
timestamp: Instant::now(),
thread_id: ThreadId(1),
stack_trace: None,
};
let result = allocator.handle_allocation(&info);
assert!(result.should_proceed);
let stats = allocator.get_statistics();
assert_eq!(stats.total_allocations, 1);
assert_eq!(stats.total_bytes_allocated, 100);
}
#[test]
fn test_sample_rate_filtering() {
let mut allocator = PlatformAllocator::new();
allocator.config.sample_rate = 0.5;
let mut tracked_count = 0;
for i in 0..1000 {
let info = AllocationInfo {
ptr: (0x1000 + i) as *mut u8,
size: 64,
align: 8,
timestamp: Instant::now(),
thread_id: ThreadId(1),
stack_trace: None,
};
if allocator.should_track_allocation(&info) {
tracked_count += 1;
}
}
assert!(tracked_count > 400 && tracked_count < 600);
}
}