memscope_rs/platform/
allocator.rs

1use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
2use std::time::Instant;
3
4/// Platform-specific allocator hooking system
5pub struct PlatformAllocator {
6    /// Original allocator functions
7    #[allow(dead_code)]
8    original_alloc: AtomicPtr<u8>,
9    #[allow(dead_code)]
10    original_dealloc: AtomicPtr<u8>,
11    /// Hook configuration
12    config: HookConfig,
13    /// Hook statistics
14    stats: HookStats,
15}
16
17/// Configuration for allocation hooks
18#[derive(Debug, Clone)]
19pub struct HookConfig {
20    /// Whether to track allocations
21    pub track_allocations: bool,
22    /// Whether to track deallocations
23    pub track_deallocations: bool,
24    /// Minimum allocation size to track
25    pub min_tracked_size: usize,
26    /// Maximum allocation size to track
27    pub max_tracked_size: usize,
28    /// Sample rate for tracking (0.0 to 1.0)
29    pub sample_rate: f64,
30}
31
32/// Statistics for allocation hooks
33#[derive(Debug)]
34struct HookStats {
35    /// Total allocations intercepted
36    total_allocations: AtomicUsize,
37    /// Total deallocations intercepted
38    total_deallocations: AtomicUsize,
39    /// Total bytes allocated
40    total_bytes_allocated: AtomicUsize,
41    /// Total bytes deallocated
42    total_bytes_deallocated: AtomicUsize,
43    /// Hook overhead time
44    total_hook_time: AtomicUsize,
45}
46
47/// Result of allocation hook
48#[derive(Debug, Clone)]
49pub struct HookResult {
50    /// Whether allocation should proceed
51    pub should_proceed: bool,
52    /// Whether to track this allocation
53    pub should_track: bool,
54    /// Additional metadata
55    pub metadata: Option<AllocationMetadata>,
56}
57
58/// Information about an allocation
59#[derive(Debug, Clone)]
60pub struct AllocationInfo {
61    /// Pointer to allocated memory
62    pub ptr: *mut u8,
63    /// Size of allocation
64    pub size: usize,
65    /// Alignment requirement
66    pub align: usize,
67    /// Timestamp of allocation
68    pub timestamp: Instant,
69    /// Thread ID that made allocation
70    pub thread_id: ThreadId,
71    /// Stack trace if captured
72    pub stack_trace: Option<Vec<usize>>,
73}
74
75/// Metadata for allocation tracking
76#[derive(Debug, Clone)]
77pub struct AllocationMetadata {
78    /// Type name if known
79    pub type_name: Option<String>,
80    /// Source location if available
81    pub source_location: Option<SourceLocation>,
82    /// Custom tags
83    pub tags: Vec<String>,
84}
85
86/// Source code location information
87#[derive(Debug, Clone)]
88pub struct SourceLocation {
89    /// File name
90    pub file: String,
91    /// Line number
92    pub line: u32,
93    /// Column number
94    pub column: Option<u32>,
95}
96
97/// Thread identifier
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct ThreadId(pub u64);
100
101/// Allocation hook function type
102pub type AllocationHook = fn(&AllocationInfo) -> HookResult;
103
104/// Deallocation hook function type
105pub type DeallocationHook = fn(*mut u8, usize) -> bool;
106
107impl PlatformAllocator {
108    /// Create new platform allocator
109    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    /// Install allocation hooks
119    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    /// Remove allocation hooks
142    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    /// Get hook statistics
165    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    /// Update hook configuration
186    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        // Linux-specific implementation using LD_PRELOAD or similar
193        // This is a simplified placeholder
194        Ok(())
195    }
196
197    #[cfg(target_os = "linux")]
198    fn remove_linux_hooks(&mut self) -> Result<(), HookError> {
199        // Linux-specific cleanup
200        Ok(())
201    }
202
203    #[cfg(target_os = "windows")]
204    fn install_windows_hooks(&mut self) -> Result<(), HookError> {
205        // Windows-specific implementation using detours or similar
206        // This is a simplified placeholder
207        Ok(())
208    }
209
210    #[cfg(target_os = "windows")]
211    fn remove_windows_hooks(&mut self) -> Result<(), HookError> {
212        // Windows-specific cleanup
213        Ok(())
214    }
215
216    #[cfg(target_os = "macos")]
217    fn install_macos_hooks(&mut self) -> Result<(), HookError> {
218        // macOS-specific implementation using interpose or similar
219        // This is a simplified placeholder
220        Ok(())
221    }
222
223    #[cfg(target_os = "macos")]
224    fn remove_macos_hooks(&mut self) -> Result<(), HookError> {
225        // macOS-specific cleanup
226        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    /// Handle allocation event
242    pub fn handle_allocation(&self, info: &AllocationInfo) -> HookResult {
243        let start_time = Instant::now();
244
245        // Update statistics
246        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        // Check if we should track this allocation
252        let should_track = self.should_track_allocation(info);
253
254        // Record hook overhead
255        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    /// Handle deallocation event
268    pub fn handle_deallocation(&self, _ptr: *mut u8, size: usize) -> bool {
269        let start_time = Instant::now();
270
271        // Update statistics
272        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        // Record hook overhead
280        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        // Check size limits
290        if info.size < self.config.min_tracked_size || info.size > self.config.max_tracked_size {
291            return false;
292        }
293
294        // Apply sampling
295        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        // Extract metadata from allocation context
307        // This would use debug info, compiler hints, etc.
308        Some(AllocationMetadata {
309            type_name: None,       // Would be extracted from debug info
310            source_location: None, // Would be extracted from debug info
311            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/// Statistics about allocation hooks
329#[derive(Debug, Clone)]
330pub struct AllocationStatistics {
331    /// Total number of allocations
332    pub total_allocations: usize,
333    /// Total number of deallocations
334    pub total_deallocations: usize,
335    /// Total bytes allocated
336    pub total_bytes_allocated: usize,
337    /// Total bytes deallocated
338    pub total_bytes_deallocated: usize,
339    /// Current active allocations
340    pub current_allocations: usize,
341    /// Current active bytes
342    pub current_bytes: usize,
343    /// Average hook overhead in nanoseconds
344    pub average_hook_overhead: f64,
345}
346
347/// Errors that can occur during hook installation
348#[derive(Debug, Clone, PartialEq)]
349pub enum HookError {
350    /// Platform not supported
351    UnsupportedPlatform,
352    /// Permission denied
353    PermissionDenied,
354    /// Hook already installed
355    AlreadyInstalled,
356    /// Hook not installed
357    NotInstalled,
358    /// System error
359    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        // Test multiple allocations to check sampling
461        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        // Should track roughly 50% with some variance
478        assert!(tracked_count > 400 && tracked_count < 600);
479    }
480}