1use std::any::type_name_of_val;
8use std::cell::RefCell;
9use std::collections::BTreeMap;
10use std::fmt::Debug;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::{Mutex, MutexGuard, OnceLock};
13
14use crate::session::{
15 Edge, EdgeKind, Event, EventKind, FunctionMeta, Node, NodeKind, Session, TypeMeta, ValueSlot,
16};
17
18#[derive(Clone, Debug)]
24struct CallFrame {
25 call_id: u64,
26 node_id: String,
27}
28
29#[derive(Default)]
31struct RuntimeState {
32 title: String,
33 nodes: BTreeMap<String, Node>,
34 edges: BTreeMap<(String, String), Edge>,
35 events: Vec<Event>,
36 last_event_node_id: Option<String>,
37 next_seq: AtomicU64,
38 next_call_id: AtomicU64,
39}
40
41thread_local! {
42 static CALL_STACK: RefCell<Vec<CallFrame>> = const { RefCell::new(Vec::new()) };
43}
44
45static STATE: OnceLock<Mutex<RuntimeState>> = OnceLock::new();
46
47fn state() -> &'static Mutex<RuntimeState> {
48 STATE.get_or_init(|| Mutex::new(RuntimeState::default()))
49}
50
51fn lock_state() -> MutexGuard<'static, RuntimeState> {
52 state().lock().expect("dbgflow-core runtime mutex poisoned")
53}
54
55fn next_seq(state: &RuntimeState) -> u64 {
56 state.next_seq.fetch_add(1, Ordering::Relaxed) + 1
57}
58
59fn next_call_id(state: &RuntimeState) -> u64 {
60 state.next_call_id.fetch_add(1, Ordering::Relaxed) + 1
61}
62
63fn push_event(state: &mut RuntimeState, mut event: Event) {
64 event.seq = next_seq(state);
65 state.last_event_node_id = Some(event.node_id.clone());
66 state.events.push(event);
67}
68
69fn ensure_node(state: &mut RuntimeState, node: Node) {
70 state.nodes.entry(node.id.clone()).or_insert(node);
71}
72
73fn ensure_edge(state: &mut RuntimeState, from: &str, to: &str) {
74 ensure_edge_kind(state, from, to, EdgeKind::ControlFlow, None);
75}
76
77fn ensure_edge_kind(
78 state: &mut RuntimeState,
79 from: &str,
80 to: &str,
81 kind: EdgeKind,
82 label: Option<String>,
83) {
84 if from == to {
85 return;
86 }
87
88 state.edges.insert(
89 (from.to_owned(), to.to_owned()),
90 Edge {
91 from: from.to_owned(),
92 to: to.to_owned(),
93 kind,
94 label,
95 },
96 );
97}
98
99pub fn reset_session(title: impl Into<String>) {
105 let mut state = lock_state();
106 state.title = title.into();
107 state.nodes.clear();
108 state.edges.clear();
109 state.events.clear();
110 state.last_event_node_id = None;
111 state.next_seq.store(0, Ordering::Relaxed);
112 state.next_call_id.store(0, Ordering::Relaxed);
113 CALL_STACK.with(|stack| stack.borrow_mut().clear());
114}
115
116pub fn current_session() -> Session {
118 let state = lock_state();
119 let nodes = state.nodes.values().cloned().collect();
120 let edges = state.edges.values().cloned().collect();
121
122 Session {
123 title: state.title.clone(),
124 nodes,
125 edges,
126 events: state.events.clone(),
127 }
128}
129
130pub fn type_preview<T>(value: &T) -> String {
132 format!("type {}", type_name_of_val(value))
133}
134
135pub trait UiDebugValue: Debug + Sized {
141 fn ui_debug_type_meta() -> TypeMeta;
143
144 fn ui_debug_snapshot(&self) -> String {
146 format!("{self:#?}")
147 }
148
149 fn emit_snapshot(&self, label: impl Into<String>) {
151 runtime::record_type_snapshot(self, label.into());
152 }
153}
154
155pub mod runtime {
161 use super::{
162 CALL_STACK, CallFrame, EdgeKind, Event, EventKind, FunctionMeta, Node, NodeKind,
163 UiDebugValue, ValueSlot, ensure_edge, ensure_edge_kind, ensure_node, lock_state,
164 next_call_id, push_event,
165 };
166
167 #[derive(Debug)]
169 pub struct TraceFrame {
170 call_id: u64,
171 parent_call_id: Option<u64>,
172 node_id: &'static str,
173 finished: bool,
174 }
175
176 impl TraceFrame {
177 pub fn enter(meta: FunctionMeta, values: Vec<ValueSlot>) -> Self {
179 let (call_id, parent_call_id) = {
180 let mut state = lock_state();
181
182 ensure_node(
183 &mut state,
184 Node {
185 id: meta.id.to_owned(),
186 function_id: None,
187 call_id: None,
188 label: meta.label.to_owned(),
189 kind: NodeKind::Function,
190 module_path: meta.module_path.to_owned(),
191 file: meta.file.to_owned(),
192 line: meta.line,
193 source: Some(meta.source.to_owned()),
194 },
195 );
196
197 let call_id = next_call_id(&state);
198 let parent_call_id = CALL_STACK.with(|stack| {
199 let stack = stack.borrow();
200 stack.last().map(|frame| frame.call_id)
201 });
202
203 let parent_node = CALL_STACK.with(|stack| {
204 let stack = stack.borrow();
205 stack.last().map(|frame| frame.node_id.clone())
206 });
207
208 if let Some(parent_node) = parent_node {
209 ensure_edge(&mut state, &parent_node, meta.id);
210 }
211
212 push_event(
213 &mut state,
214 Event {
215 seq: 0,
216 call_id: Some(call_id),
217 parent_call_id,
218 node_id: meta.id.to_owned(),
219 kind: EventKind::FunctionEnter,
220 title: format!("enter {}", meta.label),
221 values,
222 },
223 );
224
225 (call_id, parent_call_id)
226 };
227
228 CALL_STACK.with(|stack| {
229 stack.borrow_mut().push(CallFrame {
230 call_id,
231 node_id: meta.id.to_owned(),
232 });
233 });
234
235 Self {
236 call_id,
237 parent_call_id,
238 node_id: meta.id,
239 finished: false,
240 }
241 }
242
243 pub fn finish_return<T: std::fmt::Debug>(&mut self, result: &T) {
245 if self.finished {
246 return;
247 }
248
249 {
250 let mut state = lock_state();
251 push_event(
252 &mut state,
253 Event {
254 seq: 0,
255 call_id: Some(self.call_id),
256 parent_call_id: self.parent_call_id,
257 node_id: self.node_id.to_owned(),
258 kind: EventKind::FunctionExit,
259 title: format!(
260 "return {}",
261 self.node_id.rsplit("::").next().unwrap_or(self.node_id)
262 ),
263 values: vec![ValueSlot {
264 name: "result".to_owned(),
265 preview: format!("{result:#?}"),
266 }],
267 },
268 );
269 }
270
271 self.finished = true;
272 pop_stack(self.call_id);
273 }
274
275 pub fn finish_error(&mut self, message: impl Into<String>) {
277 if self.finished {
278 return;
279 }
280
281 {
282 let mut state = lock_state();
283 push_event(
284 &mut state,
285 Event {
286 seq: 0,
287 call_id: Some(self.call_id),
288 parent_call_id: self.parent_call_id,
289 node_id: self.node_id.to_owned(),
290 kind: EventKind::FunctionExit,
291 title: format!(
292 "panic {}",
293 self.node_id.rsplit("::").next().unwrap_or(self.node_id)
294 ),
295 values: vec![ValueSlot {
296 name: "status".to_owned(),
297 preview: message.into(),
298 }],
299 },
300 );
301 }
302
303 self.finished = true;
304 pop_stack(self.call_id);
305 }
306 }
307
308 impl Drop for TraceFrame {
309 fn drop(&mut self) {
310 if self.finished {
311 return;
312 }
313
314 self.finish_error("unwound before explicit return");
315 }
316 }
317
318 fn pop_stack(call_id: u64) {
319 CALL_STACK.with(|stack| {
320 let mut stack = stack.borrow_mut();
321 if stack.last().map(|frame| frame.call_id) == Some(call_id) {
322 stack.pop();
323 }
324 });
325 }
326
327 pub fn preview_argument<T: std::fmt::Debug>(name: impl Into<String>, value: &T) -> ValueSlot {
329 ValueSlot {
330 name: name.into(),
331 preview: format!("{value:#?}"),
332 }
333 }
334
335 pub fn record_type_snapshot<T: UiDebugValue>(value: &T, label: impl Into<String>) {
337 let meta = T::ui_debug_type_meta();
338 let label = label.into();
339 let mut state = lock_state();
340
341 ensure_node(
342 &mut state,
343 Node {
344 id: meta.id.to_owned(),
345 function_id: None,
346 call_id: None,
347 label: meta.label.to_owned(),
348 kind: NodeKind::Type,
349 module_path: meta.module_path.to_owned(),
350 file: meta.file.to_owned(),
351 line: meta.line,
352 source: Some(meta.source.to_owned()),
353 },
354 );
355
356 if let Some(parent_node) = CALL_STACK.with(|stack| {
357 let stack = stack.borrow();
358 stack.last().map(|frame| frame.node_id.clone())
359 }) {
360 ensure_edge_kind(
361 &mut state,
362 &parent_node,
363 meta.id,
364 EdgeKind::DataFlow,
365 Some(label.clone()),
366 );
367 }
368
369 push_event(
370 &mut state,
371 Event {
372 seq: 0,
373 call_id: CALL_STACK.with(|stack| {
374 let stack = stack.borrow();
375 stack.last().map(|frame| frame.call_id)
376 }),
377 parent_call_id: None,
378 node_id: meta.id.to_owned(),
379 kind: EventKind::ValueSnapshot,
380 title: label,
381 values: vec![ValueSlot {
382 name: meta.label.to_owned(),
383 preview: value.ui_debug_snapshot(),
384 }],
385 },
386 );
387 }
388
389 pub fn record_test_started(test_name: impl Into<String>, node_id: impl Into<String>) {
391 let test_name = test_name.into();
392 record_test_event(
393 EventKind::TestStarted,
394 test_name.clone(),
395 short_label(&test_name),
396 node_id.into(),
397 Vec::new(),
398 );
399 }
400
401 pub fn record_test_passed(test_name: impl Into<String>, node_id: impl Into<String>) {
403 let test_name = test_name.into();
404 record_test_event(
405 EventKind::TestPassed,
406 test_name.clone(),
407 short_label(&test_name),
408 node_id.into(),
409 Vec::new(),
410 );
411 }
412
413 pub fn record_test_failed(
415 test_name: impl Into<String>,
416 node_id: impl Into<String>,
417 failure: impl Into<String>,
418 ) {
419 let test_name = test_name.into();
420 record_test_event(
421 EventKind::TestFailed,
422 test_name.clone(),
423 short_label(&test_name),
424 node_id.into(),
425 vec![ValueSlot {
426 name: "failure".to_owned(),
427 preview: failure.into(),
428 }],
429 );
430 }
431
432 pub fn latest_node_id() -> Option<String> {
434 let state = lock_state();
435 state.last_event_node_id.clone()
436 }
437
438 pub fn record_test_started_latest(test_name: impl Into<String>) {
440 let test_name = test_name.into();
441 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
442 let label = short_label(&test_name);
443 record_test_event(
444 EventKind::TestStarted,
445 test_name,
446 label,
447 node_id,
448 Vec::new(),
449 );
450 }
451
452 pub fn record_test_passed_latest(test_name: impl Into<String>) {
454 let test_name = test_name.into();
455 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
456 let label = short_label(&test_name);
457 record_test_event(EventKind::TestPassed, test_name, label, node_id, Vec::new());
458 }
459
460 pub fn record_test_failed_latest(test_name: impl Into<String>, failure: impl Into<String>) {
462 let test_name = test_name.into();
463 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
464 let label = short_label(&test_name);
465 record_test_event(
466 EventKind::TestFailed,
467 test_name,
468 label,
469 node_id,
470 vec![ValueSlot {
471 name: "failure".to_owned(),
472 preview: failure.into(),
473 }],
474 );
475 }
476
477 pub fn record_test_started_latest_with_label(
479 test_name: impl Into<String>,
480 label: impl Into<String>,
481 ) {
482 let test_name = test_name.into();
483 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
484 record_test_event(
485 EventKind::TestStarted,
486 test_name,
487 label.into(),
488 node_id,
489 Vec::new(),
490 );
491 }
492
493 pub fn record_test_passed_latest_with_label(
495 test_name: impl Into<String>,
496 label: impl Into<String>,
497 ) {
498 let test_name = test_name.into();
499 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
500 record_test_event(
501 EventKind::TestPassed,
502 test_name,
503 label.into(),
504 node_id,
505 Vec::new(),
506 );
507 }
508
509 pub fn record_test_failed_latest_with_label(
511 test_name: impl Into<String>,
512 label: impl Into<String>,
513 failure: impl Into<String>,
514 ) {
515 let test_name = test_name.into();
516 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
517 record_test_event(
518 EventKind::TestFailed,
519 test_name,
520 label.into(),
521 node_id,
522 vec![ValueSlot {
523 name: "failure".to_owned(),
524 preview: failure.into(),
525 }],
526 );
527 }
528
529 fn record_test_event(
530 kind: EventKind,
531 test_name: String,
532 label: String,
533 node_id: String,
534 values: Vec<ValueSlot>,
535 ) {
536 let test_id = format!("test::{test_name}");
537 let mut state = lock_state();
538
539 ensure_node(
540 &mut state,
541 Node {
542 id: test_id.clone(),
543 function_id: None,
544 call_id: None,
545 label,
546 kind: NodeKind::Test,
547 module_path: "cargo::test".to_owned(),
548 file: "<runner>".to_owned(),
549 line: 0,
550 source: None,
551 },
552 );
553 ensure_edge_kind(&mut state, &test_id, &node_id, EdgeKind::TestLink, None);
554
555 push_event(
556 &mut state,
557 Event {
558 seq: 0,
559 call_id: None,
560 parent_call_id: None,
561 node_id: test_id,
562 kind,
563 title: short_label(&test_name),
564 values,
565 },
566 );
567 }
568
569 fn short_label(path: &str) -> String {
571 path.rsplit("::").next().unwrap_or(path).to_owned()
572 }
573
574 use std::future::Future;
579 use std::pin::Pin;
580 use std::task::{Context, Poll};
581
582 pin_project_lite::pin_project! {
583 pub struct InstrumentedFuture<F> {
585 #[pin]
586 inner: F,
587 meta: FunctionMeta,
588 values: Option<Vec<ValueSlot>>,
589 call_id: Option<u64>,
590 parent_call_id: Option<u64>,
591 node_id: &'static str,
592 finished: bool,
593 }
594 }
595
596 pub fn trace_future<F: Future>(
598 meta: FunctionMeta,
599 values: Vec<ValueSlot>,
600 inner: F,
601 ) -> InstrumentedFuture<F>
602 where
603 F::Output: std::fmt::Debug,
604 {
605 let parent_call_id =
606 CALL_STACK.with(|stack| stack.borrow().last().map(|frame| frame.call_id));
607
608 InstrumentedFuture {
609 inner,
610 meta,
611 values: Some(values),
612 call_id: None,
613 parent_call_id,
614 node_id: meta.id,
615 finished: false,
616 }
617 }
618
619 impl<F: Future> Future for InstrumentedFuture<F>
620 where
621 F::Output: std::fmt::Debug,
622 {
623 type Output = F::Output;
624
625 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
626 let this = self.project();
627
628 if this.call_id.is_none() {
629 let mut state = lock_state();
630
631 ensure_node(
632 &mut state,
633 Node {
634 id: this.meta.id.to_owned(),
635 function_id: None,
636 call_id: None,
637 label: this.meta.label.to_owned(),
638 kind: NodeKind::Function,
639 module_path: this.meta.module_path.to_owned(),
640 file: this.meta.file.to_owned(),
641 line: this.meta.line,
642 source: Some(this.meta.source.to_owned()),
643 },
644 );
645
646 let new_call_id = next_call_id(&state);
647 *this.call_id = Some(new_call_id);
648
649 let parent_node = CALL_STACK.with(|stack| {
650 let stack = stack.borrow();
651 stack.last().map(|frame| frame.node_id.clone())
652 });
653
654 if let Some(parent_node) = parent_node {
655 ensure_edge(&mut state, &parent_node, this.meta.id);
656 }
657
658 push_event(
659 &mut state,
660 Event {
661 seq: 0,
662 call_id: Some(new_call_id),
663 parent_call_id: *this.parent_call_id,
664 node_id: this.meta.id.to_owned(),
665 kind: EventKind::FunctionEnter,
666 title: format!("enter {}", this.meta.label),
667 values: this.values.take().unwrap_or_default(),
668 },
669 );
670 }
671
672 let call_id = this.call_id.unwrap();
673
674 CALL_STACK.with(|stack| {
675 stack.borrow_mut().push(CallFrame {
676 call_id,
677 node_id: this.node_id.to_owned(),
678 });
679 });
680
681 let res = this.inner.poll(cx);
682
683 CALL_STACK.with(|stack| {
684 stack.borrow_mut().pop();
685 });
686
687 if res.is_ready() && !*this.finished {
688 *this.finished = true;
689 let mut state = lock_state();
690 push_event(
691 &mut state,
692 Event {
693 seq: 0,
694 call_id: Some(call_id),
695 parent_call_id: *this.parent_call_id,
696 node_id: this.node_id.to_owned(),
697 kind: EventKind::FunctionExit,
698 title: format!(
699 "return {}",
700 this.node_id.rsplit("::").next().unwrap_or(this.node_id)
701 ),
702 values: vec![ValueSlot {
703 name: "result".to_owned(),
704 preview: match &res {
705 std::task::Poll::Ready(val) => format!("{val:#?}"),
706 _ => unreachable!(),
707 },
708 }],
709 },
710 );
711 }
712
713 res
714 }
715 }
716}