1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::sync::{Mutex, OnceLock};
4
5use moire_trace_types::{BacktraceId, FrameId, RelPc};
6use moire_types::{
7 BacktraceFrameResolved, BacktraceFrameUnresolved, SnapshotBacktrace, SnapshotBacktraceFrame,
8 SnapshotCutResponse, SnapshotFrameRecord,
9};
10
11use crate::db::Db;
12use crate::snapshot::repository::load_backtrace_frame_batches;
13use crate::util::source_path::resolve_source_path;
14
15pub struct SnapshotBacktraceTable {
16 pub backtraces: Vec<SnapshotBacktrace>,
17 pub frames: Vec<SnapshotFrameRecord>,
18}
19
20pub fn collect_snapshot_backtrace_ids(snapshot: &SnapshotCutResponse) -> Vec<BacktraceId> {
21 let mut backtrace_ids = Vec::new();
22 for process in &snapshot.processes {
23 for entity in &process.snapshot.entities {
24 backtrace_ids.push(entity.backtrace);
25 }
26 for scope in &process.snapshot.scopes {
27 backtrace_ids.push(scope.backtrace);
28 }
29 for edge in &process.snapshot.edges {
30 backtrace_ids.push(edge.backtrace);
31 }
32 for event in &process.snapshot.events {
33 backtrace_ids.push(event.backtrace);
34 }
35 }
36 backtrace_ids.sort_unstable();
37 backtrace_ids.dedup();
38 backtrace_ids
39}
40
41pub fn is_pending_frame(frame: &SnapshotBacktraceFrame) -> bool {
42 matches!(
43 frame,
44 SnapshotBacktraceFrame::Unresolved(BacktraceFrameUnresolved { reason, .. })
45 if reason == "symbolication pending"
46 )
47}
48
49#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
50struct FrameDedupKey {
51 module_identity: String,
52 module_path: String,
53 rel_pc: RelPc,
54}
55
56pub async fn load_snapshot_backtrace_table(
57 db: Arc<Db>,
58 backtrace_ids: &[BacktraceId],
59) -> SnapshotBacktraceTable {
60 if backtrace_ids.is_empty() {
61 return SnapshotBacktraceTable {
62 backtraces: vec![],
63 frames: vec![],
64 };
65 }
66
67 let backtrace_ids = backtrace_ids.to_vec();
68 tokio::task::spawn_blocking(move || load_snapshot_backtrace_table_blocking(&db, &backtrace_ids))
69 .await
70 .unwrap_or_else(|error| panic!("join snapshot backtrace loader: {error}"))
71 .unwrap_or_else(|error| panic!("load snapshot backtrace table: {error}"))
72}
73
74fn load_snapshot_backtrace_table_blocking(
75 db: &Db,
76 backtrace_ids: &[BacktraceId],
77) -> Result<SnapshotBacktraceTable, String> {
78 let batches = load_backtrace_frame_batches(db, backtrace_ids)?;
80
81 let mut frame_id_by_key: BTreeMap<FrameDedupKey, FrameId> = BTreeMap::new();
82 let mut frame_by_id: BTreeMap<FrameId, SnapshotBacktraceFrame> = BTreeMap::new();
83 let mut backtrace_entries: Vec<SnapshotBacktrace> = Vec::with_capacity(batches.len());
84
85 for batch in batches {
86 let mut this_frame_ids: Vec<FrameId> = Vec::with_capacity(batch.raw_rows.len());
87 for raw in batch.raw_rows {
88 let frame = match batch.symbolicated_by_index.get(&raw.frame_index) {
89 Some(sym) if sym.status == "resolved" => {
90 match (sym.function_name.as_ref(), sym.source_file_path.as_ref()) {
91 (Some(function_name), Some(source_file)) => {
92 SnapshotBacktraceFrame::Resolved(BacktraceFrameResolved {
93 module_path: sym.module_path.clone(),
94 function_name: function_name.clone(),
95 source_file: resolve_source_path(source_file).into_owned(),
96 line: sym.source_line.and_then(|line| u32::try_from(line).ok()),
97 })
98 }
99 _ => SnapshotBacktraceFrame::Unresolved(BacktraceFrameUnresolved {
100 module_path: sym.module_path.clone(),
101 rel_pc: sym.rel_pc,
102 reason: String::from(
103 "resolved symbolication row missing function/source fields",
104 ),
105 }),
106 }
107 }
108 Some(sym) => SnapshotBacktraceFrame::Unresolved(BacktraceFrameUnresolved {
109 module_path: sym.module_path.clone(),
110 rel_pc: sym.rel_pc,
111 reason: sym
112 .unresolved_reason
113 .clone()
114 .unwrap_or_else(|| String::from("symbolication unresolved")),
115 }),
116 None => SnapshotBacktraceFrame::Unresolved(BacktraceFrameUnresolved {
117 module_path: raw.module_path.clone(),
118 rel_pc: raw.rel_pc,
119 reason: String::from("symbolication pending"),
120 }),
121 };
122 let key = FrameDedupKey {
123 module_identity: raw.module_identity,
124 module_path: raw.module_path.clone(),
125 rel_pc: raw.rel_pc,
126 };
127 let frame_id = match frame_id_by_key.get(&key) {
128 Some(existing) => {
129 let existing_frame = frame_by_id.get(existing).cloned().ok_or_else(|| {
130 format!(
131 "invariant violated: frame id {} missing from frame map for backtrace {}",
132 existing,
133 batch.backtrace_id
134 )
135 })?;
136 let merged = merge_frame_state(&existing_frame, &frame, &key)?;
137 frame_by_id.insert(*existing, merged);
138 *existing
139 }
140 None => {
141 let assigned = stable_frame_id(&key)?;
142 if let Some(existing_key) =
143 frame_id_by_key
144 .iter()
145 .find_map(|(existing_key, existing_id)| {
146 if *existing_id == assigned {
147 Some(existing_key.clone())
148 } else {
149 None
150 }
151 })
152 && existing_key != key
153 {
154 return Err(format!(
155 "invariant violated: stable frame_id collision id={} existing=({}, {}, {:#x}) incoming=({}, {}, {:#x})",
156 assigned,
157 existing_key.module_identity,
158 existing_key.module_path,
159 existing_key.rel_pc.get(),
160 key.module_identity,
161 key.module_path,
162 key.rel_pc.get()
163 ));
164 }
165 frame_id_by_key.insert(key, assigned);
166 frame_by_id.insert(assigned, frame.clone());
167 assigned
168 }
169 };
170 this_frame_ids.push(frame_id);
171 }
172 backtrace_entries.push(SnapshotBacktrace {
173 backtrace_id: batch.backtrace_id,
174 frame_ids: this_frame_ids,
175 });
176 }
177
178 let frames = frame_by_id
179 .into_iter()
180 .map(|(frame_id, frame)| SnapshotFrameRecord { frame_id, frame })
181 .collect();
182
183 Ok(SnapshotBacktraceTable {
184 backtraces: backtrace_entries,
185 frames,
186 })
187}
188
189fn frame_resolution_rank(frame: &SnapshotBacktraceFrame) -> u8 {
190 match frame {
191 SnapshotBacktraceFrame::Resolved(_) => 2,
192 unresolved @ SnapshotBacktraceFrame::Unresolved(_) => {
193 if is_pending_frame(unresolved) {
194 0
195 } else {
196 1
197 }
198 }
199 }
200}
201
202fn merge_frame_state(
203 existing: &SnapshotBacktraceFrame,
204 incoming: &SnapshotBacktraceFrame,
205 key: &FrameDedupKey,
206) -> Result<SnapshotBacktraceFrame, String> {
207 if existing == incoming {
208 return Ok(existing.clone());
209 }
210
211 match (existing, incoming) {
212 (SnapshotBacktraceFrame::Resolved(a), SnapshotBacktraceFrame::Resolved(b)) if a != b => {
213 return Err(format!(
214 "invariant violated: conflicting resolved symbolication for frame key ({}, {}, {:#x})",
215 key.module_identity,
216 key.module_path,
217 key.rel_pc.get()
218 ));
219 }
220 _ => {}
221 }
222
223 let existing_rank = frame_resolution_rank(existing);
224 let incoming_rank = frame_resolution_rank(incoming);
225 if incoming_rank >= existing_rank {
226 Ok(incoming.clone())
227 } else {
228 Ok(existing.clone())
229 }
230}
231
232static FRAME_ID_REGISTRY: OnceLock<Mutex<BTreeMap<FrameDedupKey, FrameId>>> = OnceLock::new();
233
234pub fn lookup_frame_source_by_raw(raw_id: u64) -> Option<(FrameId, String, RelPc)> {
239 let registry = FRAME_ID_REGISTRY.get()?;
240 let guard = registry.lock().ok()?;
241 guard
242 .iter()
243 .find(|(_, id)| id.as_u64() == raw_id)
244 .map(|(key, id)| (*id, key.module_identity.clone(), key.rel_pc))
245}
246
247fn stable_frame_id(key: &FrameDedupKey) -> Result<FrameId, String> {
248 let registry = FRAME_ID_REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()));
249 let mut guard = registry
250 .lock()
251 .map_err(|_| String::from("invariant violated: frame id registry mutex poisoned"))?;
252 if let Some(existing) = guard.get(key).copied() {
253 return Ok(existing);
254 }
255 let frame_id = FrameId::next().map_err(|error| format!("invariant violated: {error}"))?;
256 guard.insert(key.clone(), frame_id);
257 Ok(frame_id)
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
266 fn stable_frame_id_is_deterministic_and_js_safe() {
267 let key_a = FrameDedupKey {
268 module_identity: String::from("debug_id:abc"),
269 module_path: String::from("/tmp/a"),
270 rel_pc: RelPc::new(0x1234).expect("valid rel_pc"),
271 };
272 let key_b = FrameDedupKey {
273 module_identity: String::from("debug_id:abc"),
274 module_path: String::from("/tmp/a"),
275 rel_pc: RelPc::new(0x5678).expect("valid rel_pc"),
276 };
277
278 let a1 = stable_frame_id(&key_a).expect("frame id for key_a");
279 let a2 = stable_frame_id(&key_a).expect("frame id for key_a (repeat)");
280 let b = stable_frame_id(&key_b).expect("frame id for key_b");
281
282 assert_eq!(a1, a2, "same frame key must map to same frame_id");
283 assert_ne!(
284 a1, b,
285 "different frame keys should map to different frame_id"
286 );
287 assert!(format!("{a1}").starts_with("FRAME#"));
288 assert!(format!("{b}").starts_with("FRAME#"));
289 }
290}