6 time::{Instant, SystemTime, UNIX_EPOCH},
9 use anyhow::{bail, format_err, Result};
10 use xshell::{cmd, mkdir_p, pushd, pushenv, read_file, rm_rf};
17 pub(crate) fn run(self) -> Result<()> {
18 let mut metrics = Metrics::new()?;
20 rm_rf("./target/release")?;
22 if !Path::new("./target/rustc-perf").exists() {
23 mkdir_p("./target/rustc-perf")?;
24 cmd!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")
28 let _d = pushd("./target/rustc-perf")?;
29 let revision = &metrics.perf_revision;
30 cmd!("git reset --hard {revision}").run()?;
33 let _env = pushenv("RA_METRICS", "1");
36 // https://github.com/rust-analyzer/rust-analyzer/issues/9997
37 let _d = pushd("target/rustc-perf/collector/benchmarks/webrender")?;
38 cmd!("cargo update -p url --precise 1.6.1").run()?;
40 metrics.measure_build()?;
41 metrics.measure_analysis_stats_self()?;
42 metrics.measure_analysis_stats("ripgrep")?;
43 metrics.measure_analysis_stats("webrender")?;
44 metrics.measure_analysis_stats("diesel/diesel")?;
47 let _d = pushd("target")?;
48 let metrics_token = env::var("METRICS_TOKEN").unwrap();
50 "git clone --depth 1 https://{metrics_token}@github.com/rust-analyzer/metrics.git"
53 let _d = pushd("metrics")?;
55 let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
56 writeln!(file, "{}", metrics.json())?;
57 cmd!("git add .").run()?;
58 cmd!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")
60 cmd!("git push origin master").run()?;
62 eprintln!("{:#?}", metrics);
68 fn measure_build(&mut self) -> Result<()> {
69 eprintln!("\nMeasuring build");
70 cmd!("cargo fetch").run()?;
72 let time = Instant::now();
73 cmd!("cargo build --release --package rust-analyzer --bin rust-analyzer").run()?;
74 let time = time.elapsed();
75 self.report("build", time.as_millis() as u64, "ms".into());
78 fn measure_analysis_stats_self(&mut self) -> Result<()> {
79 self.measure_analysis_stats_path("self", ".")
81 fn measure_analysis_stats(&mut self, bench: &str) -> Result<()> {
82 self.measure_analysis_stats_path(
84 &format!("./target/rustc-perf/collector/benchmarks/{}", bench),
87 fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> {
88 eprintln!("\nMeasuring analysis-stats/{}", name);
89 let output = cmd!("./target/release/rust-analyzer -q analysis-stats --memory-usage {path}")
91 for (metric, value, unit) in parse_metrics(&output) {
92 self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into());
98 fn parse_metrics(output: &str) -> Vec<(&str, u64, &str)> {
102 let entry = it.split(':').collect::<Vec<_>>();
103 match entry.as_slice() {
104 ["METRIC", name, value, unit] => Some((*name, value.parse().unwrap(), *unit)),
114 timestamp: SystemTime,
116 perf_revision: String,
117 metrics: BTreeMap<String, (u64, Unit)>,
128 fn new() -> Result<Metrics> {
129 let host = Host::new()?;
130 let timestamp = SystemTime::now();
131 let revision = cmd!("git rev-parse HEAD").read()?;
132 let perf_revision = "c52ee623e231e7690a93be88d943016968c1036b".into();
133 Ok(Metrics { host, timestamp, revision, perf_revision, metrics: BTreeMap::new() })
136 fn report(&mut self, name: &str, value: u64, unit: Unit) {
137 self.metrics.insert(name.into(), (value, unit));
140 fn json(&self) -> String {
141 let mut buf = String::new();
142 self.to_json(write_json::object(&mut buf));
146 fn to_json(&self, mut obj: write_json::Object<'_>) {
147 self.host.to_json(obj.object("host"));
148 let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
149 obj.number("timestamp", timestamp.as_secs() as f64);
150 obj.string("revision", &self.revision);
151 obj.string("perf_revision", &self.perf_revision);
152 let mut metrics = obj.object("metrics");
153 for (k, (value, unit)) in &self.metrics {
154 metrics.array(k).number(*value as f64).string(unit);
160 fn new() -> Result<Host> {
161 if cfg!(not(target_os = "linux")) {
162 bail!("can only collect metrics on Linux ");
165 let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
168 read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();
170 let mem = read_field("/proc/meminfo", "MemTotal:")?;
172 return Ok(Host { os, cpu, mem });
174 fn read_field(path: &str, field: &str) -> Result<String> {
175 let text = read_file(path)?;
179 .find(|it| it.starts_with(field))
180 .ok_or_else(|| format_err!("can't parse {}", path))?;
181 Ok(line[field.len()..].trim().to_string())
184 fn to_json(&self, mut obj: write_json::Object<'_>) {
185 obj.string("os", &self.os).string("cpu", &self.cpu).string("mem", &self.mem);