tinymist_query/analysis/
stats.rs1use 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#[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#[derive(Default)]
62pub struct AnalysisStats {
63 pub(crate) query_stats: FxDashMap<FileId, FxDashMap<&'static str, QueryStatBucket>>,
64}
65
66impl AnalysisStats {
67 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 data.sort_by(|x, y| y.1.max.cmp(&x.1.max));
84
85 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}