From a3ab144e5559da7c52388b257b09a2696ef9141e Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:16:07 +0200 Subject: [PATCH] Add benchmark for project search --- Cargo.lock | 17 ++++ Cargo.toml | 1 + crates/project_benchmarks/Cargo.toml | 21 ++++ crates/project_benchmarks/LICENSE-GPL | 1 + crates/project_benchmarks/src/main.rs | 136 ++++++++++++++++++++++++++ 5 files changed, 176 insertions(+) create mode 100644 crates/project_benchmarks/Cargo.toml create mode 120000 crates/project_benchmarks/LICENSE-GPL create mode 100644 crates/project_benchmarks/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 27848216c6931b8db41500a1d835d997dafd4cbd..b75b8896ac0611bca05fdddc42aefea2aa2c09c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12876,6 +12876,23 @@ dependencies = [ "zlog", ] +[[package]] +name = "project_benchmarks" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "client", + "futures 0.3.31", + "gpui", + "http_client", + "language", + "node_runtime", + "project", + "settings", + "watch", +] + [[package]] name = "project_panel" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bd0083d122018c6769ca161c31e0a9b3ef4ec898..1f5a4dc8fe9dc31b74ca699da0090a0223f14f5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,7 @@ members = [ "crates/picker", "crates/prettier", "crates/project", + "crates/project_benchmarks", "crates/project_panel", "crates/project_symbols", "crates/prompt_store", diff --git a/crates/project_benchmarks/Cargo.toml b/crates/project_benchmarks/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1171d468c649bdd9f76a44b3ef0155dc652c6034 --- /dev/null +++ b/crates/project_benchmarks/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "project_benchmarks" +version = "0.1.0" +publish.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +client.workspace = true +futures.workspace = true +gpui = { workspace = true, features = ["windows-manifest"] } +http_client = { workspace = true, features = ["test-support"]} +language.workspace = true +node_runtime.workspace = true +project.workspace = true +settings.workspace = true +watch.workspace = true + +[lints] +workspace = true diff --git a/crates/project_benchmarks/LICENSE-GPL b/crates/project_benchmarks/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/project_benchmarks/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/project_benchmarks/src/main.rs b/crates/project_benchmarks/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..5075016665a072f172da461cffdf6c5dbcabb4ac --- /dev/null +++ b/crates/project_benchmarks/src/main.rs @@ -0,0 +1,136 @@ +use std::sync::Arc; + +use clap::Parser; +use client::{Client, UserStore}; +use gpui::{AppContext as _, Application}; +use http_client::FakeHttpClient; +use language::LanguageRegistry; +use node_runtime::NodeRuntime; +use project::{ + Project, RealFs, + search::{SearchQuery, SearchResult}, +}; + +#[derive(Parser)] +struct Args { + /// List of worktrees to run the search against. + worktrees: Vec, + #[clap(short)] + query: String, + /// Treat query as a regex. + #[clap(short, long)] + regex: bool, + /// Matches have to be standalone words. + #[clap(long)] + whole_word: bool, + /// Make matching case-sensitive. + #[clap(long, default_value_t = true)] + case_sensitive: bool, + /// Include gitignored files in the search. + #[clap(long)] + include_ignored: bool, +} + +fn main() -> Result<(), anyhow::Error> { + let args = Args::parse(); + let query = if args.regex { + SearchQuery::regex( + args.query, + args.whole_word, + args.case_sensitive, + args.include_ignored, + false, + Default::default(), + Default::default(), + false, + None, + ) + } else { + SearchQuery::text( + args.query, + args.whole_word, + args.case_sensitive, + args.include_ignored, + Default::default(), + Default::default(), + false, + None, + ) + }?; + Application::headless().run(|cx| { + settings::init(cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + let client = Client::production(cx); + let http_client = FakeHttpClient::with_200_response(); + let (_, rx) = watch::channel(None); + let node = NodeRuntime::new(http_client, None, rx); + let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); + let registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone())); + let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())); + let project = Project::local( + client, + node, + user_store, + registry, + fs, + Some(Default::default()), + cx, + ); + + project.clone().update(cx, move |_, cx| { + cx.spawn(async move |_, cx| { + println!("Loading worktrees"); + let worktrees = project.update(cx, |this, cx| { + args.worktrees + .into_iter() + .map(|worktree| this.find_or_create_worktree(worktree, true, cx)) + .collect::>() + })?; + + let worktrees = futures::future::join_all(worktrees) + .await + .into_iter() + .collect::, anyhow::Error>>()?; + + for (worktree, _) in &worktrees { + worktree + .update(cx, |this, _| this.as_local().unwrap().scan_complete())? + .await; + } + println!("Worktrees loaded"); + + println!("Starting a project search"); + let timer = std::time::Instant::now(); + let mut first_match = None; + let matches = project + .update(cx, |this, cx| this.search(query, cx)) + .unwrap(); + let mut matched_files = 0; + let mut matched_chunks = 0; + while let Ok(match_result) = matches.recv().await { + if first_match.is_none() { + let time = timer.elapsed(); + first_match = Some(time); + println!("First match found after {time:?}"); + } + if let SearchResult::Buffer { ranges, .. } = match_result { + matched_files += 1; + matched_chunks += ranges.len(); + } + } + let elapsed = timer.elapsed(); + println!( + "Finished project search after {elapsed:?}. Matched {matched_files} files and {matched_chunks} excerpts" + ); + drop(project); + cx.update(|cx| cx.quit())?; + + anyhow::Ok(()) + }) + .detach(); + }); + }); + Ok(()) +}