1use std::collections::HashSet;
4
5use katra_core::{EventKind, Phase};
6
7use crate::reader::TraceReader;
8use crate::record::TraceRecord;
9
10#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
12pub struct VerifyIssue {
13 pub is_error: bool,
15 pub message: String,
17}
18
19#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
21pub struct VerifyReport {
22 pub issues: Vec<VerifyIssue>,
24 pub event_count: u64,
26 pub max_seq: u64,
28 pub monotonic: bool,
30 pub causal_ok: bool,
32 pub unclosed_spans: u64,
34}
35
36impl VerifyReport {
37 pub fn is_clean(&self) -> bool {
39 !self.issues.iter().any(|i| i.is_error)
40 }
41}
42
43pub fn verify(reader: &mut TraceReader) -> VerifyReport {
45 let mut report = VerifyReport::default();
46 let mut seen_seqs: HashSet<u64> = HashSet::new();
47 let mut last_seq: Option<u64> = None;
48 let mut open_spans: std::collections::HashMap<(u64, u64), u64> =
50 std::collections::HashMap::new();
51
52 loop {
53 match reader.next_record() {
54 Ok(Some(TraceRecord::Event(ev))) => {
55 report.event_count += 1;
56 if ev.seq.0 > report.max_seq {
57 report.max_seq = ev.seq.0;
58 }
59 if let Some(last) = last_seq {
60 if ev.seq.0 <= last {
61 report.monotonic = false;
62 report.issues.push(VerifyIssue {
63 is_error: true,
64 message: format!("sequence not monotonic: {} after {}", ev.seq.0, last),
65 });
66 }
67 }
68 last_seq = Some(ev.seq.0);
69 if !seen_seqs.insert(ev.seq.0) {
70 report.issues.push(VerifyIssue {
71 is_error: true,
72 message: format!("duplicate sequence {}", ev.seq.0),
73 });
74 }
75 for cause in &ev.causes {
77 if !seen_seqs.contains(cause) {
78 report.causal_ok = false;
79 report.issues.push(VerifyIssue {
80 is_error: true,
81 message: format!(
82 "event {} references unknown/future cause {}",
83 ev.seq.0, cause
84 ),
85 });
86 }
87 }
88 match ev.phase {
90 Phase::Begin => {
91 if let Some(span) = ev.span_id {
92 let key = (ev.thread_id, span.0);
93 *open_spans.entry(key).or_insert(0) += 1;
94 } else {
95 report.issues.push(VerifyIssue {
96 is_error: true,
97 message: format!("event {} is Begin without span_id", ev.seq.0),
98 });
99 }
100 }
101 Phase::End => {
102 if let Some(span) = ev.span_id {
103 let key = (ev.thread_id, span.0);
104 match open_spans.get_mut(&key) {
105 Some(n) if *n > 0 => {
106 *n -= 1;
107 if *n == 0 {
108 open_spans.remove(&key);
109 }
110 }
111 _ => {
112 report.issues.push(VerifyIssue {
113 is_error: true,
114 message: format!(
115 "event {} is End without matching Begin \
116 (thread {}, span {})",
117 ev.seq.0, ev.thread_id, span.0
118 ),
119 });
120 }
121 }
122 }
123 }
124 _ => {}
125 }
126 }
127 Ok(Some(TraceRecord::SessionSummary(_))) => {}
128 Ok(Some(_)) => {}
129 Ok(None) => break,
130 Err(e) => {
131 report
132 .issues
133 .push(VerifyIssue { is_error: true, message: format!("read error: {e}") });
134 break;
135 }
136 }
137 }
138
139 report.unclosed_spans = open_spans.values().sum();
140 for ((thread, span), count) in open_spans {
141 if count > 0 {
142 report.issues.push(VerifyIssue {
143 is_error: true,
144 message: format!("unclosed span: thread {thread}, span {span}, {count} begins"),
145 });
146 }
147 }
148 if report.monotonic {
149 report.issues.push(VerifyIssue {
150 is_error: false,
151 message: "sequence numbers strictly monotonic".into(),
152 });
153 }
154 if report.causal_ok && report.event_count > 0 {
155 report.issues.push(VerifyIssue {
156 is_error: false,
157 message: "all causal links reference existing earlier events".into(),
158 });
159 }
160 report
161}
162
163pub fn is_graph_kind(kind: EventKind) -> bool {
165 matches!(
166 kind,
167 EventKind::FileRead
168 | EventKind::NtReadFile
169 | EventKind::IoRead
170 | EventKind::KatraDecompress
171 | EventKind::KatraStaging
172 | EventKind::ResourceUpload
173 | EventKind::Barrier
174 | EventKind::FenceWait
175 | EventKind::FenceSignal
176 | EventKind::Present
177 | EventKind::GpuDraw
178 | EventKind::GpuComputeDispatch
179 | EventKind::KatraPrefetch
180 | EventKind::KatraAllocate
181 | EventKind::CacheHit
182 | EventKind::CacheMiss
183 | EventKind::PipelineCreate
184 | EventKind::ShaderTranslate
185 | EventKind::KatraIoWait
186 )
187}