Skip to content

Commit de71464

Browse files
committed
Add metrics
1 parent 14a3a71 commit de71464

File tree

4 files changed

+255
-0
lines changed

4 files changed

+255
-0
lines changed

.github/workflows/metrics.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: rustdoc
2+
on:
3+
push:
4+
branches:
5+
- master
6+
7+
env:
8+
CARGO_INCREMENTAL: 0
9+
CARGO_NET_RETRY: 10
10+
RUSTFLAGS: -D warnings
11+
RUSTUP_MAX_RETRIES: 10
12+
13+
jobs:
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 }}

xtask/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod install;
77
pub mod release;
88
pub mod dist;
99
pub mod pre_commit;
10+
pub mod metrics;
1011

1112
pub mod codegen;
1213
mod ast_src;

xtask/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use xtask::{
1515
codegen::{self, Mode},
1616
dist::DistCmd,
1717
install::{ClientOpt, InstallCmd, Malloc, ServerOpt},
18+
metrics::run_metrics,
1819
not_bash::pushd,
1920
pre_commit, project_root,
2021
release::{PromoteCmd, ReleaseCmd},
@@ -117,6 +118,7 @@ FLAGS:
117118
args.finish()?;
118119
DistCmd { nightly, client_version }.run()
119120
}
121+
"metrics" => run_metrics(),
120122
_ => {
121123
eprintln!(
122124
"\

xtask/src/metrics.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use std::{
2+
collections::BTreeMap,
3+
env,
4+
fmt::{self, Write as _},
5+
io::Write as _,
6+
time::{Instant, SystemTime, UNIX_EPOCH},
7+
};
8+
9+
use anyhow::{bail, format_err, Result};
10+
11+
use crate::not_bash::{fs2, pushd, rm_rf, run};
12+
13+
type Unit = &'static str;
14+
15+
pub 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+
36+
impl 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)]
50+
struct Metrics {
51+
host: Host,
52+
timestamp: SystemTime,
53+
revision: String,
54+
metrics: BTreeMap<String, (u64, Unit)>,
55+
}
56+
57+
#[derive(Debug)]
58+
struct Host {
59+
os: String,
60+
cpu: String,
61+
mem: String,
62+
}
63+
64+
impl 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+
113+
impl 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)]
155+
struct Json {
156+
object_comma: bool,
157+
array_comma: bool,
158+
buf: String,
159+
}
160+
161+
impl 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+
210+
impl fmt::Display for Json {
211+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212+
write!(f, "{}", self.buf)
213+
}
214+
}

0 commit comments

Comments
 (0)