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 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    Opt,
99    Clippy,
100}
101
102impl Display for Profile {
103    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104        let name = match self {
105            Profile::Check => "Check",
106            Profile::Debug => "Debug",
107            Profile::Doc => "Doc",
108            Profile::Opt => "Opt",
109            Profile::Clippy => "Clippy",
110        };
111        f.write_str(name)
112    }
113}
114
115#[derive(Clone, Copy, Debug, clap::ValueEnum)]
116#[value(rename_all = "PascalCase")]
117pub enum Scenario {
118    Full,
119    IncrFull,
120    IncrUnchanged,
121    IncrPatched,
122}
123
124impl Display for Scenario {
125    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126        let name = match self {
127            Scenario::Full => "Full",
128            Scenario::IncrFull => "IncrFull",
129            Scenario::IncrUnchanged => "IncrUnchanged",
130            Scenario::IncrPatched => "IncrPatched",
131        };
132        f.write_str(name)
133    }
134}
135
136/// Performs profiling using `rustc-perf` on a built version of the compiler.
137pub fn perf(builder: &Builder<'_>, args: &PerfArgs) {
138    let collector = builder.ensure(RustcPerf {
139        compiler: builder.compiler(0, builder.config.host_target),
140        target: builder.config.host_target,
141    });
142
143    let rustc_perf_dir = builder.build.tempdir().join("rustc-perf");
144    let results_dir = rustc_perf_dir.join("results");
145    builder.create_dir(&results_dir);
146
147    let mut cmd = command(collector.tool_path);
148
149    // We need to set the working directory to `src/tools/rustc-perf`, so that it can find the directory
150    // with compile-time benchmarks.
151    cmd.current_dir(builder.src.join("src/tools/rustc-perf"));
152
153    let db_path = results_dir.join("results.db");
154
155    let is_profiling = match &args.cmd {
156        PerfCommand::Eprintln { .. }
157        | PerfCommand::Samply { .. }
158        | PerfCommand::Cachegrind { .. } => true,
159        PerfCommand::Benchmark { .. } | PerfCommand::Compare { .. } => false,
160    };
161    if is_profiling && builder.build.config.rust_debuginfo_level_rustc == DebuginfoLevel::None {
162        builder.info(r#"WARNING: You are compiling rustc without debuginfo, this will make profiling less useful.
163Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#);
164    }
165
166    let prepare_rustc = || {
167        let compiler = builder.compiler(builder.top_stage, builder.config.host_target);
168        builder.std(compiler, builder.config.host_target);
169
170        if let Some(opts) = args.cmd.shared_opts()
171            && opts.profiles.contains(&Profile::Doc)
172        {
173            builder.ensure(Rustdoc { target_compiler: compiler });
174        }
175
176        let sysroot = builder.ensure(Sysroot::new(compiler));
177        let mut rustc = sysroot.clone();
178        rustc.push("bin");
179        rustc.push("rustc");
180        rustc.set_extension(EXE_EXTENSION);
181        rustc
182    };
183
184    match &args.cmd {
185        PerfCommand::Eprintln { opts }
186        | PerfCommand::Samply { opts }
187        | PerfCommand::Cachegrind { opts } => {
188            cmd.arg("profile_local");
189            cmd.arg(match &args.cmd {
190                PerfCommand::Eprintln { .. } => "eprintln",
191                PerfCommand::Samply { .. } => "samply",
192                PerfCommand::Cachegrind { .. } => "cachegrind",
193                _ => unreachable!(),
194            });
195
196            cmd.arg("--out-dir").arg(&results_dir);
197            cmd.arg(prepare_rustc());
198
199            apply_shared_opts(&mut cmd, opts);
200            cmd.run(builder);
201
202            println!("You can find the results at `{}`", results_dir.display());
203        }
204        PerfCommand::Benchmark { id, opts } => {
205            cmd.arg("bench_local");
206            cmd.arg("--db").arg(&db_path);
207            cmd.arg("--id").arg(id);
208            cmd.arg(prepare_rustc());
209
210            apply_shared_opts(&mut cmd, opts);
211            cmd.run(builder);
212        }
213        PerfCommand::Compare { base, modified } => {
214            cmd.arg("bench_cmp");
215            cmd.arg("--db").arg(&db_path);
216            cmd.arg(base).arg(modified);
217
218            cmd.run(builder);
219        }
220    }
221}
222
223fn apply_shared_opts(cmd: &mut BootstrapCommand, opts: &SharedOpts) {
224    if !opts.include.is_empty() {
225        cmd.arg("--include").arg(opts.include.join(","));
226    }
227    if !opts.exclude.is_empty() {
228        cmd.arg("--exclude").arg(opts.exclude.join(","));
229    }
230    if !opts.profiles.is_empty() {
231        cmd.arg("--profiles")
232            .arg(opts.profiles.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
233    }
234    if !opts.scenarios.is_empty() {
235        cmd.arg("--scenarios")
236            .arg(opts.scenarios.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
237    }
238}