diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 792ed8775b3970..ee8ec2563af2e5 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -54,6 +54,7 @@ pub struct FileFlags { pub struct BenchFlags { pub files: FileFlags, pub filter: Option, + pub json: bool, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -717,6 +718,12 @@ fn clap_root(version: &str) -> Command { fn bench_subcommand<'a>() -> Command<'a> { runtime_args(Command::new("bench"), true, false) .trailing_var_arg(true) + .arg( + Arg::new("json") + .long("json") + .help("UNSTABLE: Output benchmark result in JSON format") + .takes_value(false), + ) .arg( Arg::new("ignore") .long("ignore") @@ -2325,6 +2332,8 @@ fn bench_parse(flags: &mut Flags, matches: &clap::ArgMatches) { // interactive prompts, unless done by user code flags.no_prompt = true; + let json = matches.is_present("json"); + let ignore = match matches.values_of("ignore") { Some(f) => f.map(PathBuf::from).collect(), None => vec![], @@ -2359,6 +2368,7 @@ fn bench_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.subcommand = DenoSubcommand::Bench(BenchFlags { files: FileFlags { include, ignore }, filter, + json, }); } @@ -6535,6 +6545,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "bench", + "--json", "--unstable", "--filter", "- foo", @@ -6552,6 +6563,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Bench(BenchFlags { filter: Some("- foo".to_string()), + json: true, files: FileFlags { include: vec![PathBuf::from("dir1/"), PathBuf::from("dir2/")], ignore: vec![], @@ -6576,6 +6588,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Bench(BenchFlags { filter: None, + json: false, files: FileFlags { include: vec![], ignore: vec![], diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 95dfa45353f83c..da36c70717042f 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -105,6 +105,7 @@ impl CacheSetting { pub struct BenchOptions { pub files: FilesConfig, pub filter: Option, + pub json: bool, } impl BenchOptions { @@ -119,6 +120,7 @@ impl BenchOptions { Some(bench_flags.files), ), filter: bench_flags.filter, + json: bench_flags.json, }) } } diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs index be0d9ba6cbb59d..447cb1fcbc031e 100644 --- a/cli/tools/bench.rs +++ b/cli/tools/bench.rs @@ -4,6 +4,7 @@ use crate::args::BenchOptions; use crate::args::CliOptions; use crate::args::TypeCheckMode; use crate::colors; +use crate::display::write_json_to_stdout; use crate::graph_util::graph_valid_with_cli_options; use crate::ops; use crate::proc_state::ProcState; @@ -13,6 +14,7 @@ use crate::util::file_watcher; use crate::util::file_watcher::ResolutionResult; use crate::util::fs::collect_specifiers; use crate::util::path::is_supported_ext; +use crate::version::get_user_agent; use crate::worker::create_main_worker_for_test_or_bench; use deno_core::error::generic_error; @@ -41,6 +43,7 @@ use tokio::sync::mpsc::UnboundedSender; #[derive(Debug, Clone)] struct BenchSpecifierOptions { filter: TestFilter, + json: bool, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize)] @@ -62,7 +65,7 @@ pub enum BenchEvent { Result(usize, BenchResult), } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum BenchResult { Ok(BenchStats), @@ -109,7 +112,13 @@ impl BenchReport { } } -fn create_reporter(show_output: bool) -> Box { +fn create_reporter( + show_output: bool, + json: bool, +) -> Box { + if json { + return Box::new(JsonReporter::new()); + } Box::new(ConsoleReporter::new(show_output)) } @@ -123,6 +132,74 @@ pub trait BenchReporter { fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult); } +#[derive(Debug, Serialize)] +struct JsonReporterResult { + runtime: String, + cpu: String, + origin: String, + group: Option, + name: String, + baseline: bool, + result: BenchResult, +} + +impl JsonReporterResult { + fn new( + origin: String, + group: Option, + name: String, + baseline: bool, + result: BenchResult, + ) -> Self { + Self { + runtime: format!("{} {}", get_user_agent(), env!("TARGET")), + cpu: mitata::cpu::name(), + origin, + group, + name, + baseline, + result, + } + } +} + +#[derive(Debug, Serialize)] +struct JsonReporter(Vec); +impl JsonReporter { + fn new() -> Self { + Self(vec![]) + } +} + +impl BenchReporter for JsonReporter { + fn report_group_summary(&mut self) {} + #[cold] + fn report_plan(&mut self, _plan: &BenchPlan) {} + + fn report_end(&mut self, _report: &BenchReport) { + match write_json_to_stdout(self) { + Ok(_) => (), + Err(e) => println!("{e}"), + } + } + + fn report_register(&mut self, _desc: &BenchDescription) {} + + fn report_wait(&mut self, _desc: &BenchDescription) {} + + fn report_output(&mut self, _output: &str) {} + + fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult) { + self.0.push(JsonReporterResult::new( + desc.origin.clone(), + desc.group.clone(), + desc.name.clone(), + desc.baseline, + result.clone(), + )); + } +} + struct ConsoleReporter { name: String, show_output: bool, @@ -376,12 +453,14 @@ async fn bench_specifiers( let (sender, mut receiver) = unbounded_channel::(); + let option_for_handles = options.clone(); + let join_handles = specifiers.into_iter().map(move |specifier| { let ps = ps.clone(); let permissions = permissions.clone(); let specifier = specifier; let sender = sender.clone(); - let options = options.clone(); + let options = option_for_handles.clone(); tokio::task::spawn_blocking(move || { let future = bench_specifier(ps, permissions, specifier, sender, options); @@ -398,7 +477,8 @@ async fn bench_specifiers( tokio::task::spawn(async move { let mut used_only = false; 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), options.json); let mut benches = IndexMap::new(); while let Some(event) = receiver.recv().await { @@ -509,6 +589,7 @@ pub async fn run_benchmarks( specifiers, BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_options.filter), + json: bench_options.json, }, ) .await?; @@ -658,6 +739,7 @@ pub async fn run_benchmarks_with_watch( specifiers, BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_options.filter), + json: bench_options.json, }, ) .await?;