]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/cli/analysis_bench.rs
Simpify workspace handling
[rust.git] / crates / rust-analyzer / src / cli / analysis_bench.rs
1 //! Benchmark operations like highlighting or goto definition.
2
3 use std::{
4     path::{Path, PathBuf},
5     str::FromStr,
6     sync::Arc,
7     time::Instant,
8 };
9
10 use anyhow::{format_err, Result};
11 use ra_db::{
12     salsa::{Database, Durability},
13     FileId, SourceDatabaseExt,
14 };
15 use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol};
16
17 use crate::cli::{load_cargo::load_cargo, Verbosity};
18
19 pub enum BenchWhat {
20     Highlight { path: PathBuf },
21     Complete(Position),
22     GotoDef(Position),
23 }
24
25 pub struct Position {
26     pub path: PathBuf,
27     pub line: u32,
28     pub column: u32,
29 }
30
31 impl FromStr for Position {
32     type Err = anyhow::Error;
33     fn from_str(s: &str) -> Result<Self> {
34         let (path_line, column) = rsplit_at_char(s, ':')?;
35         let (path, line) = rsplit_at_char(path_line, ':')?;
36         Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? })
37     }
38 }
39
40 fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
41     let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?;
42     Ok((&s[..idx], &s[idx + 1..]))
43 }
44
45 pub fn analysis_bench(
46     verbosity: Verbosity,
47     path: &Path,
48     what: BenchWhat,
49     load_output_dirs: bool,
50 ) -> Result<()> {
51     ra_prof::init();
52
53     let start = Instant::now();
54     eprint!("loading: ");
55     let (mut host, roots) = load_cargo(path, load_output_dirs)?;
56     let db = host.raw_database();
57     eprintln!("{:?}\n", start.elapsed());
58
59     let file_id = {
60         let path = match &what {
61             BenchWhat::Highlight { path } => path,
62             BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
63         };
64         let path = std::env::current_dir()?.join(path).canonicalize()?;
65         roots
66             .iter()
67             .find_map(|(source_root_id, project_root)| {
68                 if project_root.is_member {
69                     for file_id in db.source_root(*source_root_id).walk() {
70                         let rel_path = db.file_relative_path(file_id);
71                         let abs_path = rel_path.to_path(&project_root.path);
72                         if abs_path == path {
73                             return Some(file_id);
74                         }
75                     }
76                 }
77                 None
78             })
79             .ok_or_else(|| format_err!("Can't find {}", path.display()))?
80     };
81
82     match &what {
83         BenchWhat::Highlight { .. } => {
84             let res = do_work(&mut host, file_id, |analysis| {
85                 analysis.diagnostics(file_id).unwrap();
86                 analysis.highlight_as_html(file_id, false).unwrap()
87             });
88             if verbosity.is_verbose() {
89                 println!("\n{}", res);
90             }
91         }
92         BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => {
93             let is_completion = match what {
94                 BenchWhat::Complete(..) => true,
95                 _ => false,
96             };
97
98             let offset = host
99                 .analysis()
100                 .file_line_index(file_id)?
101                 .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
102             let file_position = FilePosition { file_id, offset };
103
104             if is_completion {
105                 let options = CompletionConfig::default();
106                 let res = do_work(&mut host, file_id, |analysis| {
107                     analysis.completions(file_position, &options)
108                 });
109                 if verbosity.is_verbose() {
110                     println!("\n{:#?}", res);
111                 }
112             } else {
113                 let res =
114                     do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_position));
115                 if verbosity.is_verbose() {
116                     println!("\n{:#?}", res);
117                 }
118             }
119         }
120     }
121     Ok(())
122 }
123
124 fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T {
125     {
126         let start = Instant::now();
127         eprint!("from scratch:   ");
128         work(&host.analysis());
129         eprintln!("{:?}", start.elapsed());
130     }
131     {
132         let start = Instant::now();
133         eprint!("no change:      ");
134         work(&host.analysis());
135         eprintln!("{:?}", start.elapsed());
136     }
137     {
138         let start = Instant::now();
139         eprint!("trivial change: ");
140         host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW);
141         work(&host.analysis());
142         eprintln!("{:?}", start.elapsed());
143     }
144     {
145         let start = Instant::now();
146         eprint!("comment change: ");
147         {
148             let mut text = host.analysis().file_text(file_id).unwrap().to_string();
149             text.push_str("\n/* Hello world */\n");
150             let mut change = AnalysisChange::new();
151             change.change_file(file_id, Arc::new(text));
152             host.apply_change(change);
153         }
154         work(&host.analysis());
155         eprintln!("{:?}", start.elapsed());
156     }
157     {
158         let start = Instant::now();
159         eprint!("const change:   ");
160         host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH);
161         let res = work(&host.analysis());
162         eprintln!("{:?}", start.elapsed());
163         res
164     }
165 }