1use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
2use std::time::Instant;
3
4pub struct PlatformAllocator {
6 #[allow(dead_code)]
8 original_alloc: AtomicPtr<u8>,
9 #[allow(dead_code)]
10 original_dealloc: AtomicPtr<u8>,
11 config: HookConfig,
13 stats: HookStats,
15}
16
17#[derive(Debug, Clone)]
19pub struct HookConfig {
20 pub track_allocations: bool,
22 pub track_deallocations: bool,
24 pub min_tracked_size: usize,
26 pub max_tracked_size: usize,
28 pub sample_rate: f64,
30}
31
32#[derive(Debug)]
34struct HookStats {
35 total_allocations: AtomicUsize,
37 total_deallocations: AtomicUsize,
39 total_bytes_allocated: AtomicUsize,
41 total_bytes_deallocated: AtomicUsize,
43 total_hook_time: AtomicUsize,
45}
46
47#[derive(Debug, Clone)]
49pub struct HookResult {
50 pub should_proceed: bool,
52 pub should_track: bool,
54 pub metadata: Option<AllocationMetadata>,
56}
57
58#[derive(Debug, Clone)]
60pub struct AllocationInfo {
61 pub ptr: *mut u8,
63 pub size: usize,
65 pub align: usize,
67 pub timestamp: Instant,
69 pub thread_id: ThreadId,
71 pub stack_trace: Option<Vec<usize>>,
73}
74
75#[derive(Debug, Clone)]
77pub struct AllocationMetadata {
78 pub type_name: Option<String>,
80 pub source_location: Option<SourceLocation>,
82 pub tags: Vec<String>,
84}
85
86#[derive(Debug, Clone)]
88pub struct SourceLocation {
89 pub file: String,
91 pub line: u32,
93 pub column: Option<u32>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct ThreadId(pub u64);
100
101pub type AllocationHook = fn(&AllocationInfo) -> HookResult;
103
104pub type DeallocationHook = fn(*mut u8, usize) -> bool;
106
107impl PlatformAllocator {
108 pub fn new() -> Self {
110 Self {
111 original_alloc: AtomicPtr::new(std::ptr::null_mut()),
112 original_dealloc: AtomicPtr::new(std::ptr::null_mut()),
113 config: HookConfig::default(),
114 stats: HookStats::new(),
115 }
116 }
117
118 pub fn install_hooks(&mut self) -> Result<(), HookError> {
120 #[cfg(target_os = "linux")]
121 {
122 self.install_linux_hooks()
123 }
124
125 #[cfg(target_os = "windows")]
126 {
127 self.install_windows_hooks()
128 }
129
130 #[cfg(target_os = "macos")]
131 {
132 self.install_macos_hooks()
133 }
134
135 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
136 {
137 Err(HookError::UnsupportedPlatform)
138 }
139 }
140
141 pub fn remove_hooks(&mut self) -> Result<(), HookError> {
143 #[cfg(target_os = "linux")]
144 {
145 self.remove_linux_hooks()
146 }
147
148 #[cfg(target_os = "windows")]
149 {
150 self.remove_windows_hooks()
151 }
152
153 #[cfg(target_os = "macos")]
154 {
155 self.remove_macos_hooks()
156 }
157
158 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
159 {
160 Err(HookError::UnsupportedPlatform)
161 }
162 }
163
164 pub fn get_statistics(&self) -> AllocationStatistics {
166 AllocationStatistics {
167 total_allocations: self.stats.total_allocations.load(Ordering::Relaxed),
168 total_deallocations: self.stats.total_deallocations.load(Ordering::Relaxed),
169 total_bytes_allocated: self.stats.total_bytes_allocated.load(Ordering::Relaxed),
170 total_bytes_deallocated: self.stats.total_bytes_deallocated.load(Ordering::Relaxed),
171 current_allocations: self
172 .stats
173 .total_allocations
174 .load(Ordering::Relaxed)
175 .saturating_sub(self.stats.total_deallocations.load(Ordering::Relaxed)),
176 current_bytes: self
177 .stats
178 .total_bytes_allocated
179 .load(Ordering::Relaxed)
180 .saturating_sub(self.stats.total_bytes_deallocated.load(Ordering::Relaxed)),
181 average_hook_overhead: self.calculate_average_overhead(),
182 }
183 }
184
185 pub fn update_config(&mut self, config: HookConfig) {
187 self.config = config;
188 }
189
190 #[cfg(target_os = "linux")]
191 fn install_linux_hooks(&mut self) -> Result<(), HookError> {
192 Ok(())
195 }
196
197 #[cfg(target_os = "linux")]
198 fn remove_linux_hooks(&mut self) -> Result<(), HookError> {
199 Ok(())
201 }
202
203 #[cfg(target_os = "windows")]
204 fn install_windows_hooks(&mut self) -> Result<(), HookError> {
205 Ok(())
208 }
209
210 #[cfg(target_os = "windows")]
211 fn remove_windows_hooks(&mut self) -> Result<(), HookError> {
212 Ok(())
214 }
215
216 #[cfg(target_os = "macos")]
217 fn install_macos_hooks(&mut self) -> Result<(), HookError> {
218 Ok(())
221 }
222
223 #[cfg(target_os = "macos")]
224 fn remove_macos_hooks(&mut self) -> Result<(), HookError> {
225 Ok(())
227 }
228
229 fn calculate_average_overhead(&self) -> f64 {
230 let total_time = self.stats.total_hook_time.load(Ordering::Relaxed);
231 let total_calls = self.stats.total_allocations.load(Ordering::Relaxed)
232 + self.stats.total_deallocations.load(Ordering::Relaxed);
233
234 if total_calls > 0 {
235 total_time as f64 / total_calls as f64
236 } else {
237 0.0
238 }
239 }
240
241 pub fn handle_allocation(&self, info: &AllocationInfo) -> HookResult {
243 let start_time = Instant::now();
244
245 self.stats.total_allocations.fetch_add(1, Ordering::Relaxed);
247 self.stats
248 .total_bytes_allocated
249 .fetch_add(info.size, Ordering::Relaxed);
250
251 let should_track = self.should_track_allocation(info);
253
254 let overhead = start_time.elapsed().as_nanos() as usize;
256 self.stats
257 .total_hook_time
258 .fetch_add(overhead, Ordering::Relaxed);
259
260 HookResult {
261 should_proceed: true,
262 should_track,
263 metadata: self.extract_metadata(info),
264 }
265 }
266
267 pub fn handle_deallocation(&self, _ptr: *mut u8, size: usize) -> bool {
269 let start_time = Instant::now();
270
271 self.stats
273 .total_deallocations
274 .fetch_add(1, Ordering::Relaxed);
275 self.stats
276 .total_bytes_deallocated
277 .fetch_add(size, Ordering::Relaxed);
278
279 let overhead = start_time.elapsed().as_nanos() as usize;
281 self.stats
282 .total_hook_time
283 .fetch_add(overhead, Ordering::Relaxed);
284
285 true
286 }
287
288 fn should_track_allocation(&self, info: &AllocationInfo) -> bool {
289 if info.size < self.config.min_tracked_size || info.size > self.config.max_tracked_size {
291 return false;
292 }
293
294 if self.config.sample_rate < 1.0 {
296 let sample_decision = (info.ptr as usize % 1000) as f64 / 1000.0;
297 if sample_decision >= self.config.sample_rate {
298 return false;
299 }
300 }
301
302 true
303 }
304
305 fn extract_metadata(&self, _info: &AllocationInfo) -> Option<AllocationMetadata> {
306 Some(AllocationMetadata {
309 type_name: None, source_location: None, tags: Vec::new(),
312 })
313 }
314}
315
316impl HookStats {
317 fn new() -> Self {
318 Self {
319 total_allocations: AtomicUsize::new(0),
320 total_deallocations: AtomicUsize::new(0),
321 total_bytes_allocated: AtomicUsize::new(0),
322 total_bytes_deallocated: AtomicUsize::new(0),
323 total_hook_time: AtomicUsize::new(0),
324 }
325 }
326}
327
328#[derive(Debug, Clone)]
330pub struct AllocationStatistics {
331 pub total_allocations: usize,
333 pub total_deallocations: usize,
335 pub total_bytes_allocated: usize,
337 pub total_bytes_deallocated: usize,
339 pub current_allocations: usize,
341 pub current_bytes: usize,
343 pub average_hook_overhead: f64,
345}
346
347#[derive(Debug, Clone, PartialEq)]
349pub enum HookError {
350 UnsupportedPlatform,
352 PermissionDenied,
354 AlreadyInstalled,
356 NotInstalled,
358 SystemError(String),
360}
361
362impl Default for HookConfig {
363 fn default() -> Self {
364 Self {
365 track_allocations: true,
366 track_deallocations: true,
367 min_tracked_size: 1,
368 max_tracked_size: usize::MAX,
369 sample_rate: 1.0,
370 }
371 }
372}
373
374impl Default for PlatformAllocator {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380impl std::fmt::Display for HookError {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 match self {
383 HookError::UnsupportedPlatform => {
384 write!(f, "Platform not supported for allocation hooking")
385 }
386 HookError::PermissionDenied => write!(f, "Permission denied for hook installation"),
387 HookError::AlreadyInstalled => write!(f, "Allocation hooks already installed"),
388 HookError::NotInstalled => write!(f, "Allocation hooks not installed"),
389 HookError::SystemError(msg) => write!(f, "System error: {}", msg),
390 }
391 }
392}
393
394impl std::error::Error for HookError {}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_platform_allocator_creation() {
402 let allocator = PlatformAllocator::new();
403 let stats = allocator.get_statistics();
404
405 assert_eq!(stats.total_allocations, 0);
406 assert_eq!(stats.total_deallocations, 0);
407 assert_eq!(stats.current_allocations, 0);
408 }
409
410 #[test]
411 fn test_hook_config() {
412 let config = HookConfig::default();
413 assert!(config.track_allocations);
414 assert!(config.track_deallocations);
415 assert_eq!(config.min_tracked_size, 1);
416 assert_eq!(config.sample_rate, 1.0);
417 }
418
419 #[test]
420 fn test_allocation_info() {
421 let info = AllocationInfo {
422 ptr: std::ptr::null_mut(),
423 size: 1024,
424 align: 8,
425 timestamp: Instant::now(),
426 thread_id: ThreadId(1),
427 stack_trace: None,
428 };
429
430 assert_eq!(info.size, 1024);
431 assert_eq!(info.align, 8);
432 }
433
434 #[test]
435 fn test_hook_statistics() {
436 let allocator = PlatformAllocator::new();
437
438 let info = AllocationInfo {
439 ptr: 0x1000 as *mut u8,
440 size: 100,
441 align: 8,
442 timestamp: Instant::now(),
443 thread_id: ThreadId(1),
444 stack_trace: None,
445 };
446
447 let result = allocator.handle_allocation(&info);
448 assert!(result.should_proceed);
449
450 let stats = allocator.get_statistics();
451 assert_eq!(stats.total_allocations, 1);
452 assert_eq!(stats.total_bytes_allocated, 100);
453 }
454
455 #[test]
456 fn test_sample_rate_filtering() {
457 let mut allocator = PlatformAllocator::new();
458 allocator.config.sample_rate = 0.5;
459
460 let mut tracked_count = 0;
462 for i in 0..1000 {
463 let info = AllocationInfo {
464 ptr: (0x1000 + i) as *mut u8,
465 size: 64,
466 align: 8,
467 timestamp: Instant::now(),
468 thread_id: ThreadId(1),
469 stack_trace: None,
470 };
471
472 if allocator.should_track_allocation(&info) {
473 tracked_count += 1;
474 }
475 }
476
477 assert!(tracked_count > 400 && tracked_count < 600);
479 }
480}