Skip to main content

bootstrap/core/build_steps/
perf.rs

1use 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    /// Run `profile_local eprintln`.
19    /// This executes the compiler on the given benchmarks and stores its stderr output.
20    Eprintln {
21        #[clap(flatten)]
22        opts: SharedOpts,
23    },
24    /// Run `profile_local samply`
25    /// This executes the compiler on the given benchmarks and profiles it with `samply`.
26    /// You need to install `samply`, e.g. using `cargo install --locked samply`.
27    Samply {
28        #[clap(flatten)]
29        opts: SharedOpts,
30    },
31    /// Run `profile_local cachegrind`.
32    /// This executes the compiler on the given benchmarks under `Cachegrind`.
33    Cachegrind {
34        #[clap(flatten)]
35        opts: SharedOpts,
36    },
37    /// Run compile benchmarks with a locally built compiler.
38    Benchmark {
39        /// Identifier to associate benchmark results with
40        #[clap(name = "benchmark-id")]
41        id: String,
42
43        #[clap(flatten)]
44        opts: SharedOpts,
45    },
46    /// Compare the results of two previously executed benchmark runs.
47    Compare {
48        /// The name of the base artifact to be compared.
49        base: String,
50
51        /// The name of the modified artifact to be compared.
52        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    /// Select the benchmarks that you want to run (separated by commas).
71    /// If unspecified, all benchmarks will be executed.
72    #[clap(long, global = true, value_delimiter = ',')]
73    include: Vec<String>,
74
75    /// Select the benchmarks matching a prefix in this comma-separated list that you don't want to run.
76    #[clap(long, global = true, value_delimiter = ',')]
77    exclude: Vec<String>,
78
79    /// Select the scenarios that should be benchmarked.
80    #[clap(
81        long,
82        global = true,
83        value_delimiter = ',',
84        default_value = "Full,IncrFull,IncrUnchanged,IncrPatched"
85    )]
86    scenarios: Vec<Scenario>,
87    /// Select the profiles that should be benchmarked.
88    #[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
138/// Performs profiling using `rustc-perf` on a built version of the compiler.
139pub 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    // We need to set the working directory to `src/tools/rustc-perf`, so that it can find the directory
152    // with compile-time benchmarks.
153    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}