0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-09 21:57:40 -04:00

refactor(bench): Allocate IDs for benches (#14757)

This commit is contained in:
Nayeem Rahman 2022-05-30 18:58:44 +01:00 committed by Bartek Iwańczuk
parent beef5a49c1
commit 58f8edc833
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750
6 changed files with 261 additions and 215 deletions

1
Cargo.lock generated
View file

@ -772,6 +772,7 @@ dependencies = [
"fwdansi", "fwdansi",
"http", "http",
"import_map", "import_map",
"indexmap",
"jsonc-parser", "jsonc-parser",
"libc", "libc",
"log", "log",

View file

@ -72,6 +72,7 @@ eszip = "=0.20.0"
fancy-regex = "=0.9.0" fancy-regex = "=0.9.0"
http = "=0.2.6" http = "=0.2.6"
import_map = "=0.9.0" import_map = "=0.9.0"
indexmap = "1.8.1"
jsonc-parser = { version = "=0.19.0", features = ["serde"] } jsonc-parser = { version = "=0.19.0", features = ["serde"] }
libc = "=0.2.126" libc = "=0.2.126"
log = { version = "=0.4.16", features = ["serde"] } log = { version = "=0.4.16", features = ["serde"] }

View file

@ -1,4 +1,6 @@
use crate::tools::bench::BenchDescription;
use crate::tools::bench::BenchEvent; use crate::tools::bench::BenchEvent;
use crate::tools::test::TestFilter;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op; use deno_core::op;
@ -8,22 +10,32 @@ use deno_core::OpState;
use deno_runtime::permissions::create_child_permissions; use deno_runtime::permissions::create_child_permissions;
use deno_runtime::permissions::ChildPermissionsArg; use deno_runtime::permissions::ChildPermissionsArg;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::time; use std::time;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use uuid::Uuid; use uuid::Uuid;
pub fn init(sender: UnboundedSender<BenchEvent>, unstable: bool) -> Extension { pub fn init(
sender: UnboundedSender<BenchEvent>,
filter: TestFilter,
unstable: bool,
) -> Extension {
Extension::builder() Extension::builder()
.ops(vec![ .ops(vec![
op_pledge_test_permissions::decl(), op_pledge_test_permissions::decl(),
op_restore_test_permissions::decl(), op_restore_test_permissions::decl(),
op_get_bench_origin::decl(), op_get_bench_origin::decl(),
op_register_bench::decl(),
op_dispatch_bench_event::decl(), op_dispatch_bench_event::decl(),
op_bench_now::decl(), op_bench_now::decl(),
op_bench_check_unstable::decl(), op_bench_check_unstable::decl(),
]) ])
.state(move |state| { .state(move |state| {
state.put(sender.clone()); state.put(sender.clone());
state.put(filter.clone());
state.put(Unstable(unstable)); state.put(Unstable(unstable));
Ok(()) Ok(())
}) })
@ -97,6 +109,44 @@ fn op_get_bench_origin(state: &mut OpState) -> String {
state.borrow::<ModuleSpecifier>().to_string() state.borrow::<ModuleSpecifier>().to_string()
} }
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BenchInfo {
name: String,
origin: String,
baseline: bool,
group: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BenchRegisterResult {
id: usize,
filtered_out: bool,
}
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
#[op]
fn op_register_bench(
state: &mut OpState,
info: BenchInfo,
) -> Result<BenchRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
let filter = state.borrow::<TestFilter>().clone();
let filtered_out = !filter.includes(&info.name);
let description = BenchDescription {
id,
name: info.name,
origin: info.origin,
baseline: info.baseline,
group: info.group,
};
let sender = state.borrow::<UnboundedSender<BenchEvent>>().clone();
sender.send(BenchEvent::Register(description)).ok();
Ok(BenchRegisterResult { id, filtered_out })
}
#[op] #[op]
fn op_dispatch_bench_event(state: &mut OpState, event: BenchEvent) { fn op_dispatch_bench_event(state: &mut OpState, event: BenchEvent) {
let sender = state.borrow::<UnboundedSender<BenchEvent>>().clone(); let sender = state.borrow::<UnboundedSender<BenchEvent>>().clone();

View file

@ -21,18 +21,21 @@ use crate::ops;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver; use crate::resolver::JsxResolver;
use crate::tools::test::format_test_error;
use crate::tools::test::TestFilter;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::error::JsError;
use deno_core::futures::future; use deno_core::futures::future;
use deno_core::futures::stream; use deno_core::futures::stream;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt; use deno_core::futures::StreamExt;
use deno_core::serde_json::json;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::ModuleKind; use deno_graph::ModuleKind;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use deno_runtime::tokio_util::run_basic; use deno_runtime::tokio_util::run_basic;
use indexmap::IndexMap;
use log::Level; use log::Level;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -48,12 +51,6 @@ struct BenchSpecifierOptions {
filter: Option<String>, filter: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BenchOutput {
Console(String),
}
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BenchPlan { pub struct BenchPlan {
@ -67,50 +64,36 @@ pub struct BenchPlan {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum BenchEvent { pub enum BenchEvent {
Plan(BenchPlan), Plan(BenchPlan),
Output(BenchOutput), Output(String),
Wait(BenchMetadata), Register(BenchDescription),
Result(String, BenchResult), Wait(usize),
Result(usize, BenchResult),
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum BenchResult { pub enum BenchResult {
Ok(BenchMeasurement), Ok(BenchStats),
Failed(BenchFailure), Failed(Box<JsError>),
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone)]
pub struct BenchReport { pub struct BenchReport {
pub total: usize, pub total: usize,
pub failed: usize, pub failed: usize,
pub failures: Vec<BenchFailure>, pub failures: Vec<(BenchDescription, Box<JsError>)>,
pub measurements: Vec<BenchMeasurement>, pub measurements: Vec<(BenchDescription, BenchStats)>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
pub struct BenchMetadata { pub struct BenchDescription {
pub id: usize,
pub name: String, pub name: String,
pub origin: String, pub origin: String,
pub baseline: bool, pub baseline: bool,
pub group: Option<String>, pub group: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchMeasurement {
pub name: String,
pub baseline: bool,
pub stats: BenchStats,
pub group: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchFailure {
pub name: String,
pub error: String,
pub baseline: bool,
pub group: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchStats { pub struct BenchStats {
pub n: u64, pub n: u64,
@ -142,9 +125,10 @@ pub trait BenchReporter {
fn report_group_summary(&mut self); fn report_group_summary(&mut self);
fn report_plan(&mut self, plan: &BenchPlan); fn report_plan(&mut self, plan: &BenchPlan);
fn report_end(&mut self, report: &BenchReport); fn report_end(&mut self, report: &BenchReport);
fn report_wait(&mut self, wait: &BenchMetadata); fn report_register(&mut self, desc: &BenchDescription);
fn report_output(&mut self, output: &BenchOutput); fn report_wait(&mut self, desc: &BenchDescription);
fn report_result(&mut self, result: &BenchResult); fn report_output(&mut self, output: &str);
fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult);
} }
struct ConsoleReporter { struct ConsoleReporter {
@ -152,8 +136,8 @@ struct ConsoleReporter {
show_output: bool, show_output: bool,
has_ungrouped: bool, has_ungrouped: bool,
group: Option<String>, group: Option<String>,
baseline: Option<BenchMeasurement>, baseline: bool,
group_measurements: Vec<BenchMeasurement>, group_measurements: Vec<(BenchDescription, BenchStats)>,
options: Option<mitata::reporter::Options>, options: Option<mitata::reporter::Options>,
} }
@ -163,7 +147,7 @@ impl ConsoleReporter {
show_output, show_output,
group: None, group: None,
options: None, options: None,
baseline: None, baseline: false,
name: String::new(), name: String::new(),
has_ungrouped: false, has_ungrouped: false,
group_measurements: Vec::new(), group_measurements: Vec::new(),
@ -181,7 +165,7 @@ impl BenchReporter for ConsoleReporter {
self.report_group_summary(); self.report_group_summary();
self.group = None; self.group = None;
self.baseline = None; self.baseline = false;
self.name = String::new(); self.name = String::new();
self.group_measurements.clear(); self.group_measurements.clear();
self.options = Some(mitata::reporter::Options::new( self.options = Some(mitata::reporter::Options::new(
@ -218,10 +202,12 @@ impl BenchReporter for ConsoleReporter {
); );
} }
fn report_wait(&mut self, wait: &BenchMetadata) { fn report_register(&mut self, _desc: &BenchDescription) {}
self.name = wait.name.clone();
match &wait.group { fn report_wait(&mut self, desc: &BenchDescription) {
self.name = desc.name.clone();
match &desc.group {
None => { None => {
self.has_ungrouped = true; self.has_ungrouped = true;
} }
@ -249,56 +235,52 @@ impl BenchReporter for ConsoleReporter {
} }
} }
fn report_output(&mut self, output: &BenchOutput) { fn report_output(&mut self, output: &str) {
if self.show_output { if self.show_output {
match output { print!("{} {}", colors::gray(format!("{}:", self.name)), output)
BenchOutput::Console(line) => {
print!("{} {}", colors::gray(format!("{}:", self.name)), line)
}
}
} }
} }
fn report_result(&mut self, result: &BenchResult) { fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult) {
let options = self.options.as_ref().unwrap(); let options = self.options.as_ref().unwrap();
match result { match result {
BenchResult::Ok(bench) => { BenchResult::Ok(stats) => {
let mut bench = bench.to_owned(); let mut desc = desc.clone();
if bench.baseline && self.baseline.is_none() { if desc.baseline && !self.baseline {
self.baseline = Some(bench.clone()); self.baseline = true;
} else { } else {
bench.baseline = false; desc.baseline = false;
} }
self.group_measurements.push(bench.clone());
println!( println!(
"{}", "{}",
mitata::reporter::benchmark( mitata::reporter::benchmark(
&bench.name, &desc.name,
&mitata::reporter::BenchmarkStats { &mitata::reporter::BenchmarkStats {
avg: bench.stats.avg, avg: stats.avg,
min: bench.stats.min, min: stats.min,
max: bench.stats.max, max: stats.max,
p75: bench.stats.p75, p75: stats.p75,
p99: bench.stats.p99, p99: stats.p99,
p995: bench.stats.p995, p995: stats.p995,
}, },
options options
) )
); );
self.group_measurements.push((desc, stats.clone()));
} }
BenchResult::Failed(failure) => { BenchResult::Failed(js_error) => {
println!( println!(
"{}", "{}",
mitata::reporter::benchmark_error( mitata::reporter::benchmark_error(
&failure.name, &desc.name,
&mitata::reporter::Error { &mitata::reporter::Error {
stack: None, stack: None,
message: failure.error.clone(), message: format_test_error(js_error),
}, },
options options
) )
@ -314,8 +296,7 @@ impl BenchReporter for ConsoleReporter {
}; };
if 2 <= self.group_measurements.len() if 2 <= self.group_measurements.len()
&& (self.group.is_some() && (self.group.is_some() || (self.group.is_none() && self.baseline))
|| (self.group.is_none() && self.baseline.is_some()))
{ {
println!( println!(
"\n{}", "\n{}",
@ -323,18 +304,18 @@ impl BenchReporter for ConsoleReporter {
&self &self
.group_measurements .group_measurements
.iter() .iter()
.map(|b| mitata::reporter::GroupBenchmark { .map(|(d, s)| mitata::reporter::GroupBenchmark {
name: b.name.clone(), name: d.name.clone(),
baseline: b.baseline, baseline: d.baseline,
group: b.group.as_deref().unwrap_or("").to_owned(), group: d.group.as_deref().unwrap_or("").to_owned(),
stats: mitata::reporter::BenchmarkStats { stats: mitata::reporter::BenchmarkStats {
avg: b.stats.avg, avg: s.avg,
min: b.stats.min, min: s.min,
max: b.stats.max, max: s.max,
p75: b.stats.p75, p75: s.p75,
p99: b.stats.p99, p99: s.p99,
p995: b.stats.p995, p995: s.p995,
}, },
}) })
.collect::<Vec<mitata::reporter::GroupBenchmark>>(), .collect::<Vec<mitata::reporter::GroupBenchmark>>(),
@ -343,7 +324,7 @@ impl BenchReporter for ConsoleReporter {
); );
} }
self.baseline = None; self.baseline = false;
self.group_measurements.clear(); self.group_measurements.clear();
} }
@ -380,11 +361,12 @@ async fn bench_specifier(
channel: UnboundedSender<BenchEvent>, channel: UnboundedSender<BenchEvent>,
options: BenchSpecifierOptions, options: BenchSpecifierOptions,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let filter = TestFilter::from_flag(&options.filter);
let mut worker = create_main_worker( let mut worker = create_main_worker(
&ps, &ps,
specifier.clone(), specifier.clone(),
permissions, permissions,
vec![ops::bench::init(channel.clone(), ps.flags.unstable)], vec![ops::bench::init(channel.clone(), filter, ps.flags.unstable)],
Default::default(), Default::default(),
); );
@ -413,12 +395,7 @@ async fn bench_specifier(
let bench_result = worker.js_runtime.execute_script( let bench_result = worker.js_runtime.execute_script(
&located_script_name!(), &located_script_name!(),
&format!( r#"Deno[Deno.internal].runBenchmarks()"#,
r#"Deno[Deno.internal].runBenchmarks({})"#,
json!({
"filter": options.filter,
}),
),
)?; )?;
worker.js_runtime.resolve_value(bench_result).await?; worker.js_runtime.resolve_value(bench_result).await?;
@ -462,6 +439,7 @@ async fn bench_specifiers(
let mut used_only = false; let mut used_only = false;
let mut report = BenchReport::new(); let mut report = BenchReport::new();
let mut reporter = create_reporter(log_level != Some(Level::Error)); let mut reporter = create_reporter(log_level != Some(Level::Error));
let mut benches = IndexMap::new();
while let Some(event) = receiver.recv().await { while let Some(event) = receiver.recv().await {
match event { match event {
@ -474,27 +452,32 @@ async fn bench_specifiers(
reporter.report_plan(&plan); reporter.report_plan(&plan);
} }
BenchEvent::Wait(metadata) => { BenchEvent::Register(desc) => {
reporter.report_wait(&metadata); reporter.report_register(&desc);
benches.insert(desc.id, desc);
}
BenchEvent::Wait(id) => {
reporter.report_wait(benches.get(&id).unwrap());
} }
BenchEvent::Output(output) => { BenchEvent::Output(output) => {
reporter.report_output(&output); reporter.report_output(&output);
} }
BenchEvent::Result(_origin, result) => { BenchEvent::Result(id, result) => {
match &result { let desc = benches.get(&id).unwrap();
BenchResult::Ok(bench) => { reporter.report_result(desc, &result);
report.measurements.push(bench.clone()); match result {
BenchResult::Ok(stats) => {
report.measurements.push((desc.clone(), stats));
} }
BenchResult::Failed(failure) => { BenchResult::Failed(failure) => {
report.failed += 1; report.failed += 1;
report.failures.push(failure.clone()); report.failures.push((desc.clone(), failure));
} }
}; };
reporter.report_result(&result);
} }
} }
} }

View file

@ -77,6 +77,58 @@ pub enum TestMode {
Both, Both,
} }
// TODO(nayeemrmn): This is only used for benches right now.
#[derive(Clone, Debug, Default)]
pub struct TestFilter {
pub substring: Option<String>,
pub regex: Option<Regex>,
pub include: Option<Vec<String>>,
pub exclude: Vec<String>,
}
impl TestFilter {
pub fn includes(&self, name: &String) -> bool {
if let Some(substring) = &self.substring {
if !name.contains(substring) {
return false;
}
}
if let Some(regex) = &self.regex {
if !regex.is_match(name) {
return false;
}
}
if let Some(include) = &self.include {
if !include.contains(name) {
return false;
}
}
if self.exclude.contains(name) {
return false;
}
true
}
pub fn from_flag(flag: &Option<String>) -> Self {
let mut substring = None;
let mut regex = None;
if let Some(flag) = flag {
if flag.starts_with('/') && flag.ends_with('/') {
let rs = flag.trim_start_matches('/').trim_end_matches('/');
regex =
Some(Regex::new(rs).unwrap_or_else(|_| Regex::new("$^").unwrap()));
} else {
substring = Some(flag.clone());
}
}
Self {
substring,
regex,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TestLocation { pub struct TestLocation {

View file

@ -4,11 +4,10 @@
((window) => { ((window) => {
const core = window.Deno.core; const core = window.Deno.core;
const { setExitHandler } = window.__bootstrap.os; const { setExitHandler } = window.__bootstrap.os;
const { Console, inspectArgs } = window.__bootstrap.console; const { Console } = window.__bootstrap.console;
const { serializePermissions } = window.__bootstrap.permissions; const { serializePermissions } = window.__bootstrap.permissions;
const { assert } = window.__bootstrap.infra; const { assert } = window.__bootstrap.infra;
const { const {
AggregateErrorPrototype,
ArrayFrom, ArrayFrom,
ArrayPrototypeFilter, ArrayPrototypeFilter,
ArrayPrototypeJoin, ArrayPrototypeJoin,
@ -537,8 +536,23 @@
}; };
} }
/**
* @typedef {{
* id: number,
* name: string,
* fn: BenchFunction
* origin: string,
* filteredOut: boolean,
* ignore: boolean,
* only: boolean.
* sanitizeExit: boolean,
* permissions: PermissionOptions,
* }} BenchDescription
*/
const tests = []; const tests = [];
const benches = []; /** @type {BenchDescription[]} */
const benchDescs = [];
// Main test function provided by Deno. // Main test function provided by Deno.
function test( function test(
@ -655,12 +669,11 @@
maybeFn, maybeFn,
) { ) {
core.opSync("op_bench_check_unstable"); core.opSync("op_bench_check_unstable");
let benchDef; let benchDesc;
const defaults = { const defaults = {
ignore: false, ignore: false,
baseline: false,
only: false, only: false,
sanitizeOps: true,
sanitizeResources: true,
sanitizeExit: true, sanitizeExit: true,
permissions: null, permissions: null,
}; };
@ -670,7 +683,7 @@
throw new TypeError("The bench name can't be empty"); throw new TypeError("The bench name can't be empty");
} }
if (typeof optionsOrFn === "function") { if (typeof optionsOrFn === "function") {
benchDef = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
} else { } else {
if (!maybeFn || typeof maybeFn !== "function") { if (!maybeFn || typeof maybeFn !== "function") {
throw new TypeError("Missing bench function"); throw new TypeError("Missing bench function");
@ -685,7 +698,7 @@
"Unexpected 'name' field in options, bench name is already provided as the first argument.", "Unexpected 'name' field in options, bench name is already provided as the first argument.",
); );
} }
benchDef = { benchDesc = {
...defaults, ...defaults,
...optionsOrFn, ...optionsOrFn,
fn: maybeFn, fn: maybeFn,
@ -702,7 +715,7 @@
if (maybeFn != undefined) { if (maybeFn != undefined) {
throw new TypeError("Unexpected third argument to Deno.bench()"); throw new TypeError("Unexpected third argument to Deno.bench()");
} }
benchDef = { benchDesc = {
...defaults, ...defaults,
fn: nameOrFnOrOptions, fn: nameOrFnOrOptions,
name: nameOrFnOrOptions.name, name: nameOrFnOrOptions.name,
@ -732,28 +745,18 @@
if (!name) { if (!name) {
throw new TypeError("The bench name can't be empty"); throw new TypeError("The bench name can't be empty");
} }
benchDef = { ...defaults, ...nameOrFnOrOptions, fn, name }; benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
} }
benchDesc.origin = getBenchOrigin();
const AsyncFunction = (async () => {}).constructor; const AsyncFunction = (async () => {}).constructor;
benchDef.async = AsyncFunction === benchDef.fn.constructor; benchDesc.async = AsyncFunction === benchDesc.fn.constructor;
ArrayPrototypePush(benches, benchDef); const { id, filteredOut } = core.opSync("op_register_bench", benchDesc);
} benchDesc.id = id;
benchDesc.filteredOut = filteredOut;
function formatError(error) { ArrayPrototypePush(benchDescs, benchDesc);
if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, error)) {
const message = error
.errors
.map((error) =>
inspectArgs([error]).replace(/^(?!\s*$)/gm, " ".repeat(4))
)
.join("\n");
return error.name + "\n" + message + error.stack;
}
return inspectArgs([error]);
} }
/** /**
@ -848,7 +851,8 @@
}; };
} }
async function benchMeasure(timeBudget, fn, step, sync) { async function benchMeasure(timeBudget, desc) {
const fn = desc.fn;
let n = 0; let n = 0;
let avg = 0; let avg = 0;
let wavg = 0; let wavg = 0;
@ -859,11 +863,10 @@
// warmup step // warmup step
let c = 0; let c = 0;
step.warmup = true;
let iterations = 20; let iterations = 20;
let budget = 10 * 1e6; let budget = 10 * 1e6;
if (sync) { if (!desc.async) {
while (budget > 0 || iterations-- > 0) { while (budget > 0 || iterations-- > 0) {
const t1 = benchNow(); const t1 = benchNow();
@ -890,13 +893,11 @@
wavg /= c; wavg /= c;
// measure step // measure step
step.warmup = false;
if (wavg > lowPrecisionThresholdInNs) { if (wavg > lowPrecisionThresholdInNs) {
let iterations = 10; let iterations = 10;
let budget = timeBudget * 1e6; let budget = timeBudget * 1e6;
if (sync) { if (!desc.async) {
while (budget > 0 || iterations-- > 0) { while (budget > 0 || iterations-- > 0) {
const t1 = benchNow(); const t1 = benchNow();
@ -906,7 +907,7 @@
n++; n++;
avg += iterationTime; avg += iterationTime;
budget -= iterationTime; budget -= iterationTime;
all.push(iterationTime); ArrayPrototypePush(all, iterationTime);
if (iterationTime < min) min = iterationTime; if (iterationTime < min) min = iterationTime;
if (iterationTime > max) max = iterationTime; if (iterationTime > max) max = iterationTime;
} }
@ -920,7 +921,7 @@
n++; n++;
avg += iterationTime; avg += iterationTime;
budget -= iterationTime; budget -= iterationTime;
all.push(iterationTime); ArrayPrototypePush(all, iterationTime);
if (iterationTime < min) min = iterationTime; if (iterationTime < min) min = iterationTime;
if (iterationTime > max) max = iterationTime; if (iterationTime > max) max = iterationTime;
} }
@ -929,7 +930,7 @@
let iterations = 10; let iterations = 10;
let budget = timeBudget * 1e6; let budget = timeBudget * 1e6;
if (sync) { if (!desc.async) {
while (budget > 0 || iterations-- > 0) { while (budget > 0 || iterations-- > 0) {
const t1 = benchNow(); const t1 = benchNow();
for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn();
@ -937,7 +938,7 @@
n++; n++;
avg += iterationTime; avg += iterationTime;
all.push(iterationTime); ArrayPrototypePush(all, iterationTime);
if (iterationTime < min) min = iterationTime; if (iterationTime < min) min = iterationTime;
if (iterationTime > max) max = iterationTime; if (iterationTime > max) max = iterationTime;
budget -= iterationTime * lowPrecisionThresholdInNs; budget -= iterationTime * lowPrecisionThresholdInNs;
@ -962,21 +963,15 @@
return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all);
} }
async function runBench(bench) { async function runBench(desc) {
const step = new BenchStep({
name: bench.name,
sanitizeExit: bench.sanitizeExit,
warmup: false,
});
let token = null; let token = null;
try { try {
if (bench.permissions) { if (desc.permissions) {
token = pledgePermissions(bench.permissions); token = pledgePermissions(desc.permissions);
} }
if (bench.sanitizeExit) { if (desc.sanitizeExit) {
setExitHandler((exitCode) => { setExitHandler((exitCode) => {
assert( assert(
false, false,
@ -986,24 +981,31 @@
} }
const benchTimeInMs = 500; const benchTimeInMs = 500;
const fn = bench.fn.bind(null, step); const stats = await benchMeasure(benchTimeInMs, desc);
const stats = await benchMeasure(benchTimeInMs, fn, step, !bench.async);
return { ok: { stats, ...bench } }; return { ok: stats };
} catch (error) { } catch (error) {
return { failed: { ...bench, error: formatError(error) } }; return { failed: core.destructureError(error) };
} finally { } finally {
if (bench.sanitizeExit) setExitHandler(null); if (bench.sanitizeExit) setExitHandler(null);
if (token !== null) restorePermissions(token); if (token !== null) restorePermissions(token);
} }
} }
let origin = null;
function getTestOrigin() { function getTestOrigin() {
return core.opSync("op_get_test_origin"); if (origin == null) {
origin = core.opSync("op_get_test_origin");
}
return origin;
} }
function getBenchOrigin() { function getBenchOrigin() {
return core.opSync("op_get_bench_origin"); if (origin == null) {
origin = core.opSync("op_get_bench_origin");
}
return origin;
} }
function reportTestPlan(plan) { function reportTestPlan(plan) {
@ -1036,30 +1038,6 @@
}); });
} }
function reportBenchPlan(plan) {
core.opSync("op_dispatch_bench_event", {
plan,
});
}
function reportBenchConsoleOutput(console) {
core.opSync("op_dispatch_bench_event", {
output: { console },
});
}
function reportBenchWait(description) {
core.opSync("op_dispatch_bench_event", {
wait: description,
});
}
function reportBenchResult(origin, result) {
core.opSync("op_dispatch_bench_event", {
result: [origin, result],
});
}
function benchNow() { function benchNow() {
return core.opSync("op_bench_now"); return core.opSync("op_bench_now");
} }
@ -1120,50 +1098,52 @@
} }
} }
async function runBenchmarks({ async function runBenchmarks() {
filter = null,
} = {}) {
core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask);
const origin = getBenchOrigin(); const origin = getBenchOrigin();
const originalConsole = globalThis.console; const originalConsole = globalThis.console;
globalThis.console = new Console(reportBenchConsoleOutput); globalThis.console = new Console((s) => {
core.opSync("op_dispatch_bench_event", { output: s });
});
const only = ArrayPrototypeFilter(benches, (bench) => bench.only); const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only);
const filtered = ArrayPrototypeFilter( const filtered = ArrayPrototypeFilter(
only.length > 0 ? only : benches, only.length > 0 ? only : benchDescs,
createTestFilter(filter), (desc) => !desc.filteredOut && !desc.ignore,
); );
let groups = new Set(); let groups = new Set();
const benchmarks = ArrayPrototypeFilter(filtered, (bench) => !bench.ignore);
// make sure ungrouped benchmarks are placed above grouped // make sure ungrouped benchmarks are placed above grouped
groups.add(undefined); groups.add(undefined);
for (const bench of benchmarks) { for (const desc of filtered) {
bench.group ||= undefined; desc.group ||= undefined;
groups.add(bench.group); groups.add(desc.group);
} }
groups = ArrayFrom(groups); groups = ArrayFrom(groups);
ArrayPrototypeSort( ArrayPrototypeSort(
benchmarks, filtered,
(a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group),
); );
reportBenchPlan({ core.opSync("op_dispatch_bench_event", {
plan: {
origin, origin,
total: benchmarks.length, total: filtered.length,
usedOnly: only.length > 0, usedOnly: only.length > 0,
names: ArrayPrototypeMap(benchmarks, (bench) => bench.name), names: ArrayPrototypeMap(filtered, (desc) => desc.name),
},
}); });
for (const bench of benchmarks) { for (const desc of filtered) {
bench.baseline = !!bench.baseline; desc.baseline = !!desc.baseline;
reportBenchWait({ origin, ...bench }); core.opSync("op_dispatch_bench_event", { wait: desc.id });
reportBenchResult(origin, await runBench(bench)); core.opSync("op_dispatch_bench_event", {
result: [desc.id, await runBench(desc)],
});
} }
globalThis.console = originalConsole; globalThis.console = originalConsole;
@ -1364,27 +1344,6 @@
} }
} }
/**
* @typedef {{
* name: string;
* sanitizeExit: boolean,
* warmup: boolean,
* }} BenchStepParams
*/
class BenchStep {
/** @type {BenchStepParams} */
#params;
/** @param params {BenchStepParams} */
constructor(params) {
this.#params = params;
}
get name() {
return this.#params.name;
}
}
/** @param parentStep {TestStep} */ /** @param parentStep {TestStep} */
function createTestContext(parentStep) { function createTestContext(parentStep) {
return { return {