bootstrap/core/build_steps/
perf.rs1use std::env::consts::EXE_EXTENSION;
2use std::fmt::{Display, Formatter};
3use std::path::PathBuf;
4
5use crate::core::build_steps::compile::Sysroot;
6use crate::core::build_steps::tool::{RustcPerf, Rustdoc};
7use crate::core::builder::Builder;
8use crate::core::config::DebuginfoLevel;
9use crate::utils::exec::{BootstrapCommand, command};
10
11#[derive(Debug, Clone, clap::Parser)]
12pub struct PerfArgs {
13 #[clap(subcommand)]
14 cmd: PerfCommand,
15}
16
17#[derive(Debug, Clone, clap::Parser)]
18enum PerfCommand {
19 Eprintln {
22 #[clap(flatten)]
23 opts: SharedOpts,
24 },
25 Samply {
29 #[clap(flatten)]
30 opts: SharedOpts,
31 },
32 Cachegrind {
35 #[clap(flatten)]
36 opts: SharedOpts,
37 },
38 Benchmark {
40 #[clap(name = "benchmark-id")]
42 id: String,
43
44 #[clap(flatten)]
45 opts: SharedOpts,
46 },
47 Compare {
49 base: String,
51
52 modified: String,
54
55 #[clap(long, global = true)]
56 database_path: Option<String>,
57 },
58}
59
60impl PerfCommand {
61 fn shared_opts(&self) -> Option<&SharedOpts> {
62 match self {
63 PerfCommand::Eprintln { opts, .. }
64 | PerfCommand::Samply { opts, .. }
65 | PerfCommand::Cachegrind { opts, .. }
66 | PerfCommand::Benchmark { opts, .. } => Some(opts),
67 PerfCommand::Compare { .. } => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, clap::Parser)]
73struct SharedOpts {
74 #[clap(long, global = true, value_delimiter = ',')]
77 include: Vec<String>,
78
79 #[clap(long, global = true, value_delimiter = ',')]
81 exclude: Vec<String>,
82
83 #[clap(
85 long,
86 global = true,
87 value_delimiter = ',',
88 default_value = "Full,IncrFull,IncrUnchanged,IncrPatched"
89 )]
90 scenarios: Vec<Scenario>,
91 #[clap(long, global = true, value_delimiter = ',', default_value = "Check,Debug,Opt")]
93 profiles: Vec<Profile>,
94
95 #[clap(long, global = true)]
96 database_path: Option<String>,
97
98 #[clap(long, global = true, value_delimiter = ',')]
101 frontend_threads: Vec<usize>,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, clap::ValueEnum)]
105#[value(rename_all = "PascalCase")]
106pub enum Profile {
107 Check,
108 Debug,
109 Doc,
110 DocJson,
111 Opt,
112 Clippy,
113}
114
115impl Display for Profile {
116 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117 let name = match self {
118 Profile::Check => "Check",
119 Profile::Debug => "Debug",
120 Profile::Doc => "Doc",
121 Profile::DocJson => "DocJson",
122 Profile::Opt => "Opt",
123 Profile::Clippy => "Clippy",
124 };
125 f.write_str(name)
126 }
127}
128
129#[derive(Clone, Copy, Debug, clap::ValueEnum)]
130#[value(rename_all = "PascalCase")]
131pub enum Scenario {
132 Full,
133 IncrFull,
134 IncrUnchanged,
135 IncrPatched,
136}
137
138impl Display for Scenario {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 let name = match self {
141 Scenario::Full => "Full",
142 Scenario::IncrFull => "IncrFull",
143 Scenario::IncrUnchanged => "IncrUnchanged",
144 Scenario::IncrPatched => "IncrPatched",
145 };
146 f.write_str(name)
147 }
148}
149
150pub fn perf(builder: &Builder<'_>, args: &PerfArgs, trailing_args: &[String]) {
152 let collector = builder.ensure(RustcPerf {
153 compiler: builder.compiler(0, builder.config.host_target),
154 target: builder.config.host_target,
155 });
156
157 let rustc_perf_dir = builder.sess.tempdir().join("rustc-perf");
158 let results_dir = rustc_perf_dir.join("results");
159 builder.create_dir(&results_dir);
160
161 let mut cmd = command(collector.tool_path);
162
163 cmd.current_dir(builder.src.join("src/tools/rustc-perf"));
166
167 let db_path = args
168 .cmd
169 .shared_opts()
170 .and_then(|i| i.database_path.as_ref())
171 .or(if let PerfCommand::Compare { database_path: Some(path), .. } = &args.cmd {
172 Some(path)
173 } else {
174 None
175 })
176 .map(|i| PathBuf::from(i.as_str()))
177 .unwrap_or_else(|| results_dir.join("results.db"));
178
179 let is_profiling = match &args.cmd {
180 PerfCommand::Eprintln { .. }
181 | PerfCommand::Samply { .. }
182 | PerfCommand::Cachegrind { .. } => true,
183 PerfCommand::Benchmark { .. } | PerfCommand::Compare { .. } => false,
184 };
185 if is_profiling && builder.sess.config.rust_debuginfo_level_rustc == DebuginfoLevel::None {
186 builder.info(r#"WARNING: You are compiling rustc without debuginfo, this will make profiling less useful.
187Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#);
188 }
189
190 let prepare_rustc = || {
191 let compiler = builder.compiler(builder.top_stage, builder.config.host_target);
192 builder.std(compiler, builder.config.host_target);
193
194 if let Some(opts) = args.cmd.shared_opts()
195 && opts.profiles.contains(&Profile::Doc)
196 {
197 builder.ensure(Rustdoc { target_compiler: compiler });
198 }
199
200 let sysroot = builder.ensure(Sysroot::new(compiler));
201 let mut rustc = sysroot.clone();
202 rustc.push("bin");
203 rustc.push("rustc");
204 rustc.set_extension(EXE_EXTENSION);
205 rustc
206 };
207
208 match &args.cmd {
209 PerfCommand::Eprintln { opts }
210 | PerfCommand::Samply { opts }
211 | PerfCommand::Cachegrind { opts } => {
212 cmd.arg("profile_local");
213 cmd.arg(match &args.cmd {
214 PerfCommand::Eprintln { .. } => "eprintln",
215 PerfCommand::Samply { .. } => "samply",
216 PerfCommand::Cachegrind { .. } => "cachegrind",
217 _ => unreachable!(),
218 });
219
220 cmd.arg("--out-dir").arg(&results_dir);
221 cmd.arg(prepare_rustc());
222
223 apply_shared_opts(&mut cmd, opts);
224 cmd.args(trailing_args);
225 cmd.run(builder);
226
227 println!("You can find the results at `{}`", results_dir.display());
228 }
229 PerfCommand::Benchmark { id, opts } => {
230 cmd.arg("bench_local");
231 cmd.arg("--db").arg(&db_path);
232 cmd.arg("--id").arg(id);
233 cmd.arg(prepare_rustc());
234
235 apply_shared_opts(&mut cmd, opts);
236 cmd.args(trailing_args);
237 cmd.run(builder);
238 }
239 PerfCommand::Compare { base, modified, database_path: _ } => {
240 cmd.arg("bench_cmp");
241 cmd.arg("--db").arg(&db_path);
242 cmd.arg(base).arg(modified);
243
244 cmd.args(trailing_args);
245 cmd.run(builder);
246 }
247 }
248}
249
250fn apply_shared_opts(cmd: &mut BootstrapCommand, opts: &SharedOpts) {
251 if !opts.include.is_empty() {
252 cmd.arg("--include").arg(opts.include.join(","));
253 }
254 if !opts.exclude.is_empty() {
255 cmd.arg("--exclude").arg(opts.exclude.join(","));
256 }
257 if !opts.profiles.is_empty() {
258 cmd.arg("--profiles")
259 .arg(opts.profiles.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
260 }
261 if !opts.scenarios.is_empty() {
262 cmd.arg("--scenarios")
263 .arg(opts.scenarios.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
264 }
265 if !opts.frontend_threads.is_empty() {
266 cmd.arg("--frontend-threads")
267 .arg(opts.frontend_threads.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
268 }
269}