rust-analyzer/xtask/src/metrics.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

223 lines
7.3 KiB
Rust
Raw Normal View History

2020-07-24 14:28:07 +00:00
use std::{
collections::BTreeMap,
fs,
2020-07-24 14:28:07 +00:00
io::Write as _,
2020-07-25 08:35:45 +00:00
path::Path,
2020-07-24 14:28:07 +00:00
time::{Instant, SystemTime, UNIX_EPOCH},
};
use anyhow::format_err;
2022-03-13 21:20:51 +00:00
use xshell::{cmd, Shell};
2020-07-24 14:28:07 +00:00
use crate::flags::{self, MeasurementType};
2020-07-24 14:28:07 +00:00
type Unit = String;
2020-07-24 22:16:21 +00:00
impl flags::Metrics {
2022-03-13 21:20:51 +00:00
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
let mut metrics = Metrics::new(sh)?;
2020-07-25 08:35:45 +00:00
if !Path::new("./target/rustc-perf").exists() {
2022-03-13 21:20:51 +00:00
sh.create_dir("./target/rustc-perf")?;
cmd!(sh, "git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")
2020-10-16 17:46:03 +00:00
.run()?;
2020-07-25 08:35:45 +00:00
}
{
2022-03-13 21:20:51 +00:00
let _d = sh.push_dir("./target/rustc-perf");
2021-03-15 21:21:02 +00:00
let revision = &metrics.perf_revision;
2022-03-13 21:20:51 +00:00
cmd!(sh, "git reset --hard {revision}").run()?;
2020-07-25 08:35:45 +00:00
}
2022-03-13 21:20:51 +00:00
let _env = sh.push_env("RA_METRICS", "1");
2020-07-24 22:16:21 +00:00
let name = match &self.measurement_type {
Some(ms) => {
let name = ms.as_ref();
match ms {
MeasurementType::Build => {
metrics.measure_build(sh)?;
}
MeasurementType::RustcTests => {
metrics.measure_rustc_tests(sh)?;
}
MeasurementType::AnalyzeSelf => {
metrics.measure_analysis_stats_self(sh)?;
}
2023-09-03 09:39:29 +00:00
MeasurementType::AnalyzeRipgrep
| MeasurementType::AnalyzeWebRender
| MeasurementType::AnalyzeDiesel
| MeasurementType::AnalyzeHyper => {
metrics.measure_analysis_stats(sh, name)?;
}
};
name
}
None => {
metrics.measure_build(sh)?;
metrics.measure_rustc_tests(sh)?;
metrics.measure_analysis_stats_self(sh)?;
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeRipgrep.as_ref())?;
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeWebRender.as_ref())?;
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeDiesel.as_ref())?;
2023-09-03 09:39:29 +00:00
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeHyper.as_ref())?;
"all"
2022-03-16 16:15:44 +00:00
}
};
2022-03-16 16:15:44 +00:00
let mut file =
2024-05-30 23:18:49 +00:00
fs::File::options().write(true).create(true).open(format!("target/{name}.json"))?;
writeln!(file, "{}", metrics.json())?;
2022-03-13 21:20:51 +00:00
eprintln!("{metrics:#?}");
2020-07-24 22:16:21 +00:00
Ok(())
2020-07-24 14:28:07 +00:00
}
}
impl Metrics {
2022-03-13 21:20:51 +00:00
fn measure_build(&mut self, sh: &Shell) -> anyhow::Result<()> {
2020-07-25 08:35:45 +00:00
eprintln!("\nMeasuring build");
2022-03-13 21:20:51 +00:00
cmd!(sh, "cargo fetch").run()?;
2020-07-24 14:28:07 +00:00
2020-07-24 22:16:21 +00:00
let time = Instant::now();
2022-03-13 21:20:51 +00:00
cmd!(sh, "cargo build --release --package rust-analyzer --bin rust-analyzer").run()?;
2020-07-24 22:16:21 +00:00
let time = time.elapsed();
2020-07-25 08:35:45 +00:00
self.report("build", time.as_millis() as u64, "ms".into());
2020-07-24 22:16:21 +00:00
Ok(())
}
fn measure_rustc_tests(&mut self, sh: &Shell) -> anyhow::Result<()> {
eprintln!("\nMeasuring rustc tests");
cmd!(
sh,
"git clone --depth=1 --branch 1.76.0 https://github.com/rust-lang/rust.git --single-branch"
)
.run()?;
let output = cmd!(sh, "./target/release/rust-analyzer rustc-tests ./rust").read()?;
for (metric, value, unit) in parse_metrics(&output) {
self.report(metric, value, unit.into());
}
Ok(())
}
2022-03-13 21:20:51 +00:00
fn measure_analysis_stats_self(&mut self, sh: &Shell) -> anyhow::Result<()> {
self.measure_analysis_stats_path(sh, "self", ".")
2020-07-25 08:35:45 +00:00
}
2022-03-13 21:20:51 +00:00
fn measure_analysis_stats(&mut self, sh: &Shell, bench: &str) -> anyhow::Result<()> {
2020-07-25 08:35:45 +00:00
self.measure_analysis_stats_path(
2022-03-13 21:20:51 +00:00
sh,
2020-07-25 08:35:45 +00:00
bench,
&format!("./target/rustc-perf/collector/compile-benchmarks/{bench}"),
2020-07-25 08:35:45 +00:00
)
}
2022-03-13 21:20:51 +00:00
fn measure_analysis_stats_path(
&mut self,
sh: &Shell,
name: &str,
path: &str,
) -> anyhow::Result<()> {
assert!(Path::new(path).exists(), "unable to find bench in {path}");
2022-03-13 21:20:51 +00:00
eprintln!("\nMeasuring analysis-stats/{name}");
2024-01-17 08:43:04 +00:00
let output = cmd!(
sh,
"./target/release/rust-analyzer -q analysis-stats {path} --query-sysroot-metadata"
)
.read()?;
2020-07-25 08:35:45 +00:00
for (metric, value, unit) in parse_metrics(&output) {
2022-03-13 21:20:51 +00:00
self.report(&format!("analysis-stats/{name}/{metric}"), value, unit.into());
2020-07-25 08:35:45 +00:00
}
2020-07-24 14:28:07 +00:00
Ok(())
}
}
2020-07-25 08:35:45 +00:00
fn parse_metrics(output: &str) -> Vec<(&str, u64, &str)> {
output
.lines()
.filter_map(|it| {
let entry = it.split(':').collect::<Vec<_>>();
match entry.as_slice() {
["METRIC", name, value, unit] => Some((*name, value.parse().unwrap(), *unit)),
_ => None,
}
})
.collect()
}
2020-07-24 14:28:07 +00:00
#[derive(Debug)]
struct Metrics {
host: Host,
timestamp: SystemTime,
revision: String,
2021-03-15 21:21:02 +00:00
perf_revision: String,
2020-07-24 14:28:07 +00:00
metrics: BTreeMap<String, (u64, Unit)>,
}
#[derive(Debug)]
struct Host {
os: String,
cpu: String,
mem: String,
}
impl Metrics {
2022-03-13 21:20:51 +00:00
fn new(sh: &Shell) -> anyhow::Result<Metrics> {
let host = Host::new(sh)?;
2020-07-24 14:28:07 +00:00
let timestamp = SystemTime::now();
2022-03-13 21:20:51 +00:00
let revision = cmd!(sh, "git rev-parse HEAD").read()?;
let perf_revision = "a584462e145a0c04760fd9391daefb4f6bd13a99".into();
2021-03-15 21:21:02 +00:00
Ok(Metrics { host, timestamp, revision, perf_revision, metrics: BTreeMap::new() })
2020-07-24 14:28:07 +00:00
}
fn report(&mut self, name: &str, value: u64, unit: Unit) {
self.metrics.insert(name.into(), (value, unit));
}
2020-08-01 02:09:52 +00:00
fn json(&self) -> String {
let mut buf = String::new();
self.to_json(write_json::object(&mut buf));
buf
2020-07-24 14:28:07 +00:00
}
2020-08-01 02:09:52 +00:00
fn to_json(&self, mut obj: write_json::Object<'_>) {
self.host.to_json(obj.object("host"));
let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
obj.number("timestamp", timestamp.as_secs() as f64);
obj.string("revision", &self.revision);
2021-03-15 21:21:02 +00:00
obj.string("perf_revision", &self.perf_revision);
2020-08-01 02:09:52 +00:00
let mut metrics = obj.object("metrics");
for (k, (value, unit)) in &self.metrics {
metrics.array(k).number(*value as f64).string(unit);
2020-07-24 14:28:07 +00:00
}
}
}
impl Host {
2022-03-13 21:20:51 +00:00
fn new(sh: &Shell) -> anyhow::Result<Host> {
2020-07-24 14:28:07 +00:00
if cfg!(not(target_os = "linux")) {
return Ok(Host { os: "unknown".into(), cpu: "unknown".into(), mem: "unknown".into() });
2020-07-24 14:28:07 +00:00
}
let os = read_field(sh, "/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_owned();
2020-07-24 14:28:07 +00:00
2022-03-13 21:20:51 +00:00
let cpu = read_field(sh, "/proc/cpuinfo", "model name")?
.trim_start_matches(':')
.trim()
.to_owned();
2020-07-24 14:28:07 +00:00
2022-03-13 21:20:51 +00:00
let mem = read_field(sh, "/proc/meminfo", "MemTotal:")?;
2020-07-24 14:28:07 +00:00
return Ok(Host { os, cpu, mem });
2022-03-13 21:20:51 +00:00
fn read_field(sh: &Shell, path: &str, field: &str) -> anyhow::Result<String> {
let text = sh.read_file(path)?;
2020-07-24 14:28:07 +00:00
2022-03-13 21:20:51 +00:00
text.lines()
.find_map(|it| it.strip_prefix(field))
.map(|it| it.trim().to_owned())
2022-03-13 21:20:51 +00:00
.ok_or_else(|| format_err!("can't parse {}", path))
2020-07-24 14:28:07 +00:00
}
}
2020-08-01 02:09:52 +00:00
fn to_json(&self, mut obj: write_json::Object<'_>) {
obj.string("os", &self.os).string("cpu", &self.cpu).string("mem", &self.mem);
2020-07-24 14:28:07 +00:00
}
}