about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorUrgau <3616612+Urgau@users.noreply.github.com>2025-02-09 00:37:27 +0100
committerGitHub <noreply@github.com>2025-02-09 00:37:27 +0100
commitd024cef0577ad6f3e370dfc3d5befe47695235b8 (patch)
treefbca0d8f9a752ada022faab9bd7679baf2d0dded /src/bootstrap
parent9530d243d740eb7f8eddc0a08dad635a8ff4189a (diff)
parentc73ed895c700bd646af5bfbd111268fcaca9fda0 (diff)
downloadrust-d024cef0577ad6f3e370dfc3d5befe47695235b8.tar.gz
rust-d024cef0577ad6f3e370dfc3d5befe47695235b8.zip
Rollup merge of #136530 - Kobzol:x-perf, r=onur-ozkan
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``
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/src/core/build_steps/perf.rs220
-rw-r--r--src/bootstrap/src/core/build_steps/tool.rs1
-rw-r--r--src/bootstrap/src/core/config/flags.rs8
-rw-r--r--src/bootstrap/src/lib.rs4
4 files changed, 213 insertions, 20 deletions
diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs
index 5b83080a326..98c63a41e76 100644
--- a/src/bootstrap/src/core/build_steps/perf.rs
+++ b/src/bootstrap/src/core/build_steps/perf.rs
@@ -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(","));
+    }
 }
diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs
index 75fac88252b..734ade8bfed 100644
--- a/src/bootstrap/src/core/build_steps/tool.rs
+++ b/src/bootstrap/src/core/build_steps/tool.rs
@@ -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";
diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs
index 27fb00cb06e..c08a041ebcd 100644
--- a/src/bootstrap/src/core/config/flags.rs
+++ b/src/bootstrap/src/core/config/flags.rs
@@ -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};
@@ -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 {
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 2dd83d5938e..31cb5823b72 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -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");