aiken_project/
telemetry.rs

1use aiken_lang::{
2    expr::UntypedExpr,
3    test_framework::{BenchmarkResult, PropertyTestResult, TestResult, UnitTestResult},
4};
5pub use json::{Json, json_schema};
6use std::{
7    collections::BTreeMap,
8    fmt::Display,
9    io::{self, IsTerminal},
10    path::PathBuf,
11};
12pub use terminal::Terminal;
13use uplc::machine::cost_model::ExBudget;
14
15mod json;
16mod terminal;
17
18pub trait EventListener {
19    fn handle_event(&self, _event: Event) {}
20}
21
22pub enum Event {
23    StartingCompilation {
24        name: String,
25        version: String,
26        root: PathBuf,
27    },
28    BuildingDocumentation {
29        name: String,
30        version: String,
31        root: PathBuf,
32    },
33    GeneratingDocFiles {
34        output_path: PathBuf,
35    },
36    GeneratingBlueprint {
37        path: PathBuf,
38    },
39    DumpingUPLC {
40        path: PathBuf,
41    },
42    GeneratingUPLCFor {
43        name: String,
44        path: PathBuf,
45    },
46    CollectingTests {
47        matching_module: Option<String>,
48        matching_names: Vec<String>,
49    },
50    RunningTests,
51    RunningBenchmarks,
52    FinishedTests {
53        seed: u32,
54        coverage_mode: CoverageMode,
55        tests: Vec<TestResult<UntypedExpr, UntypedExpr>>,
56        plain_numbers: bool,
57    },
58    FinishedBenchmarks {
59        seed: u32,
60        benchmarks: Vec<TestResult<UntypedExpr, UntypedExpr>>,
61    },
62    WaitingForBuildDirLock,
63    ResolvingPackages {
64        name: String,
65    },
66    PackageResolveFallback {
67        name: String,
68    },
69    PackagesDownloaded {
70        start: tokio::time::Instant,
71        count: usize,
72        source: DownloadSource,
73    },
74    ResolvingVersions,
75}
76
77#[derive(Debug, Clone, Copy, Default)]
78pub enum CoverageMode {
79    RelativeToTests,
80    #[default]
81    RelativeToLabels,
82}
83
84impl CoverageMode {
85    pub fn parse(raw_str: &str) -> Result<Self, String> {
86        if raw_str == "relative-to-tests" {
87            return Ok(CoverageMode::RelativeToTests);
88        }
89
90        if raw_str == "relative-to-labels" {
91            return Ok(CoverageMode::RelativeToLabels);
92        }
93
94        Err(
95            "unexpected coverage mode: neither 'relative-to-tests' nor 'relative-to-labels'"
96                .to_string(),
97        )
98    }
99}
100
101impl Display for CoverageMode {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self {
104            CoverageMode::RelativeToTests => write!(f, "relative-to-tests"),
105            CoverageMode::RelativeToLabels => write!(f, "relative-to-labels"),
106        }
107    }
108}
109
110#[derive(Clone)]
111pub enum EventTarget {
112    Json(Json),
113    Terminal(Terminal),
114}
115
116impl Default for EventTarget {
117    fn default() -> Self {
118        if io::stdout().is_terminal() {
119            EventTarget::Terminal(Terminal)
120        } else {
121            EventTarget::Json(Json)
122        }
123    }
124}
125
126impl EventListener for EventTarget {
127    fn handle_event(&self, event: Event) {
128        match self {
129            EventTarget::Terminal(term) => term.handle_event(event),
130            EventTarget::Json(json) => json.handle_event(event),
131        }
132    }
133}
134
135pub enum DownloadSource {
136    Network,
137    Cache,
138}
139
140impl Display for DownloadSource {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            DownloadSource::Network => write!(f, "network"),
144            DownloadSource::Cache => write!(f, "cache"),
145        }
146    }
147}
148
149pub(crate) fn group_by_module(
150    results: &[TestResult<UntypedExpr, UntypedExpr>],
151) -> BTreeMap<String, Vec<&TestResult<UntypedExpr, UntypedExpr>>> {
152    let mut modules = BTreeMap::new();
153    for r in results {
154        let xs: &mut Vec<&TestResult<_, _>> = modules.entry(r.module().to_string()).or_default();
155        xs.push(r);
156    }
157    modules
158}
159
160pub(crate) fn find_max_execution_units<T>(xs: &[TestResult<T, T>]) -> (usize, usize, usize) {
161    fn max_execution_units(max_mem: i64, max_cpu: i64, cost: &ExBudget) -> (i64, i64) {
162        if cost.mem >= max_mem && cost.cpu >= max_cpu {
163            (cost.mem, cost.cpu)
164        } else if cost.mem > max_mem {
165            (cost.mem, max_cpu)
166        } else if cost.cpu > max_cpu {
167            (max_mem, cost.cpu)
168        } else {
169            (max_mem, max_cpu)
170        }
171    }
172
173    let (max_mem, max_cpu, max_iter) =
174        xs.iter()
175            .fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test {
176                TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
177                    (max_mem, max_cpu, std::cmp::max(max_iter, *iterations))
178                }
179                TestResult::UnitTestResult(UnitTestResult { spent_budget, .. }) => {
180                    let (max_mem, max_cpu) = max_execution_units(max_mem, max_cpu, spent_budget);
181                    (max_mem, max_cpu, max_iter)
182                }
183                TestResult::BenchmarkResult(BenchmarkResult { measures, .. }) => {
184                    let (mut max_mem, mut max_cpu) = (max_mem, max_cpu);
185                    for (_, measure) in measures {
186                        (max_mem, max_cpu) = max_execution_units(max_mem, max_cpu, measure);
187                    }
188                    (max_mem, max_cpu, max_iter)
189                }
190            });
191
192    (
193        max_mem.to_string().len(),
194        max_cpu.to_string().len(),
195        max_iter.to_string().len(),
196    )
197}