memscope-rs 0.2.3

A memory tracking library for Rust applications.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
//! Query Engine - Unified query interface
//!
//! This module provides the QueryEngine which is responsible for
//! querying snapshot data.

use crate::query::types::{
    AllocationQueryResult, QueryResult, SummaryQueryResult, ThreadQueryResult,
};
use crate::snapshot::{MemorySnapshot, SharedSnapshotEngine};
use std::sync::Arc;

/// Query Engine - Unified query interface for memory data
///
/// The QueryEngine provides a unified interface for querying memory
/// snapshot data, supporting various query types and filtering options.
///
/// Key properties:
/// - Unified: Single interface for all query types
/// - Efficient: Optimized for fast query execution
/// - Flexible: Supports filtering and sorting
pub struct QueryEngine {
    /// Reference to the snapshot engine
    snapshot_engine: SharedSnapshotEngine,
}

/// Shared reference to QueryEngine
pub type SharedQueryEngine = Arc<QueryEngine>;

impl QueryEngine {
    /// Create a new QueryEngine
    pub fn new(snapshot_engine: SharedSnapshotEngine) -> Self {
        Self { snapshot_engine }
    }

    /// Get the current snapshot
    fn get_snapshot(&self) -> MemorySnapshot {
        self.snapshot_engine.build_snapshot()
    }

    /// Query for top allocations by size
    ///
    /// # Arguments
    /// * `limit` - Maximum number of allocations to return
    pub fn top_allocations(&self, limit: usize) -> QueryResult {
        let snapshot = self.get_snapshot();
        let mut allocations: Vec<_> = snapshot.active_allocations.values().cloned().collect();

        // Sort by size descending
        allocations.sort_by_key(|b| std::cmp::Reverse(b.size));

        // Limit results
        allocations.truncate(limit);

        let total_bytes = allocations.iter().map(|a| a.size).sum();

        QueryResult::Allocations(AllocationQueryResult {
            count: allocations.len(),
            total_bytes,
            allocations,
        })
    }

    /// Query for allocations from a specific thread
    ///
    /// # Arguments
    /// * `thread_id` - The thread ID to filter by
    pub fn allocations_by_thread(&self, thread_id: u64) -> QueryResult {
        let snapshot = self.get_snapshot();
        let allocations: Vec<_> = snapshot
            .active_allocations
            .values()
            .filter(|a| a.thread_id == thread_id)
            .cloned()
            .collect();

        let total_bytes = allocations.iter().map(|a| a.size).sum();

        QueryResult::Allocations(AllocationQueryResult {
            count: allocations.len(),
            total_bytes,
            allocations,
        })
    }

    /// Query for thread statistics
    pub fn thread_stats(&self) -> QueryResult {
        let snapshot = self.get_snapshot();
        let threads: Vec<_> = snapshot.thread_stats.values().cloned().collect();
        let total_bytes = threads.iter().map(|t| t.current_memory).sum();

        QueryResult::Threads(ThreadQueryResult {
            count: threads.len(),
            total_bytes,
            threads,
        })
    }

    /// Query for a summary of memory usage
    pub fn summary(&self) -> QueryResult {
        let snapshot = self.get_snapshot();

        QueryResult::Summary(SummaryQueryResult {
            total_allocations: snapshot.stats.total_allocations,
            total_deallocations: snapshot.stats.total_deallocations,
            active_allocations: snapshot.stats.active_allocations,
            total_allocated: snapshot.stats.total_allocated,
            total_deallocated: snapshot.stats.total_deallocated,
            current_memory: snapshot.stats.current_memory,
            peak_memory: snapshot.stats.peak_memory,
            thread_count: snapshot.thread_stats.len(),
        })
    }

    /// Query for allocations with a specific variable name
    ///
    /// # Arguments
    /// * `var_name` - The variable name to filter by
    pub fn allocations_by_variable(&self, var_name: &str) -> QueryResult {
        let snapshot = self.get_snapshot();
        let allocations: Vec<_> = snapshot
            .active_allocations
            .values()
            .filter(|a| a.var_name.as_ref().map(|n| n == var_name).unwrap_or(false))
            .cloned()
            .collect();

        let total_bytes = allocations.iter().map(|a| a.size).sum();

        QueryResult::Allocations(AllocationQueryResult {
            count: allocations.len(),
            total_bytes,
            allocations,
        })
    }

    /// Query for allocations larger than a certain size
    ///
    /// # Arguments
    /// * `min_size` - Minimum allocation size in bytes
    pub fn allocations_larger_than(&self, min_size: usize) -> QueryResult {
        let snapshot = self.get_snapshot();
        let allocations: Vec<_> = snapshot
            .active_allocations
            .values()
            .filter(|a| a.size > min_size)
            .cloned()
            .collect();

        let total_bytes = allocations.iter().map(|a| a.size).sum();

        QueryResult::Allocations(AllocationQueryResult {
            count: allocations.len(),
            total_bytes,
            allocations,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::event_store::EventStore;
    use crate::snapshot::SnapshotEngine;

    /// Objective: Verify that QueryEngine creates correctly with empty data.
    /// Invariants: Summary should show zero allocations for empty event store.
    #[test]
    fn test_query_engine_creation() {
        let event_store = Arc::new(EventStore::new());
        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.summary();
        match result {
            QueryResult::Summary(summary) => {
                assert_eq!(
                    summary.total_allocations, 0,
                    "Empty event store should have zero allocations"
                );
            }
            _ => panic!("Expected summary result"),
        }
    }

    /// Objective: Verify that top_allocations returns largest allocations sorted by size.
    /// Invariants: Results must be sorted descending by size and limited to requested count.
    #[test]
    fn test_top_allocations() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x3000, 512, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.top_allocations(2);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(allocations.count, 2, "Should return exactly 2 allocations");
                assert_eq!(
                    allocations.allocations[0].size, 2048,
                    "First should be largest"
                );
                assert_eq!(
                    allocations.allocations[1].size, 1024,
                    "Second should be second largest"
                );
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that allocations_by_thread filters correctly by thread ID.
    /// Invariants: Only allocations from specified thread should be returned.
    #[test]
    fn test_allocations_by_thread() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 2));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_by_thread(1);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 1,
                    "Should have one allocation for thread 1"
                );
                assert_eq!(allocations.total_bytes, 1024, "Total bytes should be 1024");
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that thread_stats returns correct thread statistics.
    /// Invariants: Thread count should match number of unique threads with allocations.
    #[test]
    fn test_thread_stats() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 2));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.thread_stats();
        match result {
            QueryResult::Threads(threads) => {
                assert_eq!(threads.count, 2, "Should have 2 threads with allocations");
            }
            _ => panic!("Expected threads result"),
        }
    }

    /// Objective: Verify that summary correctly tracks allocations and deallocations.
    /// Invariants: Summary must accurately reflect allocation/deallocation counts.
    #[test]
    fn test_summary() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
        event_store.record(crate::event_store::MemoryEvent::deallocate(0x1000, 1024, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.summary();
        match result {
            QueryResult::Summary(summary) => {
                assert_eq!(summary.total_allocations, 1, "Should have one allocation");
                assert_eq!(
                    summary.total_deallocations, 1,
                    "Should have one deallocation"
                );
                assert_eq!(
                    summary.active_allocations, 0,
                    "Should have no active allocations"
                );
            }
            _ => panic!("Expected summary result"),
        }
    }

    /// Objective: Verify that allocations_by_variable filters correctly by variable name.
    /// Invariants: Only allocations with matching var_name should be returned.
    #[test]
    fn test_allocations_by_variable() {
        let event_store = Arc::new(EventStore::new());
        let mut event1 = crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1);
        event1.var_name = Some("test_var".to_string());
        event_store.record(event1);

        let mut event2 = crate::event_store::MemoryEvent::allocate(0x2000, 2048, 1);
        event2.var_name = Some("other_var".to_string());
        event_store.record(event2);

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_by_variable("test_var");
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 1,
                    "Should have one allocation with test_var"
                );
                assert_eq!(allocations.total_bytes, 1024, "Total bytes should be 1024");
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that allocations_larger_than filters by size correctly.
    /// Invariants: Only allocations larger than min_size should be returned.
    #[test]
    fn test_allocations_larger_than() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 100, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 500, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x3000, 1000, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_larger_than(200);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 2,
                    "Should have 2 allocations larger than 200"
                );
                assert_eq!(allocations.total_bytes, 1500, "Total bytes should be 1500");
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that top_allocations with zero limit returns empty result.
    /// Invariants: Zero limit should return empty allocations list.
    #[test]
    fn test_top_allocations_zero_limit() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.top_allocations(0);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 0,
                    "Zero limit should return empty result"
                );
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that allocations_by_thread returns empty for unknown thread.
    /// Invariants: Unknown thread ID should return empty allocations.
    #[test]
    fn test_allocations_by_unknown_thread() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_by_thread(999);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 0,
                    "Unknown thread should have no allocations"
                );
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that allocations_by_variable returns empty for unknown variable.
    /// Invariants: Unknown variable name should return empty allocations.
    #[test]
    fn test_allocations_by_unknown_variable() {
        let event_store = Arc::new(EventStore::new());
        let mut event = crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1);
        event.var_name = Some("known_var".to_string());
        event_store.record(event);

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_by_variable("unknown_var");
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 0,
                    "Unknown variable should have no allocations"
                );
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that allocations_larger_than returns empty when no matches.
    /// Invariants: Very large min_size should return empty allocations.
    #[test]
    fn test_allocations_larger_than_no_matches() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 100, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.allocations_larger_than(10000);
        match result {
            QueryResult::Allocations(allocations) => {
                assert_eq!(
                    allocations.count, 0,
                    "Very large min_size should return empty"
                );
            }
            _ => panic!("Expected allocations result"),
        }
    }

    /// Objective: Verify that summary tracks peak memory correctly.
    /// Invariants: Peak memory should reflect maximum memory usage.
    #[test]
    fn test_summary_peak_memory() {
        let event_store = Arc::new(EventStore::new());
        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 1));
        event_store.record(crate::event_store::MemoryEvent::deallocate(0x1000, 1024, 1));

        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
        let query_engine = QueryEngine::new(snapshot_engine);

        let result = query_engine.summary();
        match result {
            QueryResult::Summary(summary) => {
                assert!(
                    summary.peak_memory >= 2048,
                    "Peak memory should be at least 2048"
                );
            }
            _ => panic!("Expected summary result"),
        }
    }
}