| use anyhow::Context; | |
| use build_helper::{LLVM_PGO_CRATES, RUSTC_PGO_CRATES}; | |
| use camino::{Utf8Path, Utf8PathBuf}; | |
| use humansize::BINARY; | |
| use crate::environment::{Environment, executable_extension}; | |
| use crate::exec::{CmdBuilder, cmd}; | |
| use crate::utils::io::{count_files, delete_directory}; | |
| use crate::utils::with_log_group; | |
| fn init_compiler_benchmarks( | |
| env: &Environment, | |
| profiles: &[&str], | |
| scenarios: &[&str], | |
| crates: &[&str], | |
| ) -> CmdBuilder { | |
| // Run rustc-perf benchmarks | |
| // Benchmark using profile_local with eprintln, which essentially just means | |
| // don't actually benchmark -- just make sure we run rustc a bunch of times. | |
| let mut cmd = cmd(&[ | |
| env.cargo_stage_0().as_str(), | |
| "run", | |
| "-p", | |
| "collector", | |
| "--bin", | |
| "collector", | |
| "--", | |
| "profile_local", | |
| "eprintln", | |
| env.rustc_stage_2().as_str(), | |
| "--id", | |
| "Test", | |
| "--cargo", | |
| env.cargo_stage_0().as_str(), | |
| "--profiles", | |
| profiles.join(",").as_str(), | |
| "--scenarios", | |
| scenarios.join(",").as_str(), | |
| "--exact-match", | |
| crates.join(",").as_str(), | |
| ]) | |
| .env("RUSTC", env.rustc_stage_0().as_str()) | |
| .env("RUSTC_BOOTSTRAP", "1") | |
| .workdir(&env.rustc_perf_dir()); | |
| // This propagates cargo configs to `rustc-perf --cargo-config`, | |
| // which is particularly useful when the environment is air-gapped, | |
| // and you want to use the default set of training crates vendored | |
| // in the rustc-src tarball. | |
| for config in env.benchmark_cargo_config() { | |
| cmd = cmd.arg("--cargo-config").arg(config); | |
| } | |
| cmd | |
| } | |
| /// Describes which `llvm-profdata` binary should be used for merging PGO profiles. | |
| enum LlvmProfdata { | |
| /// Use llvm-profdata from the host toolchain (i.e. from LLVM provided externally). | |
| Host, | |
| /// Use llvm-profdata from the target toolchain (i.e. from LLVM built from `src/llvm-project`). | |
| Target, | |
| } | |
| fn merge_llvm_profiles( | |
| env: &Environment, | |
| merged_path: &Utf8Path, | |
| profile_dir: &Utf8Path, | |
| profdata: LlvmProfdata, | |
| ) -> anyhow::Result<()> { | |
| let llvm_profdata = match profdata { | |
| LlvmProfdata::Host => { | |
| env.host_llvm_dir().join(format!("bin/llvm-profdata{}", executable_extension())) | |
| } | |
| LlvmProfdata::Target => env | |
| .build_artifacts() | |
| .join("llvm") | |
| .join("build") | |
| .join(format!("bin/llvm-profdata{}", executable_extension())), | |
| }; | |
| cmd(&[llvm_profdata.as_str(), "merge", "-o", merged_path.as_str(), profile_dir.as_str()]) | |
| .run() | |
| .context("Cannot merge LLVM profiles")?; | |
| Ok(()) | |
| } | |
| fn log_profile_stats( | |
| name: &str, | |
| merged_profile: &Utf8Path, | |
| profile_root: &Utf8Path, | |
| ) -> anyhow::Result<()> { | |
| log::info!("{name} PGO statistics"); | |
| log::info!( | |
| "{merged_profile}: {}", | |
| humansize::format_size(std::fs::metadata(merged_profile.as_std_path())?.len(), BINARY) | |
| ); | |
| log::info!( | |
| "{profile_root}: {}", | |
| humansize::format_size(fs_extra::dir::get_size(profile_root.as_std_path())?, BINARY) | |
| ); | |
| log::info!("Profile file count: {}", count_files(profile_root)?); | |
| Ok(()) | |
| } | |
| pub fn llvm_benchmarks(env: &Environment) -> CmdBuilder { | |
| init_compiler_benchmarks(env, &["Debug", "Opt"], &["Full"], LLVM_PGO_CRATES) | |
| } | |
| pub fn rustc_benchmarks(env: &Environment) -> CmdBuilder { | |
| init_compiler_benchmarks(env, &["Check", "Debug", "Opt"], &["All"], RUSTC_PGO_CRATES) | |
| } | |
| pub struct LlvmPGOProfile(pub Utf8PathBuf); | |
| pub fn gather_llvm_profiles( | |
| env: &Environment, | |
| profile_root: &Utf8Path, | |
| ) -> anyhow::Result<LlvmPGOProfile> { | |
| log::info!("Running benchmarks with PGO instrumented LLVM"); | |
| with_log_group("Running benchmarks", || { | |
| llvm_benchmarks(env).run().context("Cannot gather LLVM PGO profiles") | |
| })?; | |
| let merged_profile = env.artifact_dir().join("llvm-pgo.profdata"); | |
| log::info!("Merging LLVM PGO profiles to {merged_profile}"); | |
| merge_llvm_profiles(env, &merged_profile, profile_root, LlvmProfdata::Host)?; | |
| log_profile_stats("LLVM", &merged_profile, profile_root)?; | |
| // We don't need the individual .profraw files now that they have been merged | |
| // into a final .profdata | |
| delete_directory(profile_root)?; | |
| Ok(LlvmPGOProfile(merged_profile)) | |
| } | |
| pub struct RustcPGOProfile(pub Utf8PathBuf); | |
| pub fn gather_rustc_profiles( | |
| env: &Environment, | |
| profile_root: &Utf8Path, | |
| ) -> anyhow::Result<RustcPGOProfile> { | |
| log::info!("Running benchmarks with PGO instrumented rustc"); | |
| // The profile data is written into a single filepath that is being repeatedly merged when each | |
| // rustc invocation ends. Empirically, this can result in some profiling data being lost. That's | |
| // why we override the profile path to include the PID. This will produce many more profiling | |
| // files, but the resulting profile will produce a slightly faster rustc binary. | |
| let profile_template = profile_root.join("default_%m_%p.profraw"); | |
| // Here we're profiling the `rustc` frontend, so we also include `Check`. | |
| // The benchmark set includes various stress tests that put the frontend under pressure. | |
| with_log_group("Running benchmarks", || { | |
| rustc_benchmarks(env) | |
| .env("LLVM_PROFILE_FILE", profile_template.as_str()) | |
| .run() | |
| .context("Cannot gather rustc PGO profiles") | |
| })?; | |
| let merged_profile = env.artifact_dir().join("rustc-pgo.profdata"); | |
| log::info!("Merging Rustc PGO profiles to {merged_profile}"); | |
| let llvm_profdata = if env.build_llvm() { LlvmProfdata::Target } else { LlvmProfdata::Host }; | |
| merge_llvm_profiles(env, &merged_profile, profile_root, llvm_profdata)?; | |
| log_profile_stats("Rustc", &merged_profile, profile_root)?; | |
| // We don't need the individual .profraw files now that they have been merged | |
| // into a final .profdata | |
| delete_directory(profile_root)?; | |
| Ok(RustcPGOProfile(merged_profile)) | |
| } | |
| pub struct BoltProfile(pub Utf8PathBuf); | |
| pub fn gather_bolt_profiles( | |
| env: &Environment, | |
| name: &str, | |
| benchmarks: CmdBuilder, | |
| profile_prefix: &Utf8Path, | |
| ) -> anyhow::Result<BoltProfile> { | |
| log::info!("Running benchmarks with BOLT instrumented {name}"); | |
| with_log_group("Running benchmarks", || { | |
| benchmarks.run().with_context(|| "Cannot gather {name} BOLT profiles") | |
| })?; | |
| let merged_profile = env.artifact_dir().join(format!("{name}-bolt.profdata")); | |
| log::info!("Merging {name} BOLT profiles from {profile_prefix} to {merged_profile}"); | |
| let profiles: Vec<_> = | |
| glob::glob(&format!("{profile_prefix}*"))?.collect::<Result<Vec<_>, _>>()?; | |
| let fdata = env.merge_fdata(); | |
| let mut merge_args = vec![fdata.as_str()]; | |
| merge_args.extend(profiles.iter().map(|p| p.to_str().unwrap())); | |
| with_log_group("Merging BOLT profiles", || { | |
| cmd(&merge_args) | |
| .redirect_output(merged_profile.clone()) | |
| .run() | |
| .context("Cannot merge BOLT profiles") | |
| })?; | |
| log::info!("{name} BOLT statistics"); | |
| log::info!( | |
| "{merged_profile}: {}", | |
| humansize::format_size(std::fs::metadata(merged_profile.as_std_path())?.len(), BINARY) | |
| ); | |
| let size = profiles | |
| .iter() | |
| .map(|p| std::fs::metadata(p).map(|metadata| metadata.len())) | |
| .collect::<Result<Vec<_>, _>>()? | |
| .into_iter() | |
| .sum::<u64>(); | |
| log::info!("{profile_prefix}: {}", humansize::format_size(size, BINARY)); | |
| log::info!("Profile file count: {}", profiles.len()); | |
| // Delete the gathered profiles | |
| for profile in glob::glob(&format!("{profile_prefix}*"))?.flatten() { | |
| if let Err(error) = std::fs::remove_file(&profile) { | |
| log::error!("Cannot delete BOLT profile {}: {error:?}", profile.display()); | |
| } | |
| } | |
| Ok(BoltProfile(merged_profile)) | |
| } |