Skip to main content

memscope_rs/event_store/
event.rs

1//! Core memory event types for the event store
2//!
3//! This module defines the unified memory event type used across
4//! all engines in the memscope architecture.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Type of memory event
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum MemoryEventType {
12    /// Memory allocation event
13    Allocate,
14    /// Memory deallocation event
15    Deallocate,
16    /// Memory reallocation event
17    Reallocate,
18    /// Memory move event
19    Move,
20    /// Memory borrow event
21    Borrow,
22    /// Memory return event
23    Return,
24    /// Container/Value metadata (`ptr == 0`) or an annotation of a heap binding.
25    Metadata,
26    /// Clone event
27    Clone,
28}
29
30impl fmt::Display for MemoryEventType {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            MemoryEventType::Allocate => write!(f, "Allocate"),
34            MemoryEventType::Deallocate => write!(f, "Deallocate"),
35            MemoryEventType::Reallocate => write!(f, "Reallocate"),
36            MemoryEventType::Move => write!(f, "Move"),
37            MemoryEventType::Borrow => write!(f, "Borrow"),
38            MemoryEventType::Return => write!(f, "Return"),
39            MemoryEventType::Metadata => write!(f, "Metadata"),
40            MemoryEventType::Clone => write!(f, "Clone"),
41        }
42    }
43}
44
45/// Serde helper: omit `false` flags from the JSON projection.
46fn is_false(value: &bool) -> bool {
47    !*value
48}
49
50/// Unified memory event type
51///
52/// This structure captures all essential information about memory operations
53/// across all tracking backends (core, lockfree, async, unified).
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct MemoryEvent {
56    /// Event timestamp (nanoseconds since epoch)
57    pub timestamp: u64,
58    /// Event type
59    pub event_type: MemoryEventType,
60    /// Memory pointer address
61    pub ptr: usize,
62    /// Allocation size in bytes
63    pub size: usize,
64    /// Previous allocation size (for Reallocate events)
65    pub old_size: Option<usize>,
66    /// New address (for Reallocate events that moved the block).
67    ///
68    /// `None` means the block stayed at `ptr`; `Some(new_ptr)` means it migrated,
69    /// so the old address binding must be closed and a new one opened.
70    pub new_ptr: Option<usize>,
71    /// Capture-local binding identity, preserved across reallocation. Older event
72    /// streams omit it and use address/size matching during replay.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub binding_serial: Option<u64>,
75    /// Set when a release could only be matched by address (the address held several
76    /// live bindings), so the closed binding was chosen by policy rather than identified.
77    ///
78    /// Carried by the event instead of only a counter: replay and every export can then
79    /// say that this release is a lower bound of the evidence, not an exact fact.
80    #[serde(default, skip_serializing_if = "is_false")]
81    pub degraded_release: bool,
82    /// Registry charge result captured before publishing an allocation. Missing on legacy
83    /// streams and non-allocation events; replay must not replace it with live registry state.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub charge_outcome: Option<crate::task_registry::ChargeOutcome>,
86    /// Thread identifier
87    pub thread_id: u64,
88    /// Optional variable name
89    pub var_name: Option<String>,
90    /// Optional type name
91    pub type_name: Option<String>,
92    /// Optional call stack hash
93    pub call_stack_hash: Option<u64>,
94    /// Optional thread name
95    pub thread_name: Option<String>,
96    /// Optional source file path
97    pub source_file: Option<String>,
98    /// Optional source line number
99    pub source_line: Option<u32>,
100    /// Optional module path
101    pub module_path: Option<String>,
102    /// Clone source pointer (for Clone events)
103    pub clone_source_ptr: Option<usize>,
104    /// Clone target pointer (for Clone events)
105    pub clone_target_ptr: Option<usize>,
106    /// Stack pointer (for StackOwner types like Arc/Rc)
107    pub stack_ptr: Option<usize>,
108    /// Task ID (for task-aware memory tracking)
109    pub task_id: Option<u64>,
110}
111
112impl MemoryEvent {
113    /// Create a new allocation event.
114    ///
115    /// Constructing an event has no accounting side effect: task memory is
116    /// recorded once at the capture site (see `Tracker::track_inner`), otherwise
117    /// building an event — or replaying a recorded one — would double count.
118    pub fn allocate(ptr: usize, size: usize, thread_id: u64) -> Self {
119        Self {
120            timestamp: Self::now(),
121            event_type: MemoryEventType::Allocate,
122            ptr,
123            size,
124            old_size: None,
125            new_ptr: None,
126            binding_serial: None,
127            degraded_release: false,
128            charge_outcome: None,
129            thread_id,
130            var_name: None,
131            type_name: None,
132            call_stack_hash: None,
133            thread_name: None,
134            source_file: None,
135            source_line: None,
136            module_path: None,
137            clone_source_ptr: None,
138            clone_target_ptr: None,
139            stack_ptr: None,
140            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
141        }
142    }
143
144    /// Create a *migrating* reallocation event.
145    ///
146    /// `reallocate()` describes a block that stayed at `ptr`; this variant records
147    /// that the allocator returned a different address, so reconstruction can close
148    /// the old binding and open one at `new_ptr`.
149    pub fn reallocate_moved(
150        ptr: usize,
151        new_ptr: usize,
152        old_size: usize,
153        new_size: usize,
154        thread_id: u64,
155    ) -> Self {
156        let mut event = Self::reallocate(ptr, old_size, new_size, thread_id);
157        event.new_ptr = Some(new_ptr);
158        event
159    }
160
161    /// Create a new deallocation event
162    pub fn deallocate(ptr: usize, size: usize, thread_id: u64) -> Self {
163        Self {
164            timestamp: Self::now(),
165            event_type: MemoryEventType::Deallocate,
166            ptr,
167            size,
168            old_size: None,
169            new_ptr: None,
170            binding_serial: None,
171            degraded_release: false,
172            charge_outcome: None,
173            thread_id,
174            var_name: None,
175            type_name: None,
176            call_stack_hash: None,
177            thread_name: None,
178            source_file: None,
179            source_line: None,
180            module_path: None,
181            clone_source_ptr: None,
182            clone_target_ptr: None,
183            stack_ptr: None,
184            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
185        }
186    }
187
188    /// Create a new reallocation event
189    pub fn reallocate(ptr: usize, old_size: usize, new_size: usize, thread_id: u64) -> Self {
190        Self {
191            timestamp: Self::now(),
192            event_type: MemoryEventType::Reallocate,
193            ptr,
194            size: new_size,
195            old_size: Some(old_size),
196            new_ptr: None,
197            binding_serial: None,
198            degraded_release: false,
199            charge_outcome: None,
200            thread_id,
201            var_name: None,
202            type_name: None,
203            call_stack_hash: None,
204            thread_name: None,
205            source_file: None,
206            source_line: None,
207            module_path: None,
208            clone_source_ptr: None,
209            clone_target_ptr: None,
210            stack_ptr: None,
211            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
212        }
213    }
214
215    /// Create a new metadata event for Container/Value types (no heap allocation)
216    pub fn metadata(var_name: String, type_name: String, thread_id: u64, size: usize) -> Self {
217        Self {
218            timestamp: Self::now(),
219            event_type: MemoryEventType::Metadata,
220            ptr: 0, // No heap pointer
221            size,
222            old_size: None,
223            new_ptr: None,
224            binding_serial: None,
225            degraded_release: false,
226            charge_outcome: None,
227            thread_id,
228            var_name: Some(var_name),
229            type_name: Some(type_name),
230            call_stack_hash: None,
231            thread_name: None,
232            source_file: None,
233            source_line: None,
234            module_path: None,
235            clone_source_ptr: None,
236            clone_target_ptr: None,
237            stack_ptr: None,
238            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
239        }
240    }
241
242    /// Create a new clone event
243    pub fn clone_event(
244        source_ptr: usize,
245        target_ptr: usize,
246        size: usize,
247        thread_id: u64,
248        var_name: Option<String>,
249        type_name: Option<String>,
250    ) -> Self {
251        Self {
252            timestamp: Self::now(),
253            event_type: MemoryEventType::Clone,
254            ptr: target_ptr,
255            size,
256            old_size: None,
257            new_ptr: None,
258            binding_serial: None,
259            degraded_release: false,
260            charge_outcome: None,
261            thread_id,
262            var_name,
263            type_name,
264            call_stack_hash: None,
265            thread_name: None,
266            source_file: None,
267            source_line: None,
268            module_path: None,
269            clone_source_ptr: Some(source_ptr),
270            clone_target_ptr: Some(target_ptr),
271            stack_ptr: None,
272            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
273        }
274    }
275
276    /// Create a new borrow event
277    pub fn borrow(ptr: usize, size: usize, thread_id: u64, is_mutable: bool) -> Self {
278        Self {
279            timestamp: Self::now(),
280            event_type: MemoryEventType::Borrow,
281            ptr,
282            size,
283            old_size: None,
284            new_ptr: None,
285            binding_serial: None,
286            degraded_release: false,
287            charge_outcome: None,
288            thread_id,
289            var_name: None,
290            type_name: Some(if is_mutable {
291                "mutable borrow".to_string()
292            } else {
293                "immutable borrow".to_string()
294            }),
295            call_stack_hash: None,
296            thread_name: None,
297            source_file: None,
298            source_line: None,
299            module_path: None,
300            clone_source_ptr: None,
301            clone_target_ptr: None,
302            stack_ptr: None,
303            task_id: crate::task_registry::TaskIdRegistry::current_task_id(),
304        }
305    }
306
307    /// Get current timestamp in nanoseconds
308    /// Returns 0 if system time is before Unix epoch (should not happen in practice)
309    pub fn now() -> u64 {
310        use std::time::{SystemTime, UNIX_EPOCH};
311        SystemTime::now()
312            .duration_since(UNIX_EPOCH)
313            .map(|d| d.as_nanos() as u64)
314            .unwrap_or_default()
315    }
316
317    /// Set variable name
318    pub fn with_var_name(mut self, name: String) -> Self {
319        self.var_name = Some(name);
320        self
321    }
322
323    /// Set type name
324    pub fn with_type_name(mut self, name: String) -> Self {
325        self.type_name = Some(name);
326        self
327    }
328
329    /// Set source file path
330    pub fn with_source_file(mut self, file: String) -> Self {
331        self.source_file = Some(file);
332        self
333    }
334
335    /// Set source line number
336    pub fn with_source_line(mut self, line: u32) -> Self {
337        self.source_line = Some(line);
338        self
339    }
340
341    /// Set call stack hash
342    pub fn with_call_stack_hash(mut self, hash: u64) -> Self {
343        self.call_stack_hash = Some(hash);
344        self
345    }
346
347    /// Set thread name
348    pub fn with_thread_name(mut self, name: String) -> Self {
349        self.thread_name = Some(name);
350        self
351    }
352
353    /// Check if this is an allocation event
354    pub fn is_allocation(&self) -> bool {
355        matches!(
356            self.event_type,
357            MemoryEventType::Allocate | MemoryEventType::Reallocate | MemoryEventType::Metadata
358        )
359    }
360
361    /// Check if this is a deallocation event
362    pub fn is_deallocation(&self) -> bool {
363        matches!(self.event_type, MemoryEventType::Deallocate)
364    }
365
366    /// Check if this is a move event
367    pub fn is_move(&self) -> bool {
368        matches!(self.event_type, MemoryEventType::Move)
369    }
370
371    /// Check if this is a borrow event
372    pub fn is_borrow(&self) -> bool {
373        matches!(self.event_type, MemoryEventType::Borrow)
374    }
375
376    /// Check if this is a return event
377    pub fn is_return(&self) -> bool {
378        matches!(self.event_type, MemoryEventType::Return)
379    }
380}