This commit is contained in:
David Lemarier
2021-05-18 14:14:44 -04:00
parent 9e489fd04f
commit 1e90d133a5
4 changed files with 300 additions and 296 deletions

View File

@@ -5,221 +5,222 @@
use anyhow::Result;
use serde::Serialize;
use serde_json::Value;
use utils::root_path;
use std::{
collections::HashMap,
env, fs,
path::Path,
process::{Command, Stdio},
collections::HashMap,
env, fs,
path::Path,
process::{Command, Stdio},
};
use utils::root_path;
mod utils;
fn read_json(filename: &str) -> Result<Value> {
let f = fs::File::open(filename)?;
Ok(serde_json::from_reader(f)?)
let f = fs::File::open(filename)?;
Ok(serde_json::from_reader(f)?)
}
fn write_json(filename: &str, value: &Value) -> Result<()> {
let f = fs::File::create(filename)?;
serde_json::to_writer(f, value)?;
Ok(())
let f = fs::File::create(filename)?;
serde_json::to_writer(f, value)?;
Ok(())
}
/// The list of the examples of the benchmark name, arguments and return code
const EXEC_TIME_BENCHMARKS: &[(&str, &str, Option<i32>)] = &[
("hello_world", "hello_world/out/startup-electron-linux-x64/startup-electron", None)
];
const EXEC_TIME_BENCHMARKS: &[(&str, &str, Option<i32>)] = &[(
"hello_world",
"hello_world/out/startup-electron-linux-x64/startup-electron",
None,
)];
fn run_strace_benchmarks(new_data: &mut BenchResult) -> Result<()> {
use std::io::Read;
use std::io::Read;
let mut thread_count = HashMap::<String, u64>::new();
let mut syscall_count = HashMap::<String, u64>::new();
let mut thread_count = HashMap::<String, u64>::new();
let mut syscall_count = HashMap::<String, u64>::new();
for (name, example_exe, _) in EXEC_TIME_BENCHMARKS {
let mut file = tempfile::NamedTempFile::new()?;
for (name, example_exe, _) in EXEC_TIME_BENCHMARKS {
let mut file = tempfile::NamedTempFile::new()?;
Command::new("strace")
.args(&[
"-c",
"-f",
"-o",
file.path().to_str().unwrap(),
utils::root_path().join(example_exe).to_str().unwrap(),
])
.stdout(Stdio::inherit())
.spawn()?
.wait()?;
Command::new("strace")
.args(&[
"-c",
"-f",
"-o",
file.path().to_str().unwrap(),
utils::root_path().join(example_exe).to_str().unwrap(),
])
.stdout(Stdio::inherit())
.spawn()?
.wait()?;
let mut output = String::new();
file.as_file_mut().read_to_string(&mut output)?;
let mut output = String::new();
file.as_file_mut().read_to_string(&mut output)?;
let strace_result = utils::parse_strace_output(&output);
let clone = strace_result.get("clone").map(|d| d.calls).unwrap_or(0) + 1;
let total = strace_result.get("total").unwrap().calls;
thread_count.insert(name.to_string(), clone);
syscall_count.insert(name.to_string(), total);
}
let strace_result = utils::parse_strace_output(&output);
let clone = strace_result.get("clone").map(|d| d.calls).unwrap_or(0) + 1;
let total = strace_result.get("total").unwrap().calls;
thread_count.insert(name.to_string(), clone);
syscall_count.insert(name.to_string(), total);
}
new_data.thread_count = thread_count;
new_data.syscall_count = syscall_count;
new_data.thread_count = thread_count;
new_data.syscall_count = syscall_count;
Ok(())
Ok(())
}
fn run_max_mem_benchmark() -> Result<HashMap<String, u64>> {
let mut results = HashMap::<String, u64>::new();
let mut results = HashMap::<String, u64>::new();
for (name, example_exe, return_code) in EXEC_TIME_BENCHMARKS {
let proc = Command::new("time")
.args(&["-v", utils::root_path().join(example_exe).to_str().unwrap()])
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()?;
for (name, example_exe, return_code) in EXEC_TIME_BENCHMARKS {
let proc = Command::new("time")
.args(&["-v", utils::root_path().join(example_exe).to_str().unwrap()])
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()?;
let proc_result = proc.wait_with_output()?;
if let Some(code) = return_code {
assert_eq!(proc_result.status.code().unwrap(), *code);
let proc_result = proc.wait_with_output()?;
if let Some(code) = return_code {
assert_eq!(proc_result.status.code().unwrap(), *code);
}
let out = String::from_utf8(proc_result.stderr)?;
results.insert(name.to_string(), utils::parse_max_mem(&out).unwrap());
}
let out = String::from_utf8(proc_result.stderr)?;
results.insert(name.to_string(), utils::parse_max_mem(&out).unwrap());
}
Ok(results)
Ok(results)
}
fn get_binary_sizes() -> Result<HashMap<String, u64>> {
let mut sizes = HashMap::<String, u64>::new();
// add size for all EXEC_TIME_BENCHMARKS
for (name, example_exe, _) in EXEC_TIME_BENCHMARKS {
let meta = std::fs::metadata(example_exe).unwrap();
sizes.insert(name.to_string(), meta.len());
}
let mut sizes = HashMap::<String, u64>::new();
// add size for all EXEC_TIME_BENCHMARKS
for (name, example_exe, _) in EXEC_TIME_BENCHMARKS {
let meta = std::fs::metadata(example_exe).unwrap();
sizes.insert(name.to_string(), meta.len());
}
Ok(sizes)
Ok(sizes)
}
fn cargo_deps() -> usize {
let cargo_lock = utils::root_path().join("Cargo.lock");
let mut count = 0;
let file = std::fs::File::open(cargo_lock).unwrap();
use std::io::BufRead;
for line in std::io::BufReader::new(file).lines() {
if line.unwrap().starts_with("[[package]]") {
count += 1
let cargo_lock = utils::root_path().join("Cargo.lock");
let mut count = 0;
let file = std::fs::File::open(cargo_lock).unwrap();
use std::io::BufRead;
for line in std::io::BufReader::new(file).lines() {
if line.unwrap().starts_with("[[package]]") {
count += 1
}
}
}
println!("cargo_deps {}", count);
assert!(count > 10); // Sanity check.
count
println!("cargo_deps {}", count);
assert!(count > 10); // Sanity check.
count
}
const RESULT_KEYS: &[&str] = &["mean", "stddev", "user", "system", "min", "max"];
fn run_exec_time() -> Result<HashMap<String, HashMap<String, f64>>> {
let benchmark_file = root_path().join("hyperfine_results.json");
let benchmark_file = benchmark_file.to_str().unwrap();
let benchmark_file = root_path().join("hyperfine_results.json");
let benchmark_file = benchmark_file.to_str().unwrap();
let mut command = [
"hyperfine",
"--export-json",
benchmark_file,
"--warmup",
"3",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let mut command = [
"hyperfine",
"--export-json",
benchmark_file,
"--warmup",
"3",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
for (_, example_exe, _return_code) in EXEC_TIME_BENCHMARKS {
command.push(
utils::root_path()
.join(example_exe)
.to_str()
.unwrap()
.to_string(),
);
}
for (_, example_exe, _return_code) in EXEC_TIME_BENCHMARKS {
command.push(
utils::root_path()
.join(example_exe)
.to_str()
.unwrap()
.to_string(),
);
}
utils::run(&command.iter().map(|s| s.as_ref()).collect::<Vec<_>>());
utils::run(&command.iter().map(|s| s.as_ref()).collect::<Vec<_>>());
let mut results = HashMap::<String, HashMap<String, f64>>::new();
let hyperfine_results = read_json(benchmark_file)?;
for ((name, _, _), data) in EXEC_TIME_BENCHMARKS.iter().zip(
hyperfine_results
.as_object()
.unwrap()
.get("results")
.unwrap()
.as_array()
.unwrap(),
) {
let data = data.as_object().unwrap().clone();
results.insert(
name.to_string(),
data
.into_iter()
.filter(|(key, _)| RESULT_KEYS.contains(&key.as_str()))
.map(|(key, val)| (key, val.as_f64().unwrap()))
.collect(),
);
}
let mut results = HashMap::<String, HashMap<String, f64>>::new();
let hyperfine_results = read_json(benchmark_file)?;
for ((name, _, _), data) in EXEC_TIME_BENCHMARKS.iter().zip(
hyperfine_results
.as_object()
.unwrap()
.get("results")
.unwrap()
.as_array()
.unwrap(),
) {
let data = data.as_object().unwrap().clone();
results.insert(
name.to_string(),
data.into_iter()
.filter(|(key, _)| RESULT_KEYS.contains(&key.as_str()))
.map(|(key, val)| (key, val.as_f64().unwrap()))
.collect(),
);
}
Ok(results)
Ok(results)
}
#[derive(Default, Serialize, Debug)]
struct BenchResult {
created_at: String,
sha1: String,
created_at: String,
sha1: String,
exec_time: HashMap<String, HashMap<String, f64>>,
binary_size: HashMap<String, u64>,
max_memory: HashMap<String, u64>,
thread_count: HashMap<String, u64>,
syscall_count: HashMap<String, u64>,
cargo_deps: usize,
exec_time: HashMap<String, HashMap<String, f64>>,
binary_size: HashMap<String, u64>,
max_memory: HashMap<String, u64>,
thread_count: HashMap<String, u64>,
syscall_count: HashMap<String, u64>,
cargo_deps: usize,
}
fn main() -> Result<()> {
if !env::args().any(|s| s == "--bench") {
return Ok(());
}
if !env::args().any(|s| s == "--bench") {
return Ok(());
}
println!("Starting electron benchmark");
println!("Starting electron benchmark");
env::set_current_dir(&utils::root_path())?;
env::set_current_dir(&utils::root_path())?;
println!("{:?}", &utils::root_path());
println!("{:?}", &utils::root_path());
let mut new_data = BenchResult {
created_at: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
sha1: utils::run_collect(&["git", "rev-parse", "HEAD"])
.0
.trim()
.to_string(),
exec_time: run_exec_time()?,
binary_size: get_binary_sizes()?,
cargo_deps: cargo_deps(),
..Default::default()
};
let mut new_data = BenchResult {
created_at: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
sha1: utils::run_collect(&["git", "rev-parse", "HEAD"])
.0
.trim()
.to_string(),
exec_time: run_exec_time()?,
binary_size: get_binary_sizes()?,
cargo_deps: cargo_deps(),
..Default::default()
};
if cfg!(target_os = "linux") {
run_strace_benchmarks(&mut new_data)?;
new_data.max_memory = run_max_mem_benchmark()?;
}
if cfg!(target_os = "linux") {
run_strace_benchmarks(&mut new_data)?;
new_data.max_memory = run_max_mem_benchmark()?;
}
println!("===== <BENCHMARK RESULTS>");
serde_json::to_writer_pretty(std::io::stdout(), &new_data)?;
println!("\n===== </BENCHMARK RESULTS>");
println!("===== <BENCHMARK RESULTS>");
serde_json::to_writer_pretty(std::io::stdout(), &new_data)?;
println!("\n===== </BENCHMARK RESULTS>");
if let Some(filename) = root_path().join("bench.json").to_str() {
write_json(filename, &serde_json::to_value(&new_data)?)?;
} else {
eprintln!("Cannot write bench.json, path is invalid");
}
if let Some(filename) = root_path().join("bench.json").to_str() {
write_json(filename, &serde_json::to_value(&new_data)?)?;
} else {
eprintln!("Cannot write bench.json, path is invalid");
}
Ok(())
Ok(())
}

View File

@@ -7,70 +7,70 @@ use serde_json::Value;
use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf};
fn target_dir() -> PathBuf {
root_dir().join("target").join("release")
root_dir().join("target").join("release")
}
fn root_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
fn write_json(filename: &PathBuf, value: &Value) {
let f = File::create(filename).expect("Unable to create file");
serde_json::to_writer(f, value).expect("Unable to write json");
let f = File::create(filename).expect("Unable to create file");
serde_json::to_writer(f, value).expect("Unable to write json");
}
#[derive(Deserialize, Serialize, Debug, Clone)]
struct BenchResult {
created_at: String,
sha1: String,
created_at: String,
sha1: String,
exec_time: HashMap<String, HashMap<String, f64>>,
binary_size: HashMap<String, u64>,
max_memory: HashMap<String, u64>,
thread_count: HashMap<String, u64>,
syscall_count: HashMap<String, u64>,
cargo_deps: usize,
exec_time: HashMap<String, HashMap<String, f64>>,
binary_size: HashMap<String, u64>,
max_memory: HashMap<String, u64>,
thread_count: HashMap<String, u64>,
syscall_count: HashMap<String, u64>,
cargo_deps: usize,
}
fn main() {
let wry_data = root_dir().join("gh-pages").join("wry-data.json");
let wry_recent = root_dir().join("gh-pages").join("wry-recent.json");
let electron_data = root_dir().join("gh-pages").join("electron-data.json");
let electron_recent = root_dir().join("gh-pages").join("electron-recent.json");
// current data
let current_data_buffer = BufReader::new(
File::open(target_dir().join("bench.json")).expect("Unable to read current data file"),
);
let current_data: BenchResult =
serde_json::from_reader(current_data_buffer).expect("Unable to read current data buffer");
// current data
let current_data_buffer = BufReader::new(
File::open(target_dir().join("bench.json")).expect("Unable to read current data file"),
);
let current_data: BenchResult =
serde_json::from_reader(current_data_buffer).expect("Unable to read current data buffer");
// all data's
let all_data_buffer =
BufReader::new(File::open(&wry_data).expect("Unable to read all data file"));
let mut all_data: Vec<BenchResult> =
serde_json::from_reader(all_data_buffer).expect("Unable to read all data buffer");
// all data's
let all_data_buffer =
BufReader::new(File::open(&electron_data).expect("Unable to read all data file"));
let mut all_data: Vec<BenchResult> =
serde_json::from_reader(all_data_buffer).expect("Unable to read all data buffer");
// add current data to alls data
all_data.push(current_data);
// add current data to alls data
all_data.push(current_data);
// use only latest 20 elements from alls data
let recent: Vec<BenchResult>;
if all_data.len() > 20 {
recent = all_data[all_data.len() - 20..].to_vec();
} else {
recent = all_data.clone();
}
// use only latest 20 elements from alls data
let recent: Vec<BenchResult>;
if all_data.len() > 20 {
recent = all_data[all_data.len() - 20..].to_vec();
} else {
recent = all_data.clone();
}
write_json(
&wry_data,
&serde_json::to_value(&all_data).expect("Unable to build final json (alls)"),
);
write_json(
&wry_recent,
&serde_json::to_value(&recent).expect("Unable to build final json (recent)"),
);
write_json(
&electron_data,
&serde_json::to_value(&all_data).expect("Unable to build final json (alls)"),
);
write_json(
&electron_recent,
&serde_json::to_value(&recent).expect("Unable to build final json (recent)"),
);
}

View File

@@ -4,123 +4,123 @@
use serde::Serialize;
use std::{
collections::HashMap,
path::PathBuf,
process::{Command, Output, Stdio},
collections::HashMap,
path::PathBuf,
process::{Command, Output, Stdio},
};
pub fn root_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
pub fn run_collect(cmd: &[&str]) -> (String, String) {
let mut process_builder = Command::new(cmd[0]);
process_builder
.args(&cmd[1..])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let prog = process_builder.spawn().expect("failed to spawn script");
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 !status.success() {
eprintln!("stdout: <<<{}>>>", stdout);
eprintln!("stderr: <<<{}>>>", stderr);
panic!("Unexpected exit code: {:?}", status.code());
}
(stdout, stderr)
let mut process_builder = Command::new(cmd[0]);
process_builder
.args(&cmd[1..])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let prog = process_builder.spawn().expect("failed to spawn script");
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 !status.success() {
eprintln!("stdout: <<<{}>>>", stdout);
eprintln!("stderr: <<<{}>>>", stderr);
panic!("Unexpected exit code: {:?}", status.code());
}
(stdout, stderr)
}
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);
// 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
None
}
#[derive(Debug, Clone, Serialize)]
pub struct StraceOutput {
pub percent_time: f64,
pub seconds: f64,
pub usecs_per_call: Option<u64>,
pub calls: u64,
pub errors: u64,
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();
let mut summary = HashMap::new();
let mut lines = output
.lines()
.filter(|line| !line.is_empty() && !line.contains("detached ..."));
let count = lines.clone().count();
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..=6).contains(&len) {
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()
},
},
);
if count < 4 {
return summary;
}
}
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(),
},
);
let total_line = lines.next_back().unwrap();
lines.next_back(); // Drop separator
let data_lines = lines.skip(2);
summary
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..=6).contains(&len) {
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 run(cmd: &[&str]) {
let mut process_builder = Command::new(cmd[0]);
process_builder.args(&cmd[1..]).stdin(Stdio::piped());
let mut prog = process_builder.spawn().expect("failed to spawn script");
let status = prog.wait().expect("failed to wait on child");
if !status.success() {
panic!("Unexpected exit code: {:?}", status.code());
}
let mut process_builder = Command::new(cmd[0]);
process_builder.args(&cmd[1..]).stdin(Stdio::piped());
let mut prog = process_builder.spawn().expect("failed to spawn script");
let status = prog.wait().expect("failed to wait on child");
if !status.success() {
panic!("Unexpected exit code: {:?}", status.code());
}
}

View File

@@ -14,7 +14,10 @@ const createWindow = () => {
height: 600,
});
mainWindow.webContents.on("dom-ready", () => {
app.quit();
// without interval it panic?
setInterval(() => {
app.quit();
}, 1);
});
mainWindow.loadFile(path.join(__dirname, "index.html"));
};