tinymist_query/analysis/
stats.rs

1//! Statistics about the analyzers
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6use tinymist_std::hash::FxDashMap;
7use tinymist_std::time::Duration;
8use typst::syntax::FileId;
9
10#[derive(Clone)]
11pub(crate) struct QueryStatBucketData {
12    pub query: u64,
13    pub missing: u64,
14    pub total: Duration,
15    pub min: Duration,
16    pub max: Duration,
17}
18
19impl Default for QueryStatBucketData {
20    fn default() -> Self {
21        Self {
22            query: 0,
23            missing: 0,
24            total: Duration::from_secs(0),
25            min: Duration::from_secs(u64::MAX),
26            max: Duration::from_secs(0),
27        }
28    }
29}
30
31/// Statistics about some query
32#[derive(Default, Clone)]
33pub(crate) struct QueryStatBucket {
34    pub data: Arc<Mutex<QueryStatBucketData>>,
35}
36
37pub(crate) struct QueryStatGuard {
38    pub bucket: QueryStatBucket,
39    pub since: tinymist_std::time::Instant,
40}
41
42impl Drop for QueryStatGuard {
43    fn drop(&mut self) {
44        let elapsed = self.since.elapsed();
45        let mut data = self.bucket.data.lock();
46        data.query += 1;
47        data.total += elapsed;
48        data.min = data.min.min(elapsed);
49        data.max = data.max.max(elapsed);
50    }
51}
52
53impl QueryStatGuard {
54    pub(crate) fn miss(&self) {
55        let mut data = self.bucket.data.lock();
56        data.missing += 1;
57    }
58}
59
60/// Statistics about the analyzers
61#[derive(Default)]
62pub struct AnalysisStats {
63    pub(crate) query_stats: FxDashMap<FileId, FxDashMap<&'static str, QueryStatBucket>>,
64}
65
66impl AnalysisStats {
67    /// Report the statistics of the analysis.
68    pub fn report(&self) -> String {
69        let stats = &self.query_stats;
70        let mut data = Vec::new();
71        for refs in stats.iter() {
72            let id = refs.key();
73            let queries = refs.value();
74            for refs2 in queries.iter() {
75                let query = refs2.key();
76                let bucket = refs2.value().data.lock().clone();
77                let name = format!("{id:?}:{query}").replace('\\', "/");
78                data.push((name, bucket));
79            }
80        }
81
82        // sort by query duration
83        data.sort_by(|x, y| y.1.max.cmp(&x.1.max));
84
85        // format to html
86
87        let mut html = String::new();
88        html.push_str(r#"<div>
89<style>
90table.analysis-stats { width: 100%; border-collapse: collapse; }
91table.analysis-stats th, table.analysis-stats td { border: 1px solid black; padding: 8px; text-align: center; }
92table.analysis-stats th.name-column, table.analysis-stats td.name-column { text-align: left; }
93table.analysis-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0.8); }
94@media (prefers-color-scheme: dark) {
95    table.analysis-stats tr:nth-child(odd) { background-color: rgba(50, 50, 50, 0.8); }
96}
97</style>
98<table class="analysis-stats"><tr><th class="query-column">Query</th><th>Count</th><th>Missing</th><th>Total</th><th>Min</th><th>Max</th></tr>"#);
99
100        for (name, bucket) in data {
101            html.push_str("<tr>");
102            html.push_str(&format!(r#"<td class="query-column">{name}</td>"#));
103            html.push_str(&format!("<td>{}</td>", bucket.query));
104            html.push_str(&format!("<td>{}</td>", bucket.missing));
105            html.push_str(&format!("<td>{:?}</td>", bucket.total));
106            html.push_str(&format!("<td>{:?}</td>", bucket.min));
107            html.push_str(&format!("<td>{:?}</td>", bucket.max));
108            html.push_str("</tr>");
109        }
110        html.push_str("</table>");
111        html.push_str("</div>");
112
113        html
114    }
115}