]> git.lizzy.rs Git - rust.git/blob - xtask/src/metrics.rs
Update url to make webrender build
[rust.git] / xtask / src / metrics.rs
1 use std::{
2     collections::BTreeMap,
3     env,
4     io::Write as _,
5     path::Path,
6     time::{Instant, SystemTime, UNIX_EPOCH},
7 };
8
9 use anyhow::{bail, format_err, Result};
10 use xshell::{cmd, mkdir_p, pushd, pushenv, read_file, rm_rf};
11
12 use crate::flags;
13
14 type Unit = String;
15
16 impl flags::Metrics {
17     pub(crate) fn run(self) -> Result<()> {
18         let mut metrics = Metrics::new()?;
19         if !self.dry_run {
20             rm_rf("./target/release")?;
21         }
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")
25                 .run()?;
26         }
27         {
28             let _d = pushd("./target/rustc-perf")?;
29             let revision = &metrics.perf_revision;
30             cmd!("git reset --hard {revision}").run()?;
31         }
32
33         let _env = pushenv("RA_METRICS", "1");
34
35         {
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()?;
39         }
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")?;
45
46         if !self.dry_run {
47             let _d = pushd("target")?;
48             let metrics_token = env::var("METRICS_TOKEN").unwrap();
49             cmd!(
50                 "git clone --depth 1 https://{metrics_token}@github.com/rust-analyzer/metrics.git"
51             )
52             .run()?;
53             let _d = pushd("metrics")?;
54
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 ðŸ“ˆ")
59                 .run()?;
60             cmd!("git push origin master").run()?;
61         }
62         eprintln!("{:#?}", metrics);
63         Ok(())
64     }
65 }
66
67 impl Metrics {
68     fn measure_build(&mut self) -> Result<()> {
69         eprintln!("\nMeasuring build");
70         cmd!("cargo fetch").run()?;
71
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());
76         Ok(())
77     }
78     fn measure_analysis_stats_self(&mut self) -> Result<()> {
79         self.measure_analysis_stats_path("self", ".")
80     }
81     fn measure_analysis_stats(&mut self, bench: &str) -> Result<()> {
82         self.measure_analysis_stats_path(
83             bench,
84             &format!("./target/rustc-perf/collector/benchmarks/{}", bench),
85         )
86     }
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}")
90             .read()?;
91         for (metric, value, unit) in parse_metrics(&output) {
92             self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into());
93         }
94         Ok(())
95     }
96 }
97
98 fn parse_metrics(output: &str) -> Vec<(&str, u64, &str)> {
99     output
100         .lines()
101         .filter_map(|it| {
102             let entry = it.split(':').collect::<Vec<_>>();
103             match entry.as_slice() {
104                 ["METRIC", name, value, unit] => Some((*name, value.parse().unwrap(), *unit)),
105                 _ => None,
106             }
107         })
108         .collect()
109 }
110
111 #[derive(Debug)]
112 struct Metrics {
113     host: Host,
114     timestamp: SystemTime,
115     revision: String,
116     perf_revision: String,
117     metrics: BTreeMap<String, (u64, Unit)>,
118 }
119
120 #[derive(Debug)]
121 struct Host {
122     os: String,
123     cpu: String,
124     mem: String,
125 }
126
127 impl Metrics {
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() })
134     }
135
136     fn report(&mut self, name: &str, value: u64, unit: Unit) {
137         self.metrics.insert(name.into(), (value, unit));
138     }
139
140     fn json(&self) -> String {
141         let mut buf = String::new();
142         self.to_json(write_json::object(&mut buf));
143         buf
144     }
145
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);
155         }
156     }
157 }
158
159 impl Host {
160     fn new() -> Result<Host> {
161         if cfg!(not(target_os = "linux")) {
162             bail!("can only collect metrics on Linux ");
163         }
164
165         let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
166
167         let cpu =
168             read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();
169
170         let mem = read_field("/proc/meminfo", "MemTotal:")?;
171
172         return Ok(Host { os, cpu, mem });
173
174         fn read_field(path: &str, field: &str) -> Result<String> {
175             let text = read_file(path)?;
176
177             let line = text
178                 .lines()
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())
182         }
183     }
184     fn to_json(&self, mut obj: write_json::Object<'_>) {
185         obj.string("os", &self.os).string("cpu", &self.cpu).string("mem", &self.mem);
186     }
187 }