ringkernel_procint/models/
trace.rs1use super::{ActivityId, GpuObjectEvent, HybridTimestamp};
4use rkyv::{Archive, Deserialize, Serialize};
5
6pub type TraceId = u64;
8
9#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
11pub struct ProcessTrace {
12 pub id: TraceId,
14 pub case_id: String,
16 pub activities: Vec<ActivityId>,
18 pub timestamps: Vec<HybridTimestamp>,
20 pub durations: Vec<u32>,
22 pub total_duration_ms: u64,
24 pub start_time: HybridTimestamp,
26 pub end_time: HybridTimestamp,
28 pub is_complete: bool,
30 pub variant_id: u32,
32}
33
34impl ProcessTrace {
35 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 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 pub fn len(&self) -> usize {
73 self.activities.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.activities.is_empty()
79 }
80
81 pub fn get(&self, index: usize) -> Option<ActivityId> {
83 self.activities.get(index).copied()
84 }
85
86 pub fn edges(&self) -> impl Iterator<Item = (ActivityId, ActivityId)> + '_ {
88 self.activities.windows(2).map(|w| (w[0], w[1]))
89 }
90
91 pub fn complete(&mut self) {
93 self.is_complete = true;
94 }
95}
96
97#[derive(Debug, Default)]
99pub struct TraceBuilder {
100 traces: std::collections::HashMap<u64, ProcessTrace>,
101 next_trace_id: TraceId,
102}
103
104impl TraceBuilder {
105 pub fn new() -> Self {
107 Self::default()
108 }
109
110 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 pub fn traces(&self) -> impl Iterator<Item = &ProcessTrace> {
123 self.traces.values()
124 }
125
126 pub fn get_trace(&self, object_id: u64) -> Option<&ProcessTrace> {
128 self.traces.get(&object_id)
129 }
130
131 pub fn into_traces(self) -> Vec<ProcessTrace> {
133 self.traces.into_values().collect()
134 }
135
136 pub fn len(&self) -> usize {
138 self.traces.len()
139 }
140
141 pub fn is_empty(&self) -> bool {
143 self.traces.is_empty()
144 }
145}
146
147#[derive(Debug, Clone, Copy, Default, Archive, Serialize, Deserialize)]
149#[repr(C, align(64))]
150pub struct GpuProcessTrace {
151 pub trace_id: u64,
153 pub case_id_hash: u64,
155 pub activity_count: u32,
157 pub variant_id: u32,
159 pub start_time_ms: u64,
161 pub end_time_ms: u64,
163 pub total_duration_ms: u64,
165 pub flags: u32,
167 pub _padding: [u8; 4],
169}
170
171const _: () = 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}