Implement --perf flag to lintcheck for benchmarking (#14116)

Turns out I was completely overcomplicating myself,
there was no need for an external tool such as becnhv2
or even the original becnh, we already had the benchmarking
infrastructure right under our noses!

This PR implements a new **lintcheck** option called
--perf, using it as a flag will mean that lintcheck
builds Clippy as a release package and hooks perf to it.

The realization that lintcheck is already 90% of what
a benchmarking tool needs came to me in a dream ☁️

changelog:none
This commit is contained in:
Alex Macleod 2025-02-02 16:00:20 +00:00 committed by GitHub
commit 6d1482cd5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 10 deletions

View file

@ -2,6 +2,7 @@ use clap::{Parser, Subcommand, ValueEnum};
use std::num::NonZero;
use std::path::PathBuf;
#[allow(clippy::struct_excessive_bools)]
#[derive(Parser, Clone, Debug)]
#[command(args_conflicts_with_subcommands = true)]
pub(crate) struct LintcheckConfig {
@ -11,6 +12,9 @@ pub(crate) struct LintcheckConfig {
short = 'j',
value_name = "N",
default_value_t = 0,
default_value_if("perf", "true", Some("1")), // Limit jobs to 1 when benchmarking
conflicts_with("perf"),
required = false,
hide_default_value = true
)]
pub max_jobs: usize,
@ -46,6 +50,11 @@ pub(crate) struct LintcheckConfig {
/// Run clippy on the dependencies of crates specified in crates-toml
#[clap(long, conflicts_with("max_jobs"))]
pub recursive: bool,
/// Also produce a `perf.data` file, implies --jobs=1,
/// the `perf.data` file can be found at
/// `target/lintcheck/sources/<package>-<version>/perf.data`
#[clap(long)]
pub perf: bool,
#[command(subcommand)]
pub subcommand: Option<Commands>,
}

View file

@ -116,7 +116,25 @@ impl Crate {
clippy_args.extend(lint_levels_args.iter().map(String::as_str));
let mut cmd = Command::new("cargo");
let mut cmd;
if config.perf {
cmd = Command::new("perf");
cmd.args(&[
"record",
"-e",
"instructions", // Only count instructions
"-g", // Enable call-graph, useful for flamegraphs and produces richer reports
"--quiet", // Do not tamper with lintcheck's normal output
"-o",
"perf.data",
"--",
"cargo",
]);
} else {
cmd = Command::new("cargo");
}
cmd.arg(if config.fix { "fix" } else { "check" })
.arg("--quiet")
.current_dir(&self.path)
@ -234,12 +252,22 @@ fn normalize_diag(
}
/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
fn build_clippy() -> String {
let output = Command::new("cargo")
.args(["run", "--bin=clippy-driver", "--", "--version"])
.stderr(Stdio::inherit())
.output()
.unwrap();
fn build_clippy(release_build: bool) -> String {
let mut build_cmd = Command::new("cargo");
build_cmd.args([
"run",
"--bin=clippy-driver",
if release_build { "-r" } else { "" },
"--",
"--version",
]);
if release_build {
build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true");
}
let output = build_cmd.stderr(Stdio::inherit()).output().unwrap();
if !output.status.success() {
eprintln!("Error: Failed to compile Clippy!");
std::process::exit(1);
@ -270,13 +298,18 @@ fn main() {
#[allow(clippy::too_many_lines)]
fn lintcheck(config: LintcheckConfig) {
let clippy_ver = build_clippy();
let clippy_driver_path = fs::canonicalize(format!("target/debug/clippy-driver{EXE_SUFFIX}")).unwrap();
let clippy_ver = build_clippy(config.perf);
let clippy_driver_path = fs::canonicalize(format!(
"target/{}/clippy-driver{EXE_SUFFIX}",
if config.perf { "release" } else { "debug" }
))
.unwrap();
// assert that clippy is found
assert!(
clippy_driver_path.is_file(),
"target/debug/clippy-driver binary not found! {}",
"target/{}/clippy-driver binary not found! {}",
if config.perf { "release" } else { "debug" },
clippy_driver_path.display()
);