memscope_rs/capture/platform/
allocator.rs1use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
2use std::time::Instant;
3
4pub struct PlatformAllocator {
6 original_alloc: AtomicPtr<u8>,
7 original_dealloc: AtomicPtr<u8>,
8 config: HookConfig,
9 stats: HookStats,
10}
11
12impl PlatformAllocator {
13 pub fn original_alloc(&self) -> *mut u8 {
14 self.original_alloc.load(Ordering::SeqCst)
15 }
16 pub fn original_dealloc(&self) -> *mut u8 {
17 self.original_dealloc.load(Ordering::SeqCst)
18 }
19}
20
21#[derive(Debug, Clone)]
23pub struct HookConfig {
24 pub track_allocations: bool,
26 pub track_deallocations: bool,
28 pub min_tracked_size: usize,
30 pub max_tracked_size: usize,
32 pub sample_rate: f64,
34}
35
36#[derive(Debug)]
38struct HookStats {
39 total_allocations: AtomicUsize,
41 total_deallocations: AtomicUsize,
43 total_bytes_allocated: AtomicUsize,
45 total_bytes_deallocated: AtomicUsize,
47 total_hook_time: AtomicUsize,
49}
50
51#[derive(Debug, Clone)]
53pub struct HookResult {
54 pub should_proceed: bool,
56 pub should_track: bool,
58 pub metadata: Option<AllocationMetadata>,
60}
61
62#[derive(Debug, Clone)]
69pub struct AllocationInfo {
70 pub ptr: *mut u8,
72 pub size: usize,
74 pub align: usize,
76 pub timestamp: Instant,
78 pub thread_id: ThreadId,
80 pub stack_trace: Option<Vec<usize>>,
82}
83
84#[derive(Debug, Clone)]
86pub struct AllocationMetadata {
87 pub type_name: Option<String>,
89 pub source_location: Option<SourceLocation>,
91 pub tags: Vec<String>,
93}
94
95#[derive(Debug, Clone)]
97pub struct SourceLocation {
98 pub file: String,
100 pub line: u32,
102 pub column: Option<u32>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Hash)]
108pub struct ThreadId(pub u64);
109
110pub type AllocationHook = fn(&AllocationInfo) -> HookResult;
112
113pub type DeallocationHook = fn(*mut u8, usize) -> bool;
115
116impl PlatformAllocator {
117 pub fn new() -> Self {
119 Self {
120 original_alloc: AtomicPtr::new(std::ptr::null_mut()),
121 original_dealloc: AtomicPtr::new(std::ptr::null_mut()),
122 config: HookConfig::default(),
123 stats: HookStats::new(),
124 }
125 }
126
127 pub fn install_hooks(&mut self) -> Result<(), HookError> {
129 #[cfg(target_os = "linux")]
130 {
131 self.install_linux_hooks()
132 }
133
134 #[cfg(target_os = "windows")]
135 {
136 self.install_windows_hooks()
137 }
138
139 #[cfg(target_os = "macos")]
140 {
141 self.install_macos_hooks()
142 }
143
144 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
145 {
146 Err(HookError::UnsupportedPlatform)
147 }
148 }
149
150 pub fn remove_hooks(&mut self) -> Result<(), HookError> {
152 #[cfg(target_os = "linux")]
153 {
154 self.remove_linux_hooks()
155 }
156
157 #[cfg(target_os = "windows")]
158 {
159 self.remove_windows_hooks()
160 }
161
162 #[cfg(target_os = "macos")]
163 {
164 self.remove_macos_hooks()
165 }
166
167 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
168 {
169 Err(HookError::UnsupportedPlatform)
170 }
171 }
172
173 pub fn get_statistics(&self) -> AllocationStatistics {
175 AllocationStatistics {
176 total_allocations: self.stats.total_allocations.load(Ordering::Relaxed),
177 total_deallocations: self.stats.total_deallocations.load(Ordering::Relaxed),
178 total_bytes_allocated: self.stats.total_bytes_allocated.load(Ordering::Relaxed),
179 total_bytes_deallocated: self.stats.total_bytes_deallocated.load(Ordering::Relaxed),
180 current_allocations: self
181 .stats
182 .total_allocations
183 .load(Ordering::Relaxed)
184 .saturating_sub(self.stats.total_deallocations.load(Ordering::Relaxed)),
185 current_bytes: self
186 .stats
187 .total_bytes_allocated
188 .load(Ordering::Relaxed)
189 .saturating_sub(self.stats.total_bytes_deallocated.load(Ordering::Relaxed)),
190 average_hook_overhead: self.calculate_average_overhead(),
191 }
192 }
193
194 pub fn update_config(&mut self, config: HookConfig) {
196 self.config = config;
197 }
198
199 #[cfg(target_os = "linux")]
200 fn install_linux_hooks(&mut self) -> Result<(), HookError> {
201 tracing::warn!("Linux hooks are not yet implemented. Using GlobalAlloc trait instead.");
211 Ok(())
212 }
213
214 #[cfg(target_os = "linux")]
215 fn remove_linux_hooks(&mut self) -> Result<(), HookError> {
216 tracing::info!("Linux hooks cleanup: No action needed (using GlobalAlloc trait)");
219 Ok(())
220 }
221
222 #[cfg(target_os = "windows")]
223 fn install_windows_hooks(&mut self) -> Result<(), HookError> {
224 tracing::warn!("Windows hooks are not yet implemented. Using GlobalAlloc trait instead.");
234 Ok(())
235 }
236
237 #[cfg(target_os = "windows")]
238 fn remove_windows_hooks(&mut self) -> Result<(), HookError> {
239 tracing::info!("Windows hooks cleanup: No action needed (using GlobalAlloc trait)");
242 Ok(())
243 }
244
245 #[cfg(target_os = "macos")]
246 fn install_macos_hooks(&mut self) -> Result<(), HookError> {
247 tracing::warn!("macOS hooks are not yet implemented. Using GlobalAlloc trait instead.");
257 Ok(())
258 }
259
260 #[cfg(target_os = "macos")]
261 fn remove_macos_hooks(&mut self) -> Result<(), HookError> {
262 tracing::info!("macOS hooks cleanup: No action needed (using GlobalAlloc trait)");
265 Ok(())
266 }
267
268 fn calculate_average_overhead(&self) -> f64 {
269 let total_time = self.stats.total_hook_time.load(Ordering::Relaxed);
270 let total_calls = self.stats.total_allocations.load(Ordering::Relaxed)
271 + self.stats.total_deallocations.load(Ordering::Relaxed);
272
273 if total_calls > 0 {
274 total_time as f64 / total_calls as f64
275 } else {
276 0.0
277 }
278 }
279
280 pub fn handle_allocation(&self, info: &AllocationInfo) -> HookResult {
282 let start_time = Instant::now();
283
284 self.stats.total_allocations.fetch_add(1, Ordering::Relaxed);
286 self.stats
287 .total_bytes_allocated
288 .fetch_add(info.size, Ordering::Relaxed);
289
290 let should_track = self.should_track_allocation(info);
292
293 let overhead = start_time.elapsed().as_nanos() as usize;
295 self.stats
296 .total_hook_time
297 .fetch_add(overhead, Ordering::Relaxed);
298
299 HookResult {
300 should_proceed: true,
301 should_track,
302 metadata: self.extract_metadata(info),
303 }
304 }
305
306 pub fn handle_deallocation(&self, _ptr: *mut u8, size: usize) -> bool {
308 let start_time = Instant::now();
309
310 self.stats
312 .total_deallocations
313 .fetch_add(1, Ordering::Relaxed);
314 self.stats
315 .total_bytes_deallocated
316 .fetch_add(size, Ordering::Relaxed);
317
318 let overhead = start_time.elapsed().as_nanos() as usize;
320 self.stats
321 .total_hook_time
322 .fetch_add(overhead, Ordering::Relaxed);
323
324 true
325 }
326
327 fn should_track_allocation(&self, info: &AllocationInfo) -> bool {
328 if info.size < self.config.min_tracked_size || info.size > self.config.max_tracked_size {
330 return false;
331 }
332
333 if self.config.sample_rate < 1.0 {
335 let sample_decision = rand::random::<f64>();
336 if sample_decision >= self.config.sample_rate {
337 return false;
338 }
339 }
340
341 true
342 }
343
344 fn extract_metadata(&self, _info: &AllocationInfo) -> Option<AllocationMetadata> {
345 None
349 }
350}
351
352impl HookStats {
353 fn new() -> Self {
354 Self {
355 total_allocations: AtomicUsize::new(0),
356 total_deallocations: AtomicUsize::new(0),
357 total_bytes_allocated: AtomicUsize::new(0),
358 total_bytes_deallocated: AtomicUsize::new(0),
359 total_hook_time: AtomicUsize::new(0),
360 }
361 }
362}
363
364#[derive(Debug, Clone)]
366pub struct AllocationStatistics {
367 pub total_allocations: usize,
369 pub total_deallocations: usize,
371 pub total_bytes_allocated: usize,
373 pub total_bytes_deallocated: usize,
375 pub current_allocations: usize,
377 pub current_bytes: usize,
379 pub average_hook_overhead: f64,
381}
382
383#[derive(Debug, Clone, PartialEq)]
385pub enum HookError {
386 UnsupportedPlatform,
388 PermissionDenied,
390 AlreadyInstalled,
392 NotInstalled,
394 SystemError(String),
396}
397
398impl Default for HookConfig {
399 fn default() -> Self {
400 Self {
401 track_allocations: true,
402 track_deallocations: true,
403 min_tracked_size: 1,
404 max_tracked_size: usize::MAX,
405 sample_rate: 1.0,
406 }
407 }
408}
409
410impl Default for PlatformAllocator {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416impl std::fmt::Display for HookError {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 match self {
419 HookError::UnsupportedPlatform => {
420 write!(f, "Platform not supported for allocation hooking")
421 }
422 HookError::PermissionDenied => write!(f, "Permission denied for hook installation"),
423 HookError::AlreadyInstalled => write!(f, "Allocation hooks already installed"),
424 HookError::NotInstalled => write!(f, "Allocation hooks not installed"),
425 HookError::SystemError(msg) => write!(f, "System error: {}", msg),
426 }
427 }
428}
429
430impl std::error::Error for HookError {}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_platform_allocator_creation() {
438 let allocator = PlatformAllocator::new();
439 let stats = allocator.get_statistics();
440
441 assert_eq!(stats.total_allocations, 0);
442 assert_eq!(stats.total_deallocations, 0);
443 assert_eq!(stats.current_allocations, 0);
444 }
445
446 #[test]
447 fn test_hook_config() {
448 let config = HookConfig::default();
449 assert!(config.track_allocations);
450 assert!(config.track_deallocations);
451 assert_eq!(config.min_tracked_size, 1);
452 assert_eq!(config.sample_rate, 1.0);
453 }
454
455 #[test]
456 fn test_allocation_info() {
457 let info = AllocationInfo {
458 ptr: std::ptr::null_mut(),
459 size: 1024,
460 align: 8,
461 timestamp: Instant::now(),
462 thread_id: ThreadId(1),
463 stack_trace: None,
464 };
465
466 assert_eq!(info.size, 1024);
467 assert_eq!(info.align, 8);
468 }
469
470 #[test]
471 fn test_hook_statistics() {
472 let allocator = PlatformAllocator::new();
473
474 let info = AllocationInfo {
475 ptr: 0x1000 as *mut u8,
476 size: 100,
477 align: 8,
478 timestamp: Instant::now(),
479 thread_id: ThreadId(1),
480 stack_trace: None,
481 };
482
483 let result = allocator.handle_allocation(&info);
484 assert!(result.should_proceed);
485
486 let stats = allocator.get_statistics();
487 assert_eq!(stats.total_allocations, 1);
488 assert_eq!(stats.total_bytes_allocated, 100);
489 }
490
491 #[test]
492 fn test_sample_rate_filtering() {
493 let mut allocator = PlatformAllocator::new();
494 allocator.config.sample_rate = 0.5;
495
496 let mut tracked_count = 0;
498 for i in 0..1000 {
499 let info = AllocationInfo {
500 ptr: (0x1000 + i) as *mut u8,
501 size: 64,
502 align: 8,
503 timestamp: Instant::now(),
504 thread_id: ThreadId(1),
505 stack_trace: None,
506 };
507
508 if allocator.should_track_allocation(&info) {
509 tracked_count += 1;
510 }
511 }
512
513 assert!(tracked_count > 400 && tracked_count < 600);
515 }
516}