Skip to main content

memscope_rs/query/
engine.rs

1//! Query Engine - Unified query interface
2//!
3//! This module provides the QueryEngine which is responsible for
4//! querying snapshot data.
5
6use crate::query::types::{
7    AllocationQueryResult, QueryResult, SummaryQueryResult, ThreadQueryResult,
8};
9use crate::snapshot::{MemorySnapshot, SharedSnapshotEngine};
10use std::sync::Arc;
11
12/// Query Engine - Unified query interface for memory data
13///
14/// The QueryEngine provides a unified interface for querying memory
15/// snapshot data, supporting various query types and filtering options.
16///
17/// Key properties:
18/// - Unified: Single interface for all query types
19/// - Efficient: Optimized for fast query execution
20/// - Flexible: Supports filtering and sorting
21pub struct QueryEngine {
22    /// Reference to the snapshot engine
23    snapshot_engine: SharedSnapshotEngine,
24}
25
26/// Shared reference to QueryEngine
27pub type SharedQueryEngine = Arc<QueryEngine>;
28
29impl QueryEngine {
30    /// Create a new QueryEngine
31    pub fn new(snapshot_engine: SharedSnapshotEngine) -> Self {
32        Self { snapshot_engine }
33    }
34
35    /// Get the current snapshot
36    fn get_snapshot(&self) -> MemorySnapshot {
37        self.snapshot_engine.build_snapshot()
38    }
39
40    /// Query for top allocations by size
41    ///
42    /// # Arguments
43    /// * `limit` - Maximum number of allocations to return
44    pub fn top_allocations(&self, limit: usize) -> QueryResult {
45        let snapshot = self.get_snapshot();
46        let mut allocations: Vec<_> = snapshot.active_allocations.values().cloned().collect();
47
48        // Sort by size descending
49        allocations.sort_by(|a, b| b.size.cmp(&a.size));
50
51        // Limit results
52        allocations.truncate(limit);
53
54        let total_bytes = allocations.iter().map(|a| a.size).sum();
55
56        QueryResult::Allocations(AllocationQueryResult {
57            count: allocations.len(),
58            total_bytes,
59            allocations,
60        })
61    }
62
63    /// Query for allocations from a specific thread
64    ///
65    /// # Arguments
66    /// * `thread_id` - The thread ID to filter by
67    pub fn allocations_by_thread(&self, thread_id: u64) -> QueryResult {
68        let snapshot = self.get_snapshot();
69        let allocations: Vec<_> = snapshot
70            .active_allocations
71            .values()
72            .filter(|a| a.thread_id == thread_id)
73            .cloned()
74            .collect();
75
76        let total_bytes = allocations.iter().map(|a| a.size).sum();
77
78        QueryResult::Allocations(AllocationQueryResult {
79            count: allocations.len(),
80            total_bytes,
81            allocations,
82        })
83    }
84
85    /// Query for thread statistics
86    pub fn thread_stats(&self) -> QueryResult {
87        let snapshot = self.get_snapshot();
88        let threads: Vec<_> = snapshot.thread_stats.values().cloned().collect();
89        let total_bytes = threads.iter().map(|t| t.current_memory).sum();
90
91        QueryResult::Threads(ThreadQueryResult {
92            count: threads.len(),
93            total_bytes,
94            threads,
95        })
96    }
97
98    /// Query for a summary of memory usage
99    pub fn summary(&self) -> QueryResult {
100        let snapshot = self.get_snapshot();
101
102        QueryResult::Summary(SummaryQueryResult {
103            total_allocations: snapshot.stats.total_allocations,
104            total_deallocations: snapshot.stats.total_deallocations,
105            active_allocations: snapshot.stats.active_allocations,
106            total_allocated: snapshot.stats.total_allocated,
107            total_deallocated: snapshot.stats.total_deallocated,
108            current_memory: snapshot.stats.current_memory,
109            peak_memory: snapshot.stats.peak_memory,
110            thread_count: snapshot.thread_stats.len(),
111        })
112    }
113
114    /// Query for allocations with a specific variable name
115    ///
116    /// # Arguments
117    /// * `var_name` - The variable name to filter by
118    pub fn allocations_by_variable(&self, var_name: &str) -> QueryResult {
119        let snapshot = self.get_snapshot();
120        let allocations: Vec<_> = snapshot
121            .active_allocations
122            .values()
123            .filter(|a| a.var_name.as_ref().map(|n| n == var_name).unwrap_or(false))
124            .cloned()
125            .collect();
126
127        let total_bytes = allocations.iter().map(|a| a.size).sum();
128
129        QueryResult::Allocations(AllocationQueryResult {
130            count: allocations.len(),
131            total_bytes,
132            allocations,
133        })
134    }
135
136    /// Query for allocations larger than a certain size
137    ///
138    /// # Arguments
139    /// * `min_size` - Minimum allocation size in bytes
140    pub fn allocations_larger_than(&self, min_size: usize) -> QueryResult {
141        let snapshot = self.get_snapshot();
142        let allocations: Vec<_> = snapshot
143            .active_allocations
144            .values()
145            .filter(|a| a.size > min_size)
146            .cloned()
147            .collect();
148
149        let total_bytes = allocations.iter().map(|a| a.size).sum();
150
151        QueryResult::Allocations(AllocationQueryResult {
152            count: allocations.len(),
153            total_bytes,
154            allocations,
155        })
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::event_store::EventStore;
163    use crate::snapshot::SnapshotEngine;
164
165    #[test]
166    fn test_query_engine_creation() {
167        let event_store = Arc::new(EventStore::new());
168        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
169        let query_engine = QueryEngine::new(snapshot_engine);
170
171        let result = query_engine.summary();
172        match result {
173            QueryResult::Summary(summary) => {
174                assert_eq!(summary.total_allocations, 0);
175            }
176            _ => panic!("Expected summary result"),
177        }
178    }
179
180    #[test]
181    fn test_top_allocations() {
182        let event_store = Arc::new(EventStore::new());
183        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
184        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 1));
185        event_store.record(crate::event_store::MemoryEvent::allocate(0x3000, 512, 1));
186
187        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
188        let query_engine = QueryEngine::new(snapshot_engine);
189
190        let result = query_engine.top_allocations(2);
191        match result {
192            QueryResult::Allocations(allocations) => {
193                assert_eq!(allocations.count, 2);
194                assert_eq!(allocations.allocations[0].size, 2048);
195                assert_eq!(allocations.allocations[1].size, 1024);
196            }
197            _ => panic!("Expected allocations result"),
198        }
199    }
200
201    #[test]
202    fn test_allocations_by_thread() {
203        let event_store = Arc::new(EventStore::new());
204        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
205        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 2));
206
207        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
208        let query_engine = QueryEngine::new(snapshot_engine);
209
210        let result = query_engine.allocations_by_thread(1);
211        match result {
212            QueryResult::Allocations(allocations) => {
213                assert_eq!(allocations.count, 1);
214                assert_eq!(allocations.total_bytes, 1024);
215            }
216            _ => panic!("Expected allocations result"),
217        }
218    }
219
220    #[test]
221    fn test_thread_stats() {
222        let event_store = Arc::new(EventStore::new());
223        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
224        event_store.record(crate::event_store::MemoryEvent::allocate(0x2000, 2048, 2));
225
226        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
227        let query_engine = QueryEngine::new(snapshot_engine);
228
229        let result = query_engine.thread_stats();
230        match result {
231            QueryResult::Threads(threads) => {
232                assert_eq!(threads.count, 2);
233            }
234            _ => panic!("Expected threads result"),
235        }
236    }
237
238    #[test]
239    fn test_summary() {
240        let event_store = Arc::new(EventStore::new());
241        event_store.record(crate::event_store::MemoryEvent::allocate(0x1000, 1024, 1));
242        event_store.record(crate::event_store::MemoryEvent::deallocate(0x1000, 1024, 1));
243
244        let snapshot_engine = Arc::new(SnapshotEngine::new(event_store));
245        let query_engine = QueryEngine::new(snapshot_engine);
246
247        let result = query_engine.summary();
248        match result {
249            QueryResult::Summary(summary) => {
250                assert_eq!(summary.total_allocations, 1);
251                assert_eq!(summary.total_deallocations, 1);
252                assert_eq!(summary.active_allocations, 0);
253            }
254            _ => panic!("Expected summary result"),
255        }
256    }
257}