Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ tempfile = "3.23.0"
name = "scan_bench"
harness = false

[[bench]]
name = "cli_bench"
harness = false

[[bench]]
name = "go_bench"
harness = false
Expand All @@ -77,3 +81,8 @@ harness = false
[[bench]]
name = "shell_bench"
harness = false

[profile.release]
lto = "thin"
codegen-units = 1
strip = "symbols"
66 changes: 66 additions & 0 deletions benches/cli_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
mod common;

use std::{
path::Path,
process::{Command, ExitStatus, Stdio},
};

use criterion::{Criterion, criterion_group, criterion_main};

fn run_qr(config_dir: &Path, args: &[&str]) -> ExitStatus {
qr_command(config_dir, args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.unwrap()
}

fn qr_command(config_dir: &Path, args: &[&str]) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_qr"));
command
.args(args)
.env_clear()
.env("QR_CONFIG_DIR", config_dir)
.env("QR_STATS_ENABLED", "false");
command
}

fn cli_benchmarks(c: &mut Criterion) {
let config_tmp = tempfile::tempdir().unwrap();
c.bench_function("cli/config_path", |b| {
b.iter(|| {
assert!(run_qr(config_tmp.path(), &["config", "path"]).success());
});
});

let exact = common::go_fixture(1_000);
c.bench_function("cli/go_exact_1000", |b| {
b.iter(|| {
assert!(run_qr(&exact.config_dir, &["go", "service-0042", "--print-path"]).success());
});
});

let miss = common::go_fixture(5_000);
let miss_args = ["go", "zzzzzzzz", "--print-path"];
let preflight = qr_command(&miss.config_dir, &miss_args).output().unwrap();
assert_eq!(preflight.status.code(), Some(1));
assert!(
String::from_utf8_lossy(&preflight.stderr).contains("No project matching 'zzzzzzzz' found")
);
c.bench_function("cli/go_no_match_5000", |b| {
b.iter(|| {
assert_eq!(run_qr(&miss.config_dir, &miss_args).code(), Some(1));
});
});

#[cfg(unix)]
c.bench_function("cli/run_true", |b| {
b.iter(|| {
assert!(run_qr(config_tmp.path(), &["run", "--output", "true"]).success());
});
});
}

criterion_group!(benches, cli_benchmarks);
criterion_main!(benches);
108 changes: 89 additions & 19 deletions benches/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
#![allow(dead_code)]

use std::{
ffi::{OsStr, OsString},
fs,
path::PathBuf,
sync::{Mutex, OnceLock},
sync::{Mutex, MutexGuard, OnceLock},
};

use quick_runner::{
Expand All @@ -20,22 +21,46 @@ pub fn env_lock() -> &'static Mutex<()> {
LOCK.get_or_init(|| Mutex::new(()))
}

pub struct ScopedEnvVar {
_lock: MutexGuard<'static, ()>,
key: &'static str,
previous: Option<OsString>,
}

pub fn scoped_env_var(key: &'static str, value: &OsStr) -> ScopedEnvVar {
let lock = env_lock().lock().unwrap();
let previous = std::env::var_os(key);
unsafe {
std::env::set_var(key, value);
}
ScopedEnvVar {
_lock: lock,
key,
previous,
}
}

impl Drop for ScopedEnvVar {
fn drop(&mut self) {
unsafe {
match &self.previous {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
}

pub struct ScanFixture {
pub _tmp: TempDir,
pub config_dir: PathBuf,
pub config: AppConfig,
}

pub fn scan_fixture(project_count: usize, nested_dirs: usize) -> ScanFixture {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("workspace");
let cfg_dir = tmp.path().join("cfg");
let _guard = env_lock().lock().unwrap();

unsafe {
std::env::set_var("QR_CONFIG_DIR", &cfg_dir);
std::env::set_var("HOME", tmp.path());
}

for index in 0..project_count {
let mut project_dir = root.join(format!("project-{index:04}"));
for depth in 0..nested_dirs {
Expand All @@ -45,43 +70,88 @@ pub fn scan_fixture(project_count: usize, nested_dirs: usize) -> ScanFixture {
fs::write(project_dir.join(".git/config"), "").unwrap();
}

let mut config = AppConfig::load_from_env_with_path(cfg_dir.join("config.toml")).unwrap();
let mut config = AppConfig::load_file_without_env(&cfg_dir.join("config.toml")).unwrap();
config.projects.roots = vec![root.display().to_string()];
config.projects.scan_depth = nested_dirs + 2;
config.stats.db_path = cfg_dir.join("stats.db").display().to_string();

ScanFixture { _tmp: tmp, config }
ScanFixture {
_tmp: tmp,
config_dir: cfg_dir,
config,
}
}

pub fn hidden_tree_scan_fixture(entry_count: usize) -> ScanFixture {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("workspace");
let cfg_dir = tmp.path().join("cfg");
for index in 0..entry_count {
fs::create_dir_all(root.join(format!(".git/objects/shard-{index:04}/nested"))).unwrap();
}

let mut config = AppConfig::load_file_without_env(&cfg_dir.join("config.toml")).unwrap();
config.projects.roots = vec![root.display().to_string()];
config.projects.scan_depth = 4;
config.stats.db_path = cfg_dir.join("stats.db").display().to_string();

ScanFixture {
_tmp: tmp,
config_dir: cfg_dir,
config,
}
}

pub fn node_modules_scan_fixture(package_count: usize) -> ScanFixture {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("workspace");
let cfg_dir = tmp.path().join("cfg");
let app = root.join("app");
fs::create_dir_all(&app).unwrap();
fs::write(app.join("package.json"), "{}").unwrap();
for index in 0..package_count {
let dependency = app.join(format!("node_modules/package-{index:04}"));
fs::create_dir_all(&dependency).unwrap();
fs::write(dependency.join("package.json"), "{}").unwrap();
}

let mut config = AppConfig::load_file_without_env(&cfg_dir.join("config.toml")).unwrap();
config.projects.roots = vec![root.display().to_string()];
config.projects.scan_depth = 3;
config.stats.db_path = cfg_dir.join("stats.db").display().to_string();

ScanFixture {
_tmp: tmp,
config_dir: cfg_dir,
config,
}
}

pub struct GoFixture {
pub _tmp: TempDir,
pub config_dir: PathBuf,
pub config: AppConfig,
pub entries: Vec<ProjectEntry>,
}

pub fn go_fixture(project_count: usize) -> GoFixture {
let tmp = tempfile::tempdir().unwrap();
let cfg_dir = tmp.path().join("cfg");
let _guard = env_lock().lock().unwrap();
let entries = sample_projects(project_count);
let cache = ProjectCache {
scanned_at_unix_ms: 1,
projects: entries.clone(),
};

unsafe {
std::env::set_var("QR_CONFIG_DIR", &cfg_dir);
std::env::set_var("HOME", tmp.path());
}

let mut config = AppConfig::load_from_env_with_path(cfg_dir.join("config.toml")).unwrap();
let mut config = AppConfig::load_file_without_env(&cfg_dir.join("config.toml")).unwrap();
config.projects.roots = vec![tmp.path().join("workspace").display().to_string()];
config.stats.db_path = cfg_dir.join("stats.db").display().to_string();
config.ensure_parent_dirs().unwrap();
write_project_cache(&config.cache_path(), &cache).unwrap();
fs::create_dir_all(&cfg_dir).unwrap();
write_project_cache(&cfg_dir.join("projects-cache.json"), &cache).unwrap();

GoFixture {
_tmp: tmp,
config_dir: cfg_dir,
config,
entries,
}
Expand Down
13 changes: 10 additions & 3 deletions benches/go_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ fn go_benchmarks(c: &mut Criterion) {
("cache_miss", "does-not-exist"),
] {
let fixture = common::go_fixture(1_000);
execute_group.bench_with_input(BenchmarkId::new(label, query), &fixture, |b, fixture| {
b.iter(|| execute(&fixture.config, query).ok());
});
{
let _env = common::scoped_env_var("QR_CONFIG_DIR", fixture.config_dir.as_os_str());
execute_group.bench_with_input(
BenchmarkId::new(label, query),
&fixture,
|b, fixture| {
b.iter(|| execute(&fixture.config, query).ok());
},
);
}
}
execute_group.finish();
}
Expand Down
23 changes: 21 additions & 2 deletions benches/scan_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@ fn scan_benchmarks(c: &mut Criterion) {
("large", 300, 3),
] {
let fixture = common::scan_fixture(projects, nested_dirs);
group.bench_with_input(BenchmarkId::new(label, projects), &fixture, |b, fixture| {
b.iter(|| scan_projects(&fixture.config).unwrap());
{
let _env = common::scoped_env_var("QR_CONFIG_DIR", fixture.config_dir.as_os_str());
group.bench_with_input(BenchmarkId::new(label, projects), &fixture, |b, fixture| {
b.iter(|| scan_projects(&fixture.config).unwrap());
});
}
}

let hidden = common::hidden_tree_scan_fixture(1_000);
{
let _env = common::scoped_env_var("QR_CONFIG_DIR", hidden.config_dir.as_os_str());
group.bench_function("prune_hidden_tree/1000", |b| {
b.iter(|| scan_projects(&hidden.config).unwrap());
});
}

let node_modules = common::node_modules_scan_fixture(1_000);
{
let _env = common::scoped_env_var("QR_CONFIG_DIR", node_modules.config_dir.as_os_str());
group.bench_function("prune_node_modules/1000", |b| {
b.iter(|| scan_projects(&node_modules.config).unwrap());
});
}

Expand Down
Loading
Loading