1use crate::fidelity::FidelityLevel;
4use serde::Serialize;
5use std::sync::Mutex;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum TraceEvent {
12 RuntimeInit {
13 seq: u64,
14 refcount: u32,
15 profile_id: String,
16 profile_revision: String,
17 fidelity: String,
18 },
19 RuntimeShutdown {
20 seq: u64,
21 refcount: u32,
22 },
23 AgentIterateBegin {
24 seq: u64,
25 agent_count: usize,
26 },
27 AgentIterateVisit {
28 seq: u64,
29 agent_handle: u64,
30 kind: String,
31 },
32 AgentGetInfo {
33 seq: u64,
34 agent_handle: u64,
35 attribute: String,
36 outcome: String,
37 },
38 MemoryAllocate {
39 seq: u64,
40 space_handle: u64,
41 size: usize,
42 ptr: u64,
43 outcome: String,
44 },
45 MemoryFree {
46 seq: u64,
47 ptr: u64,
48 outcome: String,
49 },
50 SignalCreate {
51 seq: u64,
52 signal_handle: u64,
53 initial: i64,
54 },
55 SignalDestroy {
56 seq: u64,
57 signal_handle: u64,
58 },
59 QueueCreate {
60 seq: u64,
61 queue_id: u64,
62 size: u32,
63 agent_handle: u64,
64 },
65 QueueDestroy {
66 seq: u64,
67 queue_id: u64,
68 },
69 QueueDoorbell {
70 seq: u64,
71 queue_id: u64,
72 value: i64,
73 },
74 QueueIndexStore {
75 seq: u64,
76 queue_id: u64,
77 which: String,
78 value: u64,
79 },
80 PacketObserved {
81 seq: u64,
82 queue_id: u64,
83 packet_index: u64,
84 packet_type: u16,
85 },
86 PacketValidateFailed {
87 seq: u64,
88 queue_id: u64,
89 detail: String,
90 },
91 DispatchValidated {
93 seq: u64,
94 queue_id: u64,
95 packet_index: u64,
96 packet_type: u16,
97 dimensions: u16,
98 workgroup_size: [u16; 3],
99 grid_size: [u32; 3],
100 private_segment_size: u32,
101 group_segment_size: u32,
102 kernel_object: u64,
103 kernarg_class: String,
104 completion_signal: u64,
105 },
106 DispatchRejected {
107 seq: u64,
108 queue_id: u64,
109 packet_index: u64,
110 packet_type: u16,
111 detail: String,
112 contract: String,
113 },
114 DiagnosticComplete {
116 seq: u64,
117 queue_id: u64,
118 packet_index: u64,
119 completion_signal: u64,
120 contract: String,
121 note: String,
122 },
123 Unsupported {
124 seq: u64,
125 api: String,
126 detail: String,
127 },
128}
129
130pub trait TraceSink: Send {
132 fn record(&mut self, event: TraceEvent);
133}
134
135#[derive(Debug, Default)]
137pub struct TraceLog {
138 seq: u64,
139 events: Vec<TraceEvent>,
140 capacity: usize,
141}
142
143impl TraceLog {
144 pub fn new(capacity: usize) -> Self {
145 Self {
146 seq: 0,
147 events: Vec::new(),
148 capacity: capacity.max(1),
149 }
150 }
151
152 pub fn next_seq(&mut self) -> u64 {
153 self.seq = self.seq.saturating_add(1);
154 self.seq
155 }
156
157 pub fn events(&self) -> &[TraceEvent] {
158 &self.events
159 }
160
161 pub fn fidelity_label(level: FidelityLevel) -> String {
162 level.as_str().to_string()
163 }
164}
165
166impl TraceSink for TraceLog {
167 fn record(&mut self, event: TraceEvent) {
168 if self.events.len() >= self.capacity {
169 self.events.remove(0);
170 }
171 self.events.push(event);
172 }
173}
174
175#[derive(Debug)]
177pub struct SharedTrace {
178 inner: Mutex<TraceLog>,
179}
180
181impl SharedTrace {
182 pub fn new(capacity: usize) -> Self {
183 Self {
184 inner: Mutex::new(TraceLog::new(capacity)),
185 }
186 }
187
188 pub fn with_log<R>(&self, f: impl FnOnce(&mut TraceLog) -> R) -> R {
189 let mut guard = self
190 .inner
191 .lock()
192 .unwrap_or_else(|poisoned| poisoned.into_inner());
193 f(&mut guard)
194 }
195
196 pub fn snapshot(&self) -> Vec<TraceEvent> {
197 self.with_log(|log| log.events().to_vec())
198 }
199}
200
201pub fn unix_millis_now() -> u128 {
203 SystemTime::now()
204 .duration_since(UNIX_EPOCH)
205 .map(|d| d.as_millis())
206 .unwrap_or(0)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn ring_drops_oldest() {
215 let mut log = TraceLog::new(2);
216 log.record(TraceEvent::Unsupported {
217 seq: 1,
218 api: "a".into(),
219 detail: "1".into(),
220 });
221 log.record(TraceEvent::Unsupported {
222 seq: 2,
223 api: "b".into(),
224 detail: "2".into(),
225 });
226 log.record(TraceEvent::Unsupported {
227 seq: 3,
228 api: "c".into(),
229 detail: "3".into(),
230 });
231 assert_eq!(log.events().len(), 2);
232 match &log.events()[0] {
233 TraceEvent::Unsupported { api, .. } => assert_eq!(api, "b"),
234 _ => panic!("unexpected"),
235 }
236 }
237}