1#![forbid(unsafe_code)]
2
3use std::collections::BTreeMap;
29
30use boa_engine::{Context, Source};
31
32mod scaling;
33mod shape;
34mod sizes;
35mod table;
36
37pub use scaling::{
38 build, code_lines, deepest_fold, linked_runtime_bytes, linked_runtime_bytes_in,
39 program_with_components, program_with_depth, program_with_roots, program_with_signals,
40 program_without_components, runtime_js_bytes, survey, template_bytes, time_graph_passes,
41 Emitted, GraphTimes, FOREIGN_VIEW_PROGRAM, NULL_PROGRAM, SMALLEST_PROGRAM,
42 SWIFT_BYTES_PER_LINE, SWIFT_LARGEST_APP_JS, SWIFT_LARGEST_APP_LINES, SWIFT_NULL_PROGRAM_JS,
43 SWIFT_NULL_PROGRAM_LINES,
44};
45pub use shape::{benchmark_row, emitted_row, RowShape};
46pub use sizes::{bundle_sizes, compile, repository_path, try_compile, BundleSize};
47pub use table::{generated_section, END_MARKER, START_MARKER};
48
49pub const INSTRUMENT_JS: &str = include_str!("../js/instrument.js");
51
52pub const BENCHMARK_JS: &str = include_str!("../js/benchmark.js");
54
55pub const REORDER_JS: &str = include_str!("../js/reorder.js");
62
63pub use zdc_runtime::DOM_SHIM_JS;
69
70pub const ROW_ZD: &str = include_str!("../bench/row.zd");
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Measurement {
76 pub arm: String,
77 pub step: String,
78 pub fields: BTreeMap<String, i64>,
79}
80
81impl Measurement {
82 pub fn get(&self, key: &str) -> i64 {
85 self.fields.get(key).copied().unwrap_or(0)
86 }
87}
88
89pub struct Report(pub Vec<Measurement>);
91
92impl Report {
93 pub fn find(&self, arm: &str, step: &str) -> &Measurement {
94 self.0
95 .iter()
96 .find(|m| m.arm == arm && m.step == step)
97 .unwrap_or_else(|| panic!("no measurement for `{arm}` / `{step}`"))
98 }
99
100 pub fn arms(&self) -> Vec<&str> {
101 let mut out: Vec<&str> = Vec::new();
102 for measurement in &self.0 {
103 if !out.contains(&measurement.arm.as_str()) {
104 out.push(&measurement.arm);
105 }
106 }
107 out
108 }
109
110 pub fn steps(&self) -> Vec<&str> {
111 let mut out: Vec<&str> = Vec::new();
112 for measurement in &self.0 {
113 if !out.contains(&measurement.step.as_str()) {
114 out.push(&measurement.step);
115 }
116 }
117 out
118 }
119}
120
121fn flatten(source: &str) -> String {
124 source
125 .lines()
126 .filter(|line| !line.trim_start().starts_with("import "))
127 .map(|line| line.strip_prefix("export ").unwrap_or(line))
128 .collect::<Vec<_>>()
129 .join("\n")
130}
131
132fn measure(what: &str, script: &str) -> Report {
134 let mut context = Context::default();
135 let sources = [
136 ("dom shim", DOM_SHIM_JS.to_string()),
137 ("signal.js", flatten(zdc_runtime::SIGNAL_JS)),
138 ("dom.js", flatten(zdc_runtime::DOM_JS)),
139 ("markup.js", flatten(zdc_runtime::MARKUP_JS)),
140 ("list.js", flatten(zdc_runtime::LIST_JS)),
141 ("elements.js", flatten(zdc_runtime::ELEMENTS_JS)),
142 ("instrument.js", INSTRUMENT_JS.to_string()),
143 ];
144 for (name, source) in sources {
145 context
146 .eval(Source::from_bytes(source.as_bytes()))
147 .unwrap_or_else(|e| panic!("{name} failed to evaluate: {e}"));
148 }
149
150 let report = context
151 .eval(Source::from_bytes(script.as_bytes()))
152 .unwrap_or_else(|e| panic!("{what} failed: {e}"))
153 .to_string(&mut context)
154 .expect("a measuring script returns a string")
155 .to_std_string_escaped();
156
157 Report(parse(&report))
158}
159
160pub fn run() -> Report {
162 measure("the workload", BENCHMARK_JS)
163}
164
165pub fn run_reorder() -> Report {
172 measure("the reorder measurement", REORDER_JS)
173}
174
175fn parse(report: &str) -> Vec<Measurement> {
176 let mut out = Vec::new();
177 for line in report.lines() {
178 let Some(rest) = line.strip_prefix("RESULT\t") else {
179 continue;
180 };
181 let mut parts = rest.split('\t');
182 let arm = parts.next().expect("a result line names its arm");
183 let step = parts.next().expect("a result line names its step");
184 let fields = parts.next().expect("a result line carries fields");
185
186 let mut counts = BTreeMap::new();
187 for field in fields.split(',') {
188 let (key, value) = field
189 .split_once('=')
190 .unwrap_or_else(|| panic!("malformed field `{field}`"));
191 let value: i64 = value
192 .parse()
193 .unwrap_or_else(|e| panic!("`{field}` is not a number: {e}"));
194 counts.insert(key.to_string(), value);
195 }
196 out.push(Measurement {
197 arm: arm.to_string(),
198 step: step.to_string(),
199 fields: counts,
200 });
201 }
202 assert!(
203 !out.is_empty(),
204 "the workload produced no measurements — it probably did not run"
205 );
206 out
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn a_result_line_parses_into_counters() {
215 let parsed = parse("RESULT\tzd\tcreate\trows=2,cross.cloneNode=2\nnoise\n");
216 assert_eq!(parsed.len(), 1);
217 assert_eq!(parsed[0].arm, "zd");
218 assert_eq!(parsed[0].step, "create");
219 assert_eq!(parsed[0].get("cross.cloneNode"), 2);
220 assert_eq!(parsed[0].get("absent"), 0);
221 }
222
223 #[test]
224 fn flatten_strips_module_syntax_and_nothing_else() {
225 let flattened = flatten("import { a } from './b.js';\nexport function c() {}\n indented");
226 assert_eq!(flattened, "function c() {}\n indented");
227 }
228}