ringkernel_procint/models/
trace.rs

1//! Process trace definitions.
2
3use super::{ActivityId, GpuObjectEvent, HybridTimestamp};
4use rkyv::{Archive, Deserialize, Serialize};
5
6/// Trace identifier type.
7pub type TraceId = u64;
8
9/// A process trace (case) containing a sequence of events.
10#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
11pub struct ProcessTrace {
12    /// Unique trace identifier.
13    pub id: TraceId,
14    /// Case identifier (business key).
15    pub case_id: String,
16    /// Sequence of activity IDs in execution order.
17    pub activities: Vec<ActivityId>,
18    /// Timestamps for each activity.
19    pub timestamps: Vec<HybridTimestamp>,
20    /// Durations for each activity (ms).
21    pub durations: Vec<u32>,
22    /// Total trace duration (ms).
23    pub total_duration_ms: u64,
24    /// First event timestamp.
25    pub start_time: HybridTimestamp,
26    /// Last event timestamp.
27    pub end_time: HybridTimestamp,
28    /// Whether trace is complete.
29    pub is_complete: bool,
30    /// Variant identifier (for trace clustering).
31    pub variant_id: u32,
32}
33
34impl ProcessTrace {
35    /// Create a new empty trace.
36    pub fn new(id: TraceId, case_id: impl Into<String>) -> Self {
37        Self {
38            id,
39            case_id: case_id.into(),
40            activities: Vec::new(),
41            timestamps: Vec::new(),
42            durations: Vec::new(),
43            total_duration_ms: 0,
44            start_time: HybridTimestamp::default(),
45            end_time: HybridTimestamp::default(),
46            is_complete: false,
47            variant_id: 0,
48        }
49    }
50
51    /// Add an activity to the trace.
52    pub fn add_activity(
53        &mut self,
54        activity_id: ActivityId,
55        timestamp: HybridTimestamp,
56        duration_ms: u32,
57    ) {
58        if self.activities.is_empty() {
59            self.start_time = timestamp;
60        }
61        self.activities.push(activity_id);
62        self.timestamps.push(timestamp);
63        self.durations.push(duration_ms);
64        self.end_time = timestamp;
65        self.total_duration_ms = self
66            .end_time
67            .physical_ms
68            .saturating_sub(self.start_time.physical_ms);
69    }
70
71    /// Get trace length.
72    pub fn len(&self) -> usize {
73        self.activities.len()
74    }
75
76    /// Check if trace is empty.
77    pub fn is_empty(&self) -> bool {
78        self.activities.is_empty()
79    }
80
81    /// Get activity at index.
82    pub fn get(&self, index: usize) -> Option<ActivityId> {
83        self.activities.get(index).copied()
84    }
85
86    /// Get edges (directly-follows pairs).
87    pub fn edges(&self) -> impl Iterator<Item = (ActivityId, ActivityId)> + '_ {
88        self.activities.windows(2).map(|w| (w[0], w[1]))
89    }
90
91    /// Mark trace as complete.
92    pub fn complete(&mut self) {
93        self.is_complete = true;
94    }
95}
96
97/// Builder for constructing traces from events.
98#[derive(Debug, Default)]
99pub struct TraceBuilder {
100    traces: std::collections::HashMap<u64, ProcessTrace>,
101    next_trace_id: TraceId,
102}
103
104impl TraceBuilder {
105    /// Create a new trace builder.
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Process an event and update the corresponding trace.
111    pub fn process_event(&mut self, event: &GpuObjectEvent) {
112        let trace = self.traces.entry(event.object_id).or_insert_with(|| {
113            let id = self.next_trace_id;
114            self.next_trace_id += 1;
115            ProcessTrace::new(id, event.object_id.to_string())
116        });
117
118        trace.add_activity(event.activity_id, event.timestamp, event.duration_ms);
119    }
120
121    /// Get all traces.
122    pub fn traces(&self) -> impl Iterator<Item = &ProcessTrace> {
123        self.traces.values()
124    }
125
126    /// Get trace by object ID.
127    pub fn get_trace(&self, object_id: u64) -> Option<&ProcessTrace> {
128        self.traces.get(&object_id)
129    }
130
131    /// Take ownership of all traces.
132    pub fn into_traces(self) -> Vec<ProcessTrace> {
133        self.traces.into_values().collect()
134    }
135
136    /// Number of traces.
137    pub fn len(&self) -> usize {
138        self.traces.len()
139    }
140
141    /// Check if empty.
142    pub fn is_empty(&self) -> bool {
143        self.traces.is_empty()
144    }
145}
146
147/// GPU-compatible trace representation for batch processing.
148#[derive(Debug, Clone, Copy, Default, Archive, Serialize, Deserialize)]
149#[repr(C, align(64))]
150pub struct GpuProcessTrace {
151    /// Trace identifier.
152    pub trace_id: u64,
153    /// Case identifier hash.
154    pub case_id_hash: u64,
155    /// Number of activities.
156    pub activity_count: u32,
157    /// Variant identifier.
158    pub variant_id: u32,
159    /// Start timestamp.
160    pub start_time_ms: u64,
161    /// End timestamp.
162    pub end_time_ms: u64,
163    /// Total duration.
164    pub total_duration_ms: u64,
165    /// Flags.
166    pub flags: u32,
167    /// Padding.
168    pub _padding: [u8; 4],
169}
170
171// Verify size: 8+8+4+4+8+8+8+4+4 = 56, aligned to 64
172const _: () = assert!(std::mem::size_of::<GpuProcessTrace>() == 64);
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_trace_builder() {
180        let mut builder = TraceBuilder::new();
181
182        let event1 = GpuObjectEvent::new(
183            1,
184            100,
185            1,
186            super::super::EventType::Complete,
187            HybridTimestamp::new(1000, 0),
188        );
189        let event2 = GpuObjectEvent::new(
190            2,
191            100,
192            2,
193            super::super::EventType::Complete,
194            HybridTimestamp::new(2000, 0),
195        );
196
197        builder.process_event(&event1);
198        builder.process_event(&event2);
199
200        let trace = builder.get_trace(100).unwrap();
201        assert_eq!(trace.len(), 2);
202        assert_eq!(trace.activities, vec![1, 2]);
203    }
204}