Skip to content

Commit

Permalink
Rollup merge of rust-lang#136530 - Kobzol:x-perf, r=onur-ozkan
Browse files Browse the repository at this point in the history
Implement `x perf` directly in bootstrap

Discussed [here](https://rust-lang.zulipchat.com/#narrow/channel/326414-t-infra.2Fbootstrap/topic/Turning.20.60x.20perf.60.20into.20a.20first.20class.20command).

Implementing the command directly in bootstrap let's us correctly build the compiler toolchain based on input arguments (such as include rustdoc in the toolchain [only] when needed), and it also makes the CLI interface nicer.

r? ``@onur-ozkan``
  • Loading branch information
Urgau authored Feb 8, 2025
2 parents 9530d24 + c73ed89 commit d024cef
Show file tree
Hide file tree
Showing 20 changed files with 3,642 additions and 341 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3287,13 +3287,6 @@ dependencies = [
"tikv-jemalloc-sys",
]

[[package]]
name = "rustc-perf-wrapper"
version = "0.1.0"
dependencies = [
"clap",
]

[[package]]
name = "rustc-rayon"
version = "0.5.1"
Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ members = [
"src/tools/rustdoc-gui-test",
"src/tools/opt-dist",
"src/tools/coverage-dump",
"src/tools/rustc-perf-wrapper",
"src/tools/wasm-component-ld",
"src/tools/features-status-dump",
]
Expand Down
220 changes: 208 additions & 12 deletions src/bootstrap/src/core/build_steps/perf.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,231 @@
use std::fmt::{Display, Formatter};

use crate::core::build_steps::compile::{Std, Sysroot};
use crate::core::build_steps::tool::{RustcPerf, Tool};
use crate::core::build_steps::tool::{RustcPerf, Rustdoc};
use crate::core::builder::Builder;
use crate::core::config::DebuginfoLevel;
use crate::utils::exec::{BootstrapCommand, command};

#[derive(Debug, Clone, clap::Parser)]
pub struct PerfArgs {
#[clap(subcommand)]
cmd: PerfCommand,
}

#[derive(Debug, Clone, clap::Parser)]
enum PerfCommand {
/// Run `profile_local eprintln`.
/// This executes the compiler on the given benchmarks and stores its stderr output.
Eprintln {
#[clap(flatten)]
opts: SharedOpts,
},
/// Run `profile_local samply`
/// This executes the compiler on the given benchmarks and profiles it with `samply`.
/// You need to install `samply`, e.g. using `cargo install samply`.
Samply {
#[clap(flatten)]
opts: SharedOpts,
},
/// Run `profile_local cachegrind`.
/// This executes the compiler on the given benchmarks under `Cachegrind`.
Cachegrind {
#[clap(flatten)]
opts: SharedOpts,
},
/// Run compile benchmarks with a locally built compiler.
Benchmark {
/// Identifier to associate benchmark results with
#[clap(name = "benchmark-id")]
id: String,

#[clap(flatten)]
opts: SharedOpts,
},
/// Compare the results of two previously executed benchmark runs.
Compare {
/// The name of the base artifact to be compared.
base: String,

/// The name of the modified artifact to be compared.
modified: String,
},
}

impl PerfCommand {
fn shared_opts(&self) -> Option<&SharedOpts> {
match self {
PerfCommand::Eprintln { opts, .. }
| PerfCommand::Samply { opts, .. }
| PerfCommand::Cachegrind { opts, .. }
| PerfCommand::Benchmark { opts, .. } => Some(opts),
PerfCommand::Compare { .. } => None,
}
}
}

#[derive(Debug, Clone, clap::Parser)]
struct SharedOpts {
/// Select the benchmarks that you want to run (separated by commas).
/// If unspecified, all benchmarks will be executed.
#[clap(long, global = true, value_delimiter = ',')]
include: Vec<String>,

/// Select the benchmarks matching a prefix in this comma-separated list that you don't want to run.
#[clap(long, global = true, value_delimiter = ',')]
exclude: Vec<String>,

/// Select the scenarios that should be benchmarked.
#[clap(
long,
global = true,
value_delimiter = ',',
default_value = "Full,IncrFull,IncrUnchanged,IncrPatched"
)]
scenarios: Vec<Scenario>,
/// Select the profiles that should be benchmarked.
#[clap(long, global = true, value_delimiter = ',', default_value = "Check,Debug,Opt")]
profiles: Vec<Profile>,
}

#[derive(Clone, Copy, Debug, PartialEq, clap::ValueEnum)]
#[value(rename_all = "PascalCase")]
pub enum Profile {
Check,
Debug,
Doc,
Opt,
Clippy,
}

impl Display for Profile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self {
Profile::Check => "Check",
Profile::Debug => "Debug",
Profile::Doc => "Doc",
Profile::Opt => "Opt",
Profile::Clippy => "Clippy",
};
f.write_str(name)
}
}

#[derive(Clone, Copy, Debug, clap::ValueEnum)]
#[value(rename_all = "PascalCase")]
pub enum Scenario {
Full,
IncrFull,
IncrUnchanged,
IncrPatched,
}

impl Display for Scenario {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self {
Scenario::Full => "Full",
Scenario::IncrFull => "IncrFull",
Scenario::IncrUnchanged => "IncrUnchanged",
Scenario::IncrPatched => "IncrPatched",
};
f.write_str(name)
}
}

/// Performs profiling using `rustc-perf` on a built version of the compiler.
pub fn perf(builder: &Builder<'_>) {
pub fn perf(builder: &Builder<'_>, args: &PerfArgs) {
let collector = builder.ensure(RustcPerf {
compiler: builder.compiler(0, builder.config.build),
target: builder.config.build,
});

if builder.build.config.rust_debuginfo_level_rustc == DebuginfoLevel::None {
let is_profiling = match &args.cmd {
PerfCommand::Eprintln { .. }
| PerfCommand::Samply { .. }
| PerfCommand::Cachegrind { .. } => true,
PerfCommand::Benchmark { .. } | PerfCommand::Compare { .. } => false,
};
if is_profiling && builder.build.config.rust_debuginfo_level_rustc == DebuginfoLevel::None {
builder.info(r#"WARNING: You are compiling rustc without debuginfo, this will make profiling less useful.
Consider setting `rust.debuginfo-level = 1` in `config.toml`."#);
}

let compiler = builder.compiler(builder.top_stage, builder.config.build);
builder.ensure(Std::new(compiler, builder.config.build));

if let Some(opts) = args.cmd.shared_opts() {
if opts.profiles.contains(&Profile::Doc) {
builder.ensure(Rustdoc { compiler });
}
}

let sysroot = builder.ensure(Sysroot::new(compiler));
let rustc = sysroot.join("bin/rustc");

let rustc_perf_dir = builder.build.tempdir().join("rustc-perf");
let profile_results_dir = rustc_perf_dir.join("results");
let results_dir = rustc_perf_dir.join("results");
builder.create_dir(&results_dir);

let mut cmd = command(collector);

// We need to set the working directory to `src/tools/rustc-perf`, so that it can find the directory
// with compile-time benchmarks.
cmd.current_dir(builder.src.join("src/tools/rustc-perf"));

let db_path = results_dir.join("results.db");

match &args.cmd {
PerfCommand::Eprintln { opts }
| PerfCommand::Samply { opts }
| PerfCommand::Cachegrind { opts } => {
cmd.arg("profile_local");
cmd.arg(match &args.cmd {
PerfCommand::Eprintln { .. } => "eprintln",
PerfCommand::Samply { .. } => "samply",
PerfCommand::Cachegrind { .. } => "cachegrind",
_ => unreachable!(),
});

// We need to take args passed after `--` and pass them to `rustc-perf-wrapper`
let args = std::env::args().skip_while(|a| a != "--").skip(1);
cmd.arg("--out-dir").arg(&results_dir);
cmd.arg(rustc);

let mut cmd = builder.tool_cmd(Tool::RustcPerfWrapper);
cmd.env("RUSTC_REAL", rustc)
.env("PERF_COLLECTOR", collector)
.env("PERF_RESULT_DIR", profile_results_dir)
.args(args);
cmd.run(builder);
apply_shared_opts(&mut cmd, opts);
cmd.run(builder);

println!("You can find the results at `{}`", &results_dir.display());
}
PerfCommand::Benchmark { id, opts } => {
cmd.arg("bench_local");
cmd.arg("--db").arg(&db_path);
cmd.arg("--id").arg(id);
cmd.arg(rustc);

apply_shared_opts(&mut cmd, opts);
cmd.run(builder);
}
PerfCommand::Compare { base, modified } => {
cmd.arg("bench_cmp");
cmd.arg("--db").arg(&db_path);
cmd.arg(base).arg(modified);

cmd.run(builder);
}
}
}

fn apply_shared_opts(cmd: &mut BootstrapCommand, opts: &SharedOpts) {
if !opts.include.is_empty() {
cmd.arg("--include").arg(opts.include.join(","));
}
if !opts.exclude.is_empty() {
cmd.arg("--exclude").arg(opts.exclude.join(","));
}
if !opts.profiles.is_empty() {
cmd.arg("--profiles")
.arg(opts.profiles.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
}
if !opts.scenarios.is_empty() {
cmd.arg("--scenarios")
.arg(opts.scenarios.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","));
}
}
1 change: 0 additions & 1 deletion src/bootstrap/src/core/build_steps/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@ bootstrap_tool!(
GenerateWindowsSys, "src/tools/generate-windows-sys", "generate-windows-sys";
RustdocGUITest, "src/tools/rustdoc-gui-test", "rustdoc-gui-test", is_unstable_tool = true, allow_features = "test";
CoverageDump, "src/tools/coverage-dump", "coverage-dump";
RustcPerfWrapper, "src/tools/rustc-perf-wrapper", "rustc-perf-wrapper";
WasmComponentLd, "src/tools/wasm-component-ld", "wasm-component-ld", is_unstable_tool = true, allow_features = "min_specialization";
UnicodeTableGenerator, "src/tools/unicode-table-generator", "unicode-table-generator";
FeaturesStatusDump, "src/tools/features-status-dump", "features-status-dump";
Expand Down
8 changes: 3 additions & 5 deletions src/bootstrap/src/core/config/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use clap::{CommandFactory, Parser, ValueEnum};
#[cfg(feature = "tracing")]
use tracing::instrument;

use crate::core::build_steps::perf::PerfArgs;
use crate::core::build_steps::setup::Profile;
use crate::core::builder::{Builder, Kind};
use crate::core::config::{Config, TargetSelectionList, target_selection_list};
Expand Down Expand Up @@ -481,11 +482,8 @@ Arguments:
#[arg(long)]
versioned_dirs: bool,
},
/// Perform profiling and benchmarking of the compiler using the
/// `rustc-perf-wrapper` tool.
///
/// You need to pass arguments after `--`, e.g.`x perf -- cachegrind`.
Perf {},
/// Perform profiling and benchmarking of the compiler using `rustc-perf`.
Perf(PerfArgs),
}

impl Subcommand {
Expand Down
4 changes: 2 additions & 2 deletions src/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,8 @@ impl Build {
Subcommand::Suggest { run } => {
return core::build_steps::suggest::suggest(&builder::Builder::new(self), *run);
}
Subcommand::Perf { .. } => {
return core::build_steps::perf::perf(&builder::Builder::new(self));
Subcommand::Perf(args) => {
return core::build_steps::perf::perf(&builder::Builder::new(self), args);
}
_cmd => {
debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling");
Expand Down
4 changes: 1 addition & 3 deletions src/doc/rustc-dev-guide/src/profiling/with_rustc_perf.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ However, using the suite manually can be a bit cumbersome. To make this easier f
the compiler build system (`bootstrap`) also provides built-in integration with the benchmarking suite,
which will download and build the suite for you, build a local compiler toolchain and let you profile it using a simplified command-line interface.

You can use the `./x perf -- <command> [options]` command to use this integration.

> Note that you need to specify arguments after `--` in the `x perf` command! You will not be able to pass arguments without the double dashes.
You can use the `./x perf <command> [options]` command to use this integration.

You can use normal bootstrap flags for this command, such as `--stage 1` or `--stage 2`, for example to modify the stage of the created sysroot. It might also be useful to configure `config.toml` to better support profiling, e.g. set `rust.debuginfo-level = 1` to add source line information to the built compiler.

Expand Down
Loading

0 comments on commit d024cef

Please sign in to comment.