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)]
64pub struct AllocationInfo {
65 pub ptr: *mut u8,
67 pub size: usize,
69 pub align: usize,
71 pub timestamp: Instant,
73 pub thread_id: ThreadId,
75 pub stack_trace: Option<Vec<usize>>,
77}
78
79#[derive(Debug, Clone)]
81pub struct AllocationMetadata {
82 pub type_name: Option<String>,
84 pub source_location: Option<SourceLocation>,
86 pub tags: Vec<String>,
88}
89
90#[derive(Debug, Clone)]
92pub struct SourceLocation {
93 pub file: String,
95 pub line: u32,
97 pub column: Option<u32>,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Hash)]
103pub struct ThreadId(pub u64);
104
105pub type AllocationHook = fn(&AllocationInfo) -> HookResult;
107
108pub type DeallocationHook = fn(*mut u8, usize) -> bool;
110
111impl PlatformAllocator {
112 pub fn new() -> Self {
114 Self {
115 original_alloc: AtomicPtr::new(std::ptr::null_mut()),
116 original_dealloc: AtomicPtr::new(std::ptr::null_mut()),
117 config: HookConfig::default(),
118 stats: HookStats::new(),
119 }
120 }
121
122 pub fn install_hooks(&mut self) -> Result<(), HookError> {
124 #[cfg(target_os = "linux")]
125 {
126 self.install_linux_hooks()
127 }
128
129 #[cfg(target_os = "windows")]
130 {
131 self.install_windows_hooks()
132 }
133
134 #[cfg(target_os = "macos")]
135 {
136 self.install_macos_hooks()
137 }
138
139 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
140 {
141 Err(HookError::UnsupportedPlatform)
142 }
143 }
144
145 pub fn remove_hooks(&mut self) -> Result<(), HookError> {
147 #[cfg(target_os = "linux")]
148 {
149 self.remove_linux_hooks()
150 }
151
152 #[cfg(target_os = "windows")]
153 {
154 self.remove_windows_hooks()
155 }
156
157 #[cfg(target_os = "macos")]
158 {
159 self.remove_macos_hooks()
160 }
161
162 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
163 {
164 Err(HookError::UnsupportedPlatform)
165 }
166 }
167
168 pub fn get_statistics(&self) -> AllocationStatistics {
170 AllocationStatistics {
171 total_allocations: self.stats.total_allocations.load(Ordering::Relaxed),
172 total_deallocations: self.stats.total_deallocations.load(Ordering::Relaxed),
173 total_bytes_allocated: self.stats.total_bytes_allocated.load(Ordering::Relaxed),
174 total_bytes_deallocated: self.stats.total_bytes_deallocated.load(Ordering::Relaxed),
175 current_allocations: self
176 .stats
177 .total_allocations
178 .load(Ordering::Relaxed)
179 .saturating_sub(self.stats.total_deallocations.load(Ordering::Relaxed)),
180 current_bytes: self
181 .stats
182 .total_bytes_allocated
183 .load(Ordering::Relaxed)
184 .saturating_sub(self.stats.total_bytes_deallocated.load(Ordering::Relaxed)),
185 average_hook_overhead: self.calculate_average_overhead(),
186 }
187 }
188
189 pub fn update_config(&mut self, config: HookConfig) {
191 self.config = config;
192 }
193
194 #[cfg(target_os = "linux")]
195 fn install_linux_hooks(&mut self) -> Result<(), HookError> {
196 tracing::warn!("Linux hooks are not yet implemented. Using GlobalAlloc trait instead.");
206 Ok(())
207 }
208
209 #[cfg(target_os = "linux")]
210 fn remove_linux_hooks(&mut self) -> Result<(), HookError> {
211 tracing::info!("Linux hooks cleanup: No action needed (using GlobalAlloc trait)");
214 Ok(())
215 }
216
217 #[cfg(target_os = "windows")]
218 fn install_windows_hooks(&mut self) -> Result<(), HookError> {
219 tracing::warn!("Windows hooks are not yet implemented. Using GlobalAlloc trait instead.");
229 Ok(())
230 }
231
232 #[cfg(target_os = "windows")]
233 fn remove_windows_hooks(&mut self) -> Result<(), HookError> {
234 tracing::info!("Windows hooks cleanup: No action needed (using GlobalAlloc trait)");
237 Ok(())
238 }
239
240 #[cfg(target_os = "macos")]
241 fn install_macos_hooks(&mut self) -> Result<(), HookError> {
242 tracing::warn!("macOS hooks are not yet implemented. Using GlobalAlloc trait instead.");
252 Ok(())
253 }
254
255 #[cfg(target_os = "macos")]
256 fn remove_macos_hooks(&mut self) -> Result<(), HookError> {
257 tracing::info!("macOS hooks cleanup: No action needed (using GlobalAlloc trait)");
260 Ok(())
261 }
262
263 fn calculate_average_overhead(&self) -> f64 {
264 let total_time = self.stats.total_hook_time.load(Ordering::Relaxed);
265 let total_calls = self.stats.total_allocations.load(Ordering::Relaxed)
266 + self.stats.total_deallocations.load(Ordering::Relaxed);
267
268 if total_calls > 0 {
269 total_time as f64 / total_calls as f64
270 } else {
271 0.0
272 }
273 }
274
275 pub fn handle_allocation(&self, info: &AllocationInfo) -> HookResult {
277 let start_time = Instant::now();
278
279 self.stats.total_allocations.fetch_add(1, Ordering::Relaxed);
281 self.stats
282 .total_bytes_allocated
283 .fetch_add(info.size, Ordering::Relaxed);
284
285 let should_track = self.should_track_allocation(info);
287
288 let overhead = start_time.elapsed().as_nanos() as usize;
290 self.stats
291 .total_hook_time
292 .fetch_add(overhead, Ordering::Relaxed);
293
294 HookResult {
295 should_proceed: true,
296 should_track,
297 metadata: self.extract_metadata(info),
298 }
299 }
300
301 pub fn handle_deallocation(&self, _ptr: *mut u8, size: usize) -> bool {
303 let start_time = Instant::now();
304
305 self.stats
307 .total_deallocations
308 .fetch_add(1, Ordering::Relaxed);
309 self.stats
310 .total_bytes_deallocated
311 .fetch_add(size, Ordering::Relaxed);
312
313 let overhead = start_time.elapsed().as_nanos() as usize;
315 self.stats
316 .total_hook_time
317 .fetch_add(overhead, Ordering::Relaxed);
318
319 true
320 }
321
322 fn should_track_allocation(&self, info: &AllocationInfo) -> bool {
323 if info.size < self.config.min_tracked_size || info.size > self.config.max_tracked_size {
325 return false;
326 }
327
328 if self.config.sample_rate < 1.0 {
330 let sample_decision = rand::random::<f64>();
331 if sample_decision >= self.config.sample_rate {
332 return false;
333 }
334 }
335
336 true
337 }
338
339 fn extract_metadata(&self, _info: &AllocationInfo) -> Option<AllocationMetadata> {
340 None
344 }
345}
346
347impl HookStats {
348 fn new() -> Self {
349 Self {
350 total_allocations: AtomicUsize::new(0),
351 total_deallocations: AtomicUsize::new(0),
352 total_bytes_allocated: AtomicUsize::new(0),
353 total_bytes_deallocated: AtomicUsize::new(0),
354 total_hook_time: AtomicUsize::new(0),
355 }
356 }
357}
358
359#[derive(Debug, Clone)]
361pub struct AllocationStatistics {
362 pub total_allocations: usize,
364 pub total_deallocations: usize,
366 pub total_bytes_allocated: usize,
368 pub total_bytes_deallocated: usize,
370 pub current_allocations: usize,
372 pub current_bytes: usize,
374 pub average_hook_overhead: f64,
376}
377
378#[derive(Debug, Clone, PartialEq)]
380pub enum HookError {
381 UnsupportedPlatform,
383 PermissionDenied,
385 AlreadyInstalled,
387 NotInstalled,
389 SystemError(String),
391}
392
393impl Default for HookConfig {
394 fn default() -> Self {
395 Self {
396 track_allocations: true,
397 track_deallocations: true,
398 min_tracked_size: 1,
399 max_tracked_size: usize::MAX,
400 sample_rate: 1.0,
401 }
402 }
403}
404
405impl Default for PlatformAllocator {
406 fn default() -> Self {
407 Self::new()
408 }
409}
410
411impl std::fmt::Display for HookError {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 match self {
414 HookError::UnsupportedPlatform => {
415 write!(f, "Platform not supported for allocation hooking")
416 }
417 HookError::PermissionDenied => write!(f, "Permission denied for hook installation"),
418 HookError::AlreadyInstalled => write!(f, "Allocation hooks already installed"),
419 HookError::NotInstalled => write!(f, "Allocation hooks not installed"),
420 HookError::SystemError(msg) => write!(f, "System error: {}", msg),
421 }
422 }
423}
424
425impl std::error::Error for HookError {}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_platform_allocator_creation() {
433 let allocator = PlatformAllocator::new();
434 let stats = allocator.get_statistics();
435
436 assert_eq!(stats.total_allocations, 0);
437 assert_eq!(stats.total_deallocations, 0);
438 assert_eq!(stats.current_allocations, 0);
439 }
440
441 #[test]
442 fn test_hook_config() {
443 let config = HookConfig::default();
444 assert!(config.track_allocations);
445 assert!(config.track_deallocations);
446 assert_eq!(config.min_tracked_size, 1);
447 assert_eq!(config.sample_rate, 1.0);
448 }
449
450 #[test]
451 fn test_allocation_info() {
452 let info = AllocationInfo {
453 ptr: std::ptr::null_mut(),
454 size: 1024,
455 align: 8,
456 timestamp: Instant::now(),
457 thread_id: ThreadId(1),
458 stack_trace: None,
459 };
460
461 assert_eq!(info.size, 1024);
462 assert_eq!(info.align, 8);
463 }
464
465 #[test]
466 fn test_hook_statistics() {
467 let allocator = PlatformAllocator::new();
468
469 let info = AllocationInfo {
470 ptr: 0x1000 as *mut u8,
471 size: 100,
472 align: 8,
473 timestamp: Instant::now(),
474 thread_id: ThreadId(1),
475 stack_trace: None,
476 };
477
478 let result = allocator.handle_allocation(&info);
479 assert!(result.should_proceed);
480
481 let stats = allocator.get_statistics();
482 assert_eq!(stats.total_allocations, 1);
483 assert_eq!(stats.total_bytes_allocated, 100);
484 }
485
486 #[test]
487 fn test_sample_rate_filtering() {
488 let mut allocator = PlatformAllocator::new();
489 allocator.config.sample_rate = 0.5;
490
491 let mut tracked_count = 0;
493 for i in 0..1000 {
494 let info = AllocationInfo {
495 ptr: (0x1000 + i) as *mut u8,
496 size: 64,
497 align: 8,
498 timestamp: Instant::now(),
499 thread_id: ThreadId(1),
500 stack_trace: None,
501 };
502
503 if allocator.should_track_allocation(&info) {
504 tracked_count += 1;
505 }
506 }
507
508 assert!(tracked_count > 400 && tracked_count < 600);
510 }
511}