diff options
author | Valentin Anger <syrupthinker@gryphno.de> | 2020-08-28 15:03:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-28 09:03:50 -0400 |
commit | 31f32ed8c4082d36ad2a4ea460366c0d57a5ddbc (patch) | |
tree | 2b60111cd53dd4034d1a92ee8caff89d164b7d2d /test_util/src/lib.rs | |
parent | 3d23208019a2c9faaf265ff13ad59f2d04f10079 (diff) |
Move benchmarks to Rust (#7134)
All benchmarks are done in Rust and can be invoked with
`cargo bench`.
Currently this has it's own "harness" that behaves like
`./tools/benchmark.py` did.
Because of this tests inside `cli/bench` are currently not run.
This should be switched to the language provided harness
once the `#[bench]` attribute has been stabilized.
Diffstat (limited to 'test_util/src/lib.rs')
-rw-r--r-- | test_util/src/lib.rs | 380 |
1 files changed, 340 insertions, 40 deletions
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 9b029c355..84e353a8b 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -10,6 +10,7 @@ use os_pipe::pipe; #[cfg(unix)] pub use pty; use regex::Regex; +use std::collections::HashMap; use std::env; use std::io::Read; use std::io::Write; @@ -57,10 +58,18 @@ pub fn root_path() -> PathBuf { PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/..")) } +pub fn prebuilt_path() -> PathBuf { + third_party_path().join("prebuilt") +} + pub fn tests_path() -> PathBuf { root_path().join("cli").join("tests") } +pub fn third_party_path() -> PathBuf { + root_path().join("third_party") +} + pub fn target_dir() -> PathBuf { let current_exe = std::env::current_exe().unwrap(); let target_dir = current_exe.parent().unwrap().parent().unwrap(); @@ -77,6 +86,24 @@ pub fn deno_exe_path() -> PathBuf { p } +pub fn prebuilt_tool_path(tool: &str) -> PathBuf { + let mut exe = tool.to_string(); + exe.push_str(if cfg!(windows) { ".exe" } else { "" }); + prebuilt_path().join(platform_dir_name()).join(exe) +} + +fn platform_dir_name() -> &'static str { + if cfg!(target_os = "linux") { + "linux64" + } else if cfg!(target_os = "macos") { + "mac" + } else if cfg!(target_os = "windows") { + "win" + } else { + unreachable!() + } +} + pub fn test_server_path() -> PathBuf { let mut p = target_dir().join("test_server"); if cfg!(windows) { @@ -578,6 +605,76 @@ pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> { STRIP_ANSI_RE.replace_all(s, "") } +pub fn run( + cmd: &[&str], + input: Option<&[&str]>, + envs: Option<Vec<(String, String)>>, + current_dir: Option<&str>, + expect_success: bool, +) { + let mut process_builder = Command::new(cmd[0]); + process_builder.args(&cmd[1..]).stdin(Stdio::piped()); + + if let Some(dir) = current_dir { + process_builder.current_dir(dir); + } + if let Some(envs) = envs { + process_builder.envs(envs); + } + let mut prog = process_builder.spawn().expect("failed to spawn script"); + if let Some(lines) = input { + let stdin = prog.stdin.as_mut().expect("failed to get stdin"); + stdin + .write_all(lines.join("\n").as_bytes()) + .expect("failed to write to stdin"); + } + let status = prog.wait().expect("failed to wait on child"); + if expect_success != status.success() { + panic!("Unexpected exit code: {:?}", status.code()); + } +} + +pub fn run_collect( + cmd: &[&str], + input: Option<&[&str]>, + envs: Option<Vec<(String, String)>>, + current_dir: Option<&str>, + expect_success: bool, +) -> (String, String) { + let mut process_builder = Command::new(cmd[0]); + process_builder + .args(&cmd[1..]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(dir) = current_dir { + process_builder.current_dir(dir); + } + if let Some(envs) = envs { + process_builder.envs(envs); + } + let mut prog = process_builder.spawn().expect("failed to spawn script"); + if let Some(lines) = input { + let stdin = prog.stdin.as_mut().expect("failed to get stdin"); + stdin + .write_all(lines.join("\n").as_bytes()) + .expect("failed to write to stdin"); + } + let Output { + stdout, + stderr, + status, + } = prog.wait_with_output().expect("failed to wait on child"); + let stdout = String::from_utf8(stdout).unwrap(); + let stderr = String::from_utf8(stderr).unwrap(); + if expect_success != status.success() { + eprintln!("stdout: <<<{}>>>", stdout); + eprintln!("stderr: <<<{}>>>", stderr); + panic!("Unexpected exit code: {:?}", status.code()); + } + (stdout, stderr) +} + pub fn run_and_collect_output( expect_success: bool, args: &str, @@ -855,45 +952,248 @@ pub fn test_pty(args: &str, output_path: &str, input: &[u8]) { } } -#[test] -fn test_wildcard_match() { - let fixtures = vec![ - ("foobarbaz", "foobarbaz", true), - ("[WILDCARD]", "foobarbaz", true), - ("foobar", "foobarbaz", false), - ("foo[WILDCARD]baz", "foobarbaz", true), - ("foo[WILDCARD]baz", "foobazbar", false), - ("foo[WILDCARD]baz[WILDCARD]qux", "foobarbazqatqux", true), - ("foo[WILDCARD]", "foobar", true), - ("foo[WILDCARD]baz[WILDCARD]", "foobarbazqat", true), - // check with different line endings - ("foo[WILDCARD]\nbaz[WILDCARD]\n", "foobar\nbazqat\n", true), - ( - "foo[WILDCARD]\nbaz[WILDCARD]\n", - "foobar\r\nbazqat\r\n", - true, - ), - ( - "foo[WILDCARD]\r\nbaz[WILDCARD]\n", - "foobar\nbazqat\r\n", - true, - ), - ( - "foo[WILDCARD]\r\nbaz[WILDCARD]\r\n", - "foobar\nbazqat\n", - true, - ), - ( - "foo[WILDCARD]\r\nbaz[WILDCARD]\r\n", - "foobar\r\nbazqat\r\n", - true, - ), - ]; - - // Iterate through the fixture lists, testing each one - for (pattern, string, expected) in fixtures { - let actual = wildcard_match(pattern, string); - dbg!(pattern, string, expected); - assert_eq!(actual, expected); +pub struct WrkOutput { + pub latency: f64, + pub requests: u64, +} + +pub fn parse_wrk_output(output: &str) -> WrkOutput { + lazy_static! { + static ref REQUESTS_RX: Regex = + Regex::new(r"Requests/sec:\s+(\d+)").unwrap(); + static ref LATENCY_RX: Regex = + Regex::new(r"\s+99%(?:\s+(\d+.\d+)([a-z]+))").unwrap(); + } + + let mut requests = None; + let mut latency = None; + + for line in output.lines() { + if requests == None { + if let Some(cap) = REQUESTS_RX.captures(line) { + requests = + Some(str::parse::<u64>(cap.get(1).unwrap().as_str()).unwrap()); + } + } + if latency == None { + if let Some(cap) = LATENCY_RX.captures(line) { + let time = cap.get(1).unwrap(); + let unit = cap.get(2).unwrap(); + + latency = Some( + str::parse::<f64>(time.as_str()).unwrap() + * match unit.as_str() { + "ms" => 1.0, + "us" => 0.001, + "s" => 1000.0, + _ => unreachable!(), + }, + ); + } + } + } + + WrkOutput { + requests: requests.unwrap(), + latency: latency.unwrap(), + } +} + +pub struct StraceOutput { + pub percent_time: f64, + pub seconds: f64, + pub usecs_per_call: Option<u64>, + pub calls: u64, + pub errors: u64, +} + +pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> { + let mut summary = HashMap::new(); + + // Filter out non-relevant lines. See the error log at + // https://github.com/denoland/deno/pull/3715/checks?check_run_id=397365887 + // This is checked in testdata/strace_summary2.out + let mut lines = output + .lines() + .filter(|line| !line.is_empty() && !line.contains("detached ...")); + let count = lines.clone().count(); + + if count < 4 { + return summary; + } + + let total_line = lines.next_back().unwrap(); + lines.next_back(); // Drop separator + let data_lines = lines.skip(2); + + for line in data_lines { + let syscall_fields = line.split_whitespace().collect::<Vec<_>>(); + let len = syscall_fields.len(); + let syscall_name = syscall_fields.last().unwrap(); + + if 5 <= len && len <= 6 { + summary.insert( + syscall_name.to_string(), + StraceOutput { + percent_time: str::parse::<f64>(syscall_fields[0]).unwrap(), + seconds: str::parse::<f64>(syscall_fields[1]).unwrap(), + usecs_per_call: Some(str::parse::<u64>(syscall_fields[2]).unwrap()), + calls: str::parse::<u64>(syscall_fields[3]).unwrap(), + errors: if syscall_fields.len() < 6 { + 0 + } else { + str::parse::<u64>(syscall_fields[4]).unwrap() + }, + }, + ); + } + } + + let total_fields = total_line.split_whitespace().collect::<Vec<_>>(); + summary.insert( + "total".to_string(), + StraceOutput { + percent_time: str::parse::<f64>(total_fields[0]).unwrap(), + seconds: str::parse::<f64>(total_fields[1]).unwrap(), + usecs_per_call: None, + calls: str::parse::<u64>(total_fields[2]).unwrap(), + errors: str::parse::<u64>(total_fields[3]).unwrap(), + }, + ); + + summary +} + +pub fn parse_max_mem(output: &str) -> Option<u64> { + // Takes the output from "time -v" as input and extracts the 'maximum + // resident set size' and returns it in bytes. + for line in output.lines() { + if line + .to_lowercase() + .contains("maximum resident set size (kbytes)") + { + let value = line.split(": ").nth(1).unwrap(); + return Some(str::parse::<u64>(value).unwrap() * 1024); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_wrk_output_1() { + const TEXT: &str = include_str!("./testdata/wrk1.txt"); + let wrk = parse_wrk_output(TEXT); + assert_eq!(wrk.requests, 1837); + assert!((wrk.latency - 6.25).abs() < f64::EPSILON); + } + + #[test] + fn parse_wrk_output_2() { + const TEXT: &str = include_str!("./testdata/wrk2.txt"); + let wrk = parse_wrk_output(TEXT); + assert_eq!(wrk.requests, 53435); + assert!((wrk.latency - 6.22).abs() < f64::EPSILON); + } + + #[test] + fn parse_wrk_output_3() { + const TEXT: &str = include_str!("./testdata/wrk3.txt"); + let wrk = parse_wrk_output(TEXT); + assert_eq!(wrk.requests, 96037); + assert!((wrk.latency - 6.36).abs() < f64::EPSILON); + } + + #[test] + fn strace_parse_1() { + const TEXT: &str = include_str!("./testdata/strace_summary.out"); + let strace = parse_strace_output(TEXT); + + // first syscall line + let munmap = strace.get("munmap").unwrap(); + assert_eq!(munmap.calls, 60); + assert_eq!(munmap.errors, 0); + + // line with errors + assert_eq!(strace.get("mkdir").unwrap().errors, 2); + + // last syscall line + let prlimit = strace.get("prlimit64").unwrap(); + assert_eq!(prlimit.calls, 2); + assert!((prlimit.percent_time - 0.0).abs() < f64::EPSILON); + + // summary line + assert_eq!(strace.get("total").unwrap().calls, 704); + assert_eq!(strace.get("total").unwrap().errors, 5); + } + + #[test] + fn strace_parse_2() { + const TEXT: &str = include_str!("./testdata/strace_summary2.out"); + let strace = parse_strace_output(TEXT); + + // first syscall line + let futex = strace.get("futex").unwrap(); + assert_eq!(futex.calls, 449); + assert_eq!(futex.errors, 94); + + // summary line + assert_eq!(strace.get("total").unwrap().calls, 821); + assert_eq!(strace.get("total").unwrap().errors, 107); + } + + #[test] + fn test_wildcard_match() { + let fixtures = vec![ + ("foobarbaz", "foobarbaz", true), + ("[WILDCARD]", "foobarbaz", true), + ("foobar", "foobarbaz", false), + ("foo[WILDCARD]baz", "foobarbaz", true), + ("foo[WILDCARD]baz", "foobazbar", false), + ("foo[WILDCARD]baz[WILDCARD]qux", "foobarbazqatqux", true), + ("foo[WILDCARD]", "foobar", true), + ("foo[WILDCARD]baz[WILDCARD]", "foobarbazqat", true), + // check with different line endings + ("foo[WILDCARD]\nbaz[WILDCARD]\n", "foobar\nbazqat\n", true), + ( + "foo[WILDCARD]\nbaz[WILDCARD]\n", + "foobar\r\nbazqat\r\n", + true, + ), + ( + "foo[WILDCARD]\r\nbaz[WILDCARD]\n", + "foobar\nbazqat\r\n", + true, + ), + ( + "foo[WILDCARD]\r\nbaz[WILDCARD]\r\n", + "foobar\nbazqat\n", + true, + ), + ( + "foo[WILDCARD]\r\nbaz[WILDCARD]\r\n", + "foobar\r\nbazqat\r\n", + true, + ), + ]; + + // Iterate through the fixture lists, testing each one + for (pattern, string, expected) in fixtures { + let actual = wildcard_match(pattern, string); + dbg!(pattern, string, expected); + assert_eq!(actual, expected); + } + } + + #[test] + fn max_mem_parse() { + const TEXT: &str = include_str!("./testdata/time.out"); + let size = parse_max_mem(TEXT); + + assert_eq!(size, Some(120380 * 1024)); } } |