diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs index fa244e86c38..43d3fb7e3af 100644 --- a/crates/ra_batch/src/lib.rs +++ b/crates/ra_batch/src/lib.rs @@ -8,7 +8,7 @@ CrateGraph, FileId, SourceRootId, }; use ra_ide_api::{AnalysisHost, AnalysisChange}; -use ra_project_model::ProjectWorkspace; +use ra_project_model::{ProjectWorkspace, ProjectRoot}; use ra_vfs::{Vfs, VfsChange}; use vfs_filter::IncludeRustFiles; @@ -21,13 +21,11 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId { SourceRootId(r.0) } -pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, Vec)> { +pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap)> { let root = std::env::current_dir()?.join(root); let ws = ProjectWorkspace::discover(root.as_ref())?; - let mut roots = Vec::new(); - roots.push(IncludeRustFiles::member(root.clone())); - roots.extend(IncludeRustFiles::from_roots(ws.to_roots())); - let (mut vfs, roots) = Vfs::new(roots); + let project_roots = ws.to_roots(); + let (mut vfs, roots) = Vfs::new(IncludeRustFiles::from_roots(project_roots.clone()).collect()); let crate_graph = ws.to_crate_graph(&mut |path: &Path| { let vfs_file = vfs.load(path); log::debug!("vfs file {:?} -> {:?}", path, vfs_file); @@ -35,17 +33,27 @@ pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, Vec)> { }); log::debug!("crate graph: {:?}", crate_graph); - let local_roots = roots - .into_iter() - .filter(|r| vfs.root2path(*r).starts_with(&root)) - .map(vfs_root_to_id) - .collect(); - - let host = load(root.as_path(), crate_graph, &mut vfs); - Ok((host, local_roots)) + let source_roots = roots + .iter() + .map(|&vfs_root| { + let source_root_id = vfs_root_to_id(vfs_root); + let project_root = project_roots + .iter() + .find(|it| it.path() == &vfs.root2path(vfs_root)) + .unwrap() + .clone(); + (source_root_id, project_root) + }) + .collect::>(); + let host = load(&source_roots, crate_graph, &mut vfs); + Ok((host, source_roots)) } -pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> AnalysisHost { +pub fn load( + source_roots: &FxHashMap, + crate_graph: CrateGraph, + vfs: &mut Vfs, +) -> AnalysisHost { let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::().ok()); let mut host = AnalysisHost::new(lru_cap); let mut analysis_change = AnalysisChange::new(); @@ -60,8 +68,8 @@ pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> Anal for change in vfs.commit_changes() { match change { VfsChange::AddRoot { root, files } => { - let is_local = vfs.root2path(root).starts_with(&project_root); let source_root_id = vfs_root_to_id(root); + let is_local = source_roots[&source_root_id].is_member(); log::debug!( "loaded source root {:?} with path {:?}", source_root_id, @@ -106,7 +114,7 @@ fn test_loading_rust_analyzer() { let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); let (host, roots) = load_cargo(path).unwrap(); let mut n_crates = 0; - for root in roots { + for (root, _) in roots { for _krate in Crate::source_root_crates(host.raw_database(), root) { n_crates += 1; } diff --git a/crates/ra_batch/src/vfs_filter.rs b/crates/ra_batch/src/vfs_filter.rs index dd20c12031f..8552ac999ca 100644 --- a/crates/ra_batch/src/vfs_filter.rs +++ b/crates/ra_batch/src/vfs_filter.rs @@ -1,54 +1,54 @@ -use std::path::PathBuf; -use ra_project_model::ProjectRoot; -use ra_vfs::{RootEntry, Filter, RelativePath}; - -/// `IncludeRustFiles` is used to convert -/// from `ProjectRoot` to `RootEntry` for VFS -pub struct IncludeRustFiles { - root: ProjectRoot, -} - -impl IncludeRustFiles { - pub fn from_roots(roots: R) -> impl Iterator - where - R: IntoIterator, - { - roots.into_iter().map(IncludeRustFiles::from_root) - } - - pub fn from_root(root: ProjectRoot) -> RootEntry { - IncludeRustFiles::from(root).into() - } - - #[allow(unused)] - pub fn external(path: PathBuf) -> RootEntry { - IncludeRustFiles::from_root(ProjectRoot::new(path, false)) - } - - pub fn member(path: PathBuf) -> RootEntry { - IncludeRustFiles::from_root(ProjectRoot::new(path, true)) - } -} - -impl Filter for IncludeRustFiles { - fn include_dir(&self, dir_path: &RelativePath) -> bool { - self.root.include_dir(dir_path) - } - - fn include_file(&self, file_path: &RelativePath) -> bool { - self.root.include_file(file_path) - } -} - -impl std::convert::From for IncludeRustFiles { - fn from(v: ProjectRoot) -> IncludeRustFiles { - IncludeRustFiles { root: v } - } -} - -impl std::convert::From for RootEntry { - fn from(v: IncludeRustFiles) -> RootEntry { - let path = v.root.path().clone(); - RootEntry::new(path, Box::new(v)) - } -} +use std::path::PathBuf; +use ra_project_model::ProjectRoot; +use ra_vfs::{RootEntry, Filter, RelativePath}; + +/// `IncludeRustFiles` is used to convert +/// from `ProjectRoot` to `RootEntry` for VFS +pub struct IncludeRustFiles { + root: ProjectRoot, +} + +impl IncludeRustFiles { + pub fn from_roots(roots: R) -> impl Iterator + where + R: IntoIterator, + { + roots.into_iter().map(IncludeRustFiles::from_root) + } + + pub fn from_root(root: ProjectRoot) -> RootEntry { + IncludeRustFiles::from(root).into() + } + + #[allow(unused)] + pub fn external(path: PathBuf) -> RootEntry { + IncludeRustFiles::from_root(ProjectRoot::new(path, false)) + } + + pub fn member(path: PathBuf) -> RootEntry { + IncludeRustFiles::from_root(ProjectRoot::new(path, true)) + } +} + +impl Filter for IncludeRustFiles { + fn include_dir(&self, dir_path: &RelativePath) -> bool { + self.root.include_dir(dir_path) + } + + fn include_file(&self, file_path: &RelativePath) -> bool { + self.root.include_file(file_path) + } +} + +impl From for IncludeRustFiles { + fn from(v: ProjectRoot) -> IncludeRustFiles { + IncludeRustFiles { root: v } + } +} + +impl From for RootEntry { + fn from(v: IncludeRustFiles) -> RootEntry { + let path = v.root.path().clone(); + RootEntry::new(path, Box::new(v)) + } +} diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs new file mode 100644 index 00000000000..33d47283877 --- /dev/null +++ b/crates/ra_cli/src/analysis_bench.rs @@ -0,0 +1,92 @@ +use std::{ + path::{PathBuf, Path}, + time::Instant, +}; + +use ra_db::{SourceDatabase, salsa::Database}; +use ra_ide_api::{AnalysisHost, Analysis, LineCol, FilePosition}; + +use crate::Result; + +pub(crate) enum Op { + Highlight { path: PathBuf }, + Complete { path: PathBuf, line: u32, column: u32 }, +} + +pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> { + let start = Instant::now(); + eprint!("loading: "); + let (host, roots) = ra_batch::load_cargo(path)?; + let db = host.raw_database(); + eprintln!("{:?}\n", start.elapsed()); + + let file_id = { + let path = match &op { + Op::Highlight { path } => path, + Op::Complete { path, .. } => path, + }; + let path = std::env::current_dir()?.join(path).canonicalize()?; + roots + .iter() + .find_map(|(source_root_id, project_root)| { + if project_root.is_member() { + for (rel_path, file_id) in &db.source_root(*source_root_id).files { + let abs_path = rel_path.to_path(project_root.path()); + if abs_path == path { + return Some(*file_id); + } + } + } + None + }) + .ok_or_else(|| format!("Can't find {:?}", path))? + }; + + match op { + Op::Highlight { .. } => { + let res = do_work(&host, |analysis| { + analysis.diagnostics(file_id).unwrap(); + analysis.highlight_as_html(file_id, false).unwrap() + }); + if verbose { + println!("\n{}", res); + } + } + Op::Complete { line, column, .. } => { + let offset = host + .analysis() + .file_line_index(file_id) + .offset(LineCol { line, col_utf16: column }); + let file_postion = FilePosition { file_id, offset }; + + let res = do_work(&host, |analysis| analysis.completions(file_postion)); + if verbose { + println!("\n{:#?}", res); + } + } + } + Ok(()) +} + +fn do_work T, T>(host: &AnalysisHost, work: F) -> T { + { + let start = Instant::now(); + eprint!("from scratch: "); + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("no change: "); + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("trivial change: "); + host.raw_database().salsa_runtime().next_revision(); + let res = work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + res + } +} diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs index d76c37d847a..ed98fc7f655 100644 --- a/crates/ra_cli/src/analysis_stats.rs +++ b/crates/ra_cli/src/analysis_stats.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, time::Instant, fmt::Write}; +use std::{collections::HashSet, time::Instant, fmt::Write, path::Path}; use ra_db::SourceDatabase; use ra_hir::{Crate, ModuleDef, Ty, ImplItem, HasSource}; @@ -6,20 +6,23 @@ use crate::Result; -pub fn run(verbose: bool, path: &str, only: Option<&str>) -> Result<()> { +pub fn run(verbose: bool, path: &Path, only: Option<&str>) -> Result<()> { let db_load_time = Instant::now(); - let (host, roots) = ra_batch::load_cargo(path.as_ref())?; + let (host, roots) = ra_batch::load_cargo(path)?; let db = host.raw_database(); println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); let analysis_time = Instant::now(); let mut num_crates = 0; let mut visited_modules = HashSet::new(); let mut visit_queue = Vec::new(); - for root in roots { - for krate in Crate::source_root_crates(db, root) { - num_crates += 1; - let module = krate.root_module(db).expect("crate in source root without root module"); - visit_queue.push(module); + for (source_root_id, project_root) in roots { + if project_root.is_member() { + for krate in Crate::source_root_crates(db, source_root_id) { + num_crates += 1; + let module = + krate.root_module(db).expect("crate in source root without root module"); + visit_queue.push(module); + } } } println!("Crates in this dir: {}", num_crates); diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 1db98aec130..5adf8b09676 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -1,4 +1,5 @@ mod analysis_stats; +mod analysis_bench; use std::{io::Read, error::Error}; @@ -26,6 +27,27 @@ fn main() -> Result<()> { .arg(Arg::with_name("only").short("o").takes_value(true)) .arg(Arg::with_name("path")), ) + .subcommand( + SubCommand::with_name("analysis-bench") + .arg(Arg::with_name("verbose").short("v").long("verbose")) + .arg( + Arg::with_name("highlight") + .long("highlight") + .takes_value(true) + .conflicts_with("complete") + .value_name("PATH") + .help("highlight this file"), + ) + .arg( + Arg::with_name("complete") + .long("complete") + .takes_value(true) + .conflicts_with("highlight") + .value_name("PATH:LINE:COLUMN") + .help("compute completions at this location"), + ) + .arg(Arg::with_name("path").value_name("PATH").help("project to analyze")), + ) .get_matches(); match matches.subcommand() { ("parse", Some(matches)) => { @@ -51,7 +73,25 @@ fn main() -> Result<()> { let verbose = matches.is_present("verbose"); let path = matches.value_of("path").unwrap_or(""); let only = matches.value_of("only"); - analysis_stats::run(verbose, path, only)?; + analysis_stats::run(verbose, path.as_ref(), only)?; + } + ("analysis-bench", Some(matches)) => { + let verbose = matches.is_present("verbose"); + let path = matches.value_of("path").unwrap_or(""); + let op = if let Some(path) = matches.value_of("highlight") { + analysis_bench::Op::Highlight { path: path.into() } + } else if let Some(path_line_col) = matches.value_of("complete") { + let (path_line, column) = rsplit_at_char(path_line_col, ':')?; + let (path, line) = rsplit_at_char(path_line, ':')?; + analysis_bench::Op::Complete { + path: path.into(), + line: line.parse()?, + column: column.parse()?, + } + } else { + panic!("either --highlight or --complete must be set") + }; + analysis_bench::run(verbose, path.as_ref(), op)?; } _ => unreachable!(), } @@ -68,3 +108,8 @@ fn read_stdin() -> Result { std::io::stdin().read_to_string(&mut buff)?; Ok(buff) } + +fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> { + let idx = s.rfind(":").ok_or_else(|| format!("no `{}` in {}", c, s))?; + Ok((&s[..idx], &s[idx + 1..])) +} diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index a68c5e2a588..e61d5627e00 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -276,7 +276,7 @@ pub fn maybe_collect_garbage(&mut self) { pub fn collect_garbage(&mut self) { self.db.collect_garbage(); } - pub fn raw_database(&self) -> &impl hir::db::HirDatabase { + pub fn raw_database(&self) -> &(impl hir::db::HirDatabase + salsa::Database) { &self.db } } diff --git a/crates/ra_ide_api/src/line_index.rs b/crates/ra_ide_api/src/line_index.rs index 087dfafed82..a53cf9ee006 100644 --- a/crates/ra_ide_api/src/line_index.rs +++ b/crates/ra_ide_api/src/line_index.rs @@ -10,7 +10,9 @@ pub struct LineIndex { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LineCol { + /// Zero-based pub line: u32, + /// Zero-based pub col_utf16: u32, } diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index a3af153f1aa..42156bea6fe 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -37,6 +37,7 @@ pub enum ProjectWorkspace { /// `ProjectRoot` describes a workspace root folder. /// Which may be an external dependency, or a member of /// the current workspace. +#[derive(Clone)] pub struct ProjectRoot { /// Path to the root folder path: PathBuf, diff --git a/docs/dev/README.md b/docs/dev/README.md index d34ff96c826..3dc37e86eba 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -147,3 +147,16 @@ RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more tha ``` In particular, I have `export RA_PROFILE='*>10' in my shell profile. + +To measure time for from-scratch analysis, use something like this: + +``` +$ cargo run --release -p ra_cli -- analysis-stats ../chalk/ +``` + +For measuring time of incremental analysis, use either of these: + +``` +$ cargo run --release -p ra_cli -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs +$ cargo run --release -p ra_cli -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0 +```