turbopack_core/diagnostics/
mod.rs

1use std::cmp::Ordering;
2
3use anyhow::Result;
4use async_trait::async_trait;
5use auto_hash_map::AutoSet;
6use turbo_rcstr::RcStr;
7use turbo_tasks::{CollectiblesSource, FxIndexMap, ResolvedVc, Upcast, Vc, emit};
8
9#[turbo_tasks::value(serialization = "none")]
10#[derive(Clone, Debug)]
11pub struct PlainDiagnostic {
12    pub category: RcStr,
13    pub name: RcStr,
14    pub payload: FxIndexMap<RcStr, RcStr>,
15}
16
17impl Ord for PlainDiagnostic {
18    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
19        self.name
20            .cmp(&other.name)
21            .then_with(|| self.category.cmp(&other.category))
22            .then_with(|| self.payload.len().cmp(&other.payload.len()))
23            .then_with(|| {
24                for ((a_key, a_value), (b_key, b_value)) in
25                    self.payload.iter().zip(other.payload.iter())
26                {
27                    match a_key.cmp(b_key) {
28                        Ordering::Equal => {}
29                        other => return other,
30                    }
31                    match a_value.cmp(b_value) {
32                        Ordering::Equal => {}
33                        other => return other,
34                    }
35                }
36                Ordering::Equal
37            })
38    }
39}
40
41impl PartialOrd for PlainDiagnostic {
42    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
43        Some(self.cmp(other))
44    }
45}
46
47#[turbo_tasks::value(transparent)]
48pub struct DiagnosticPayload(pub FxIndexMap<RcStr, RcStr>);
49
50/// An arbitrary payload can be used to analyze, diagnose
51/// Turbopack's behavior.
52#[turbo_tasks::value_trait]
53pub trait Diagnostic {
54    /// [NOTE]: Pseudo-reserved; this is not being used currently.
55    /// The `type` of the diagnostics that can be used selectively filtered by
56    /// consumers. For example, this could be `telemetry`, or
57    /// `slow_perf_event`, or something else. This is not strongly typed
58    /// though; since consumer or implementation may need to define own
59    /// category.
60    #[turbo_tasks::function]
61    fn category(&self) -> Vc<RcStr>;
62    /// Name of the specific diagnostic event.
63    #[turbo_tasks::function]
64    fn name(&self) -> Vc<RcStr>;
65    /// Arbitrary payload included in the diagnostic event.
66    #[turbo_tasks::function]
67    fn payload(&self) -> Vc<DiagnosticPayload>;
68
69    #[turbo_tasks::function]
70    async fn into_plain(self: Vc<Self>) -> Result<Vc<PlainDiagnostic>> {
71        Ok(PlainDiagnostic {
72            category: self.category().owned().await?,
73            name: self.name().owned().await?,
74            payload: self.payload().owned().await?,
75        }
76        .cell())
77    }
78}
79
80pub trait DiagnosticExt {
81    fn emit(self);
82}
83
84impl<T> DiagnosticExt for ResolvedVc<T>
85where
86    T: Upcast<Box<dyn Diagnostic>>,
87{
88    fn emit(self) {
89        let diagnostic = ResolvedVc::upcast::<Box<dyn Diagnostic>>(self);
90        emit(diagnostic);
91    }
92}
93
94#[async_trait]
95pub trait DiagnosticContextExt
96where
97    Self: Sized,
98{
99    async fn peek_diagnostics(self) -> Result<CapturedDiagnostics>;
100}
101
102#[async_trait]
103impl<T> DiagnosticContextExt for T
104where
105    T: CollectiblesSource + Copy + Send,
106{
107    async fn peek_diagnostics(self) -> Result<CapturedDiagnostics> {
108        Ok(CapturedDiagnostics {
109            diagnostics: self.peek_collectibles(),
110        })
111    }
112}
113
114/// A list of diagnostics captured with
115/// [`DiagnosticsVc::peek_diagnostics_with_path`] and
116#[derive(Debug)]
117#[turbo_tasks::value]
118pub struct CapturedDiagnostics {
119    pub diagnostics: AutoSet<ResolvedVc<Box<dyn Diagnostic>>>,
120}