aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/metrics.yaml38
-rw-r--r--xtask/src/lib.rs1
-rw-r--r--xtask/src/main.rs2
-rw-r--r--xtask/src/metrics.rs214
4 files changed, 255 insertions, 0 deletions
diff --git a/.github/workflows/metrics.yaml b/.github/workflows/metrics.yaml
new file mode 100644
index 000000000..e51c62bb4
--- /dev/null
+++ b/.github/workflows/metrics.yaml
@@ -0,0 +1,38 @@
1name: rustdoc
2on:
3 push:
4 branches:
5 - master
6
7env:
8 CARGO_INCREMENTAL: 0
9 CARGO_NET_RETRY: 10
10 RUSTFLAGS: -D warnings
11 RUSTUP_MAX_RETRIES: 10
12
13jobs:
14 rustdoc:
15 runs-on: ubuntu-latest
16
17 steps:
18 - name: Checkout repository
19 uses: actions/checkout@v2
20
21 - name: Checkout metrics repository
22 uses: actions/checkout@v2
23 with:
24 repository: "rust-analyzer/metrics"
25 path: "target/metrics"
26
27 - name: Install Rust toolchain
28 uses: actions-rs/toolchain@v1
29 with:
30 toolchain: stable
31 profile: minimal
32 override: true
33 components: rust-src
34
35 - name: Collect metrics
36 run: cargo xtask metrics
37 env:
38 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 94d451e23..2fdb08f2e 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -7,6 +7,7 @@ pub mod install;
7pub mod release; 7pub mod release;
8pub mod dist; 8pub mod dist;
9pub mod pre_commit; 9pub mod pre_commit;
10pub mod metrics;
10 11
11pub mod codegen; 12pub mod codegen;
12mod ast_src; 13mod ast_src;
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index fab984fc0..604954269 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -15,6 +15,7 @@ use xtask::{
15 codegen::{self, Mode}, 15 codegen::{self, Mode},
16 dist::DistCmd, 16 dist::DistCmd,
17 install::{ClientOpt, InstallCmd, Malloc, ServerOpt}, 17 install::{ClientOpt, InstallCmd, Malloc, ServerOpt},
18 metrics::run_metrics,
18 not_bash::pushd, 19 not_bash::pushd,
19 pre_commit, project_root, 20 pre_commit, project_root,
20 release::{PromoteCmd, ReleaseCmd}, 21 release::{PromoteCmd, ReleaseCmd},
@@ -117,6 +118,7 @@ FLAGS:
117 args.finish()?; 118 args.finish()?;
118 DistCmd { nightly, client_version }.run() 119 DistCmd { nightly, client_version }.run()
119 } 120 }
121 "metrics" => run_metrics(),
120 _ => { 122 _ => {
121 eprintln!( 123 eprintln!(
122 "\ 124 "\
diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs
new file mode 100644
index 000000000..c2b6c000f
--- /dev/null
+++ b/xtask/src/metrics.rs
@@ -0,0 +1,214 @@
1use std::{
2 collections::BTreeMap,
3 env,
4 fmt::{self, Write as _},
5 io::Write as _,
6 time::{Instant, SystemTime, UNIX_EPOCH},
7};
8
9use anyhow::{bail, format_err, Result};
10
11use crate::not_bash::{fs2, pushd, rm_rf, run};
12
13type Unit = &'static str;
14
15pub fn run_metrics() -> Result<()> {
16 let mut metrics = Metrics::new()?;
17 metrics.measure_build()?;
18
19 {
20 let _d = pushd("target/metrics");
21 let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
22 writeln!(file, "{}", metrics.json())?;
23 run!("git commit -am'📈'")?;
24
25 if let Ok(actor) = env::var("GITHUB_ACTOR") {
26 let token = env::var("GITHUB_TOKEN").unwrap();
27 let repo = format!("https://{}:{}@github.com/rust-analyzer/metrics.git", actor, token);
28 run!("git push {}", repo)?;
29 }
30 }
31 eprintln!("{:#?}\n", metrics);
32 eprintln!("{}", metrics.json());
33 Ok(())
34}
35
36impl Metrics {
37 fn measure_build(&mut self) -> Result<()> {
38 run!("cargo fetch")?;
39 rm_rf("./target/release")?;
40
41 let build = Instant::now();
42 run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?;
43 let build = build.elapsed();
44 self.report("build", build.as_millis() as u64, "ms");
45 Ok(())
46 }
47}
48
49#[derive(Debug)]
50struct Metrics {
51 host: Host,
52 timestamp: SystemTime,
53 revision: String,
54 metrics: BTreeMap<String, (u64, Unit)>,
55}
56
57#[derive(Debug)]
58struct Host {
59 os: String,
60 cpu: String,
61 mem: String,
62}
63
64impl Metrics {
65 fn new() -> Result<Metrics> {
66 let host = Host::new()?;
67 let timestamp = SystemTime::now();
68 let revision = run!("git rev-parse HEAD")?;
69 Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() })
70 }
71
72 fn report(&mut self, name: &str, value: u64, unit: Unit) {
73 self.metrics.insert(name.into(), (value, unit));
74 }
75
76 fn json(&self) -> Json {
77 let mut json = Json::default();
78 self.to_json(&mut json);
79 json
80 }
81 fn to_json(&self, json: &mut Json) {
82 json.begin_object();
83 {
84 json.field("host");
85 self.host.to_json(json);
86
87 json.field("timestamp");
88 let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
89 json.number(timestamp.as_secs() as f64);
90
91 json.field("revision");
92 json.string(&self.revision);
93
94 json.field("metrics");
95 json.begin_object();
96 {
97 for (k, &(value, unit)) in &self.metrics {
98 json.field(k);
99 json.begin_array();
100 {
101 json.number(value as f64);
102 json.string(unit);
103 }
104 json.end_array();
105 }
106 }
107 json.end_object()
108 }
109 json.end_object();
110 }
111}
112
113impl Host {
114 fn new() -> Result<Host> {
115 if cfg!(not(target_os = "linux")) {
116 bail!("can only collect metrics on Linux ");
117 }
118
119 let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
120
121 let cpu =
122 read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();
123
124 let mem = read_field("/proc/meminfo", "MemTotal:")?;
125
126 return Ok(Host { os, cpu, mem });
127
128 fn read_field<'a>(path: &str, field: &str) -> Result<String> {
129 let text = fs2::read_to_string(path)?;
130
131 let line = text
132 .lines()
133 .find(|it| it.starts_with(field))
134 .ok_or_else(|| format_err!("can't parse {}", path))?;
135 Ok(line[field.len()..].trim().to_string())
136 }
137 }
138 fn to_json(&self, json: &mut Json) {
139 json.begin_object();
140 {
141 json.field("os");
142 json.string(&self.os);
143
144 json.field("cpu");
145 json.string(&self.cpu);
146
147 json.field("mem");
148 json.string(&self.mem);
149 }
150 json.end_object();
151 }
152}
153
154#[derive(Default)]
155struct Json {
156 object_comma: bool,
157 array_comma: bool,
158 buf: String,
159}
160
161impl Json {
162 fn begin_object(&mut self) {
163 self.object_comma = false;
164 self.buf.push('{');
165 }
166 fn end_object(&mut self) {
167 self.buf.push('}')
168 }
169 fn begin_array(&mut self) {
170 self.array_comma = false;
171 self.buf.push('[');
172 }
173 fn end_array(&mut self) {
174 self.buf.push(']')
175 }
176 fn field(&mut self, name: &str) {
177 self.object_comma();
178 self.string_token(name);
179 self.buf.push(':');
180 }
181 fn string(&mut self, value: &str) {
182 self.array_comma();
183 self.string_token(value);
184 }
185 fn string_token(&mut self, value: &str) {
186 self.buf.push('"');
187 self.buf.extend(value.escape_default());
188 self.buf.push('"');
189 }
190 fn number(&mut self, value: f64) {
191 self.array_comma();
192 write!(self.buf, "{}", value).unwrap();
193 }
194
195 fn array_comma(&mut self) {
196 if self.array_comma {
197 self.buf.push(',');
198 }
199 self.array_comma = true;
200 }
201
202 fn object_comma(&mut self) {
203 if self.object_comma {
204 self.buf.push(',');
205 }
206 self.object_comma = true;
207 }
208}
209
210impl fmt::Display for Json {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "{}", self.buf)
213 }
214}