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