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(
49    #[bincode(with = "turbo_bincode::indexmap")] pub FxIndexMap<RcStr, RcStr>,
50);
51
52/// An arbitrary payload can be used to analyze, diagnose
53/// Turbopack's behavior.
54#[turbo_tasks::value_trait]
55pub trait Diagnostic {
56    /// [NOTE]: Pseudo-reserved; this is not being used currently.
57    /// The `type` of the diagnostics that can be used selectively filtered by
58    /// consumers. For example, this could be `telemetry`, or
59    /// `slow_perf_event`, or something else. This is not strongly typed
60    /// though; since consumer or implementation may need to define own
61    /// category.
62    #[turbo_tasks::function]
63    fn category(&self) -> Vc<RcStr>;
64    /// Name of the specific diagnostic event.
65    #[turbo_tasks::function]
66    fn name(&self) -> Vc<RcStr>;
67    /// Arbitrary payload included in the diagnostic event.
68    #[turbo_tasks::function]
69    fn payload(&self) -> Vc<DiagnosticPayload>;
70
71    #[turbo_tasks::function]
72    async fn into_plain(self: Vc<Self>) -> Result<Vc<PlainDiagnostic>> {
73        Ok(PlainDiagnostic {
74            category: self.category().owned().await?,
75            name: self.name().owned().await?,
76            payload: self.payload().owned().await?,
77        }
78        .cell())
79    }
80}
81
82pub trait DiagnosticExt {
83    fn emit(self);
84}
85
86impl<T> DiagnosticExt for ResolvedVc<T>
87where
88    T: Upcast<Box<dyn Diagnostic>>,
89{
90    fn emit(self) {
91        let diagnostic = ResolvedVc::upcast_non_strict::<Box<dyn Diagnostic>>(self);
92        emit(diagnostic);
93    }
94}
95
96#[async_trait]
97pub trait DiagnosticContextExt
98where
99    Self: Sized,
100{
101    async fn peek_diagnostics(self) -> Result<CapturedDiagnostics>;
102}
103
104#[async_trait]
105impl<T> DiagnosticContextExt for T
106where
107    T: CollectiblesSource + Copy + Send,
108{
109    async fn peek_diagnostics(self) -> Result<CapturedDiagnostics> {
110        Ok(CapturedDiagnostics {
111            diagnostics: self.peek_collectibles(),
112        })
113    }
114}
115
116/// A list of diagnostics captured with
117/// [`DiagnosticsVc::peek_diagnostics_with_path`] and
118#[derive(Debug)]
119#[turbo_tasks::value]
120pub struct CapturedDiagnostics {
121    pub diagnostics: AutoSet<ResolvedVc<Box<dyn Diagnostic>>>,
122}