From de714640bd744b194d1b4c918a6f44d3cba02339 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 24 Jul 2020 16:28:07 +0200 Subject: [PATCH] Add metrics --- .github/workflows/metrics.yaml | 38 ++++++ xtask/src/lib.rs | 1 + xtask/src/main.rs | 2 + xtask/src/metrics.rs | 214 +++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 .github/workflows/metrics.yaml create mode 100644 xtask/src/metrics.rs diff --git a/.github/workflows/metrics.yaml b/.github/workflows/metrics.yaml new file mode 100644 index 0000000000..e51c62bb4c --- /dev/null +++ b/.github/workflows/metrics.yaml @@ -0,0 +1,38 @@ +name: rustdoc +on: + push: + branches: + - master + +env: + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUSTFLAGS: -D warnings + RUSTUP_MAX_RETRIES: 10 + +jobs: + rustdoc: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Checkout metrics repository + uses: actions/checkout@v2 + with: + repository: "rust-analyzer/metrics" + path: "target/metrics" + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + components: rust-src + + - name: Collect metrics + run: cargo xtask metrics + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 94d451e231..2fdb08f2e1 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -7,6 +7,7 @@ pub mod install; pub mod release; pub mod dist; pub mod pre_commit; +pub mod metrics; pub mod codegen; mod ast_src; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fab984fc06..6049542692 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -15,6 +15,7 @@ use xtask::{ codegen::{self, Mode}, dist::DistCmd, install::{ClientOpt, InstallCmd, Malloc, ServerOpt}, + metrics::run_metrics, not_bash::pushd, pre_commit, project_root, release::{PromoteCmd, ReleaseCmd}, @@ -117,6 +118,7 @@ FLAGS: args.finish()?; DistCmd { nightly, client_version }.run() } + "metrics" => run_metrics(), _ => { eprintln!( "\ diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs new file mode 100644 index 0000000000..c2b6c000f0 --- /dev/null +++ b/xtask/src/metrics.rs @@ -0,0 +1,214 @@ +use std::{ + collections::BTreeMap, + env, + fmt::{self, Write as _}, + io::Write as _, + time::{Instant, SystemTime, UNIX_EPOCH}, +}; + +use anyhow::{bail, format_err, Result}; + +use crate::not_bash::{fs2, pushd, rm_rf, run}; + +type Unit = &'static str; + +pub fn run_metrics() -> Result<()> { + let mut metrics = Metrics::new()?; + metrics.measure_build()?; + + { + let _d = pushd("target/metrics"); + let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?; + writeln!(file, "{}", metrics.json())?; + run!("git commit -am'📈'")?; + + if let Ok(actor) = env::var("GITHUB_ACTOR") { + let token = env::var("GITHUB_TOKEN").unwrap(); + let repo = format!("https://{}:{}@github.com/rust-analyzer/metrics.git", actor, token); + run!("git push {}", repo)?; + } + } + eprintln!("{:#?}\n", metrics); + eprintln!("{}", metrics.json()); + Ok(()) +} + +impl Metrics { + fn measure_build(&mut self) -> Result<()> { + run!("cargo fetch")?; + rm_rf("./target/release")?; + + let build = Instant::now(); + run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?; + let build = build.elapsed(); + self.report("build", build.as_millis() as u64, "ms"); + Ok(()) + } +} + +#[derive(Debug)] +struct Metrics { + host: Host, + timestamp: SystemTime, + revision: String, + metrics: BTreeMap, +} + +#[derive(Debug)] +struct Host { + os: String, + cpu: String, + mem: String, +} + +impl Metrics { + fn new() -> Result { + let host = Host::new()?; + let timestamp = SystemTime::now(); + let revision = run!("git rev-parse HEAD")?; + Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() }) + } + + fn report(&mut self, name: &str, value: u64, unit: Unit) { + self.metrics.insert(name.into(), (value, unit)); + } + + fn json(&self) -> Json { + let mut json = Json::default(); + self.to_json(&mut json); + json + } + fn to_json(&self, json: &mut Json) { + json.begin_object(); + { + json.field("host"); + self.host.to_json(json); + + json.field("timestamp"); + let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap(); + json.number(timestamp.as_secs() as f64); + + json.field("revision"); + json.string(&self.revision); + + json.field("metrics"); + json.begin_object(); + { + for (k, &(value, unit)) in &self.metrics { + json.field(k); + json.begin_array(); + { + json.number(value as f64); + json.string(unit); + } + json.end_array(); + } + } + json.end_object() + } + json.end_object(); + } +} + +impl Host { + fn new() -> Result { + if cfg!(not(target_os = "linux")) { + bail!("can only collect metrics on Linux "); + } + + let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string(); + + let cpu = + read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string(); + + let mem = read_field("/proc/meminfo", "MemTotal:")?; + + return Ok(Host { os, cpu, mem }); + + fn read_field<'a>(path: &str, field: &str) -> Result { + let text = fs2::read_to_string(path)?; + + let line = text + .lines() + .find(|it| it.starts_with(field)) + .ok_or_else(|| format_err!("can't parse {}", path))?; + Ok(line[field.len()..].trim().to_string()) + } + } + fn to_json(&self, json: &mut Json) { + json.begin_object(); + { + json.field("os"); + json.string(&self.os); + + json.field("cpu"); + json.string(&self.cpu); + + json.field("mem"); + json.string(&self.mem); + } + json.end_object(); + } +} + +#[derive(Default)] +struct Json { + object_comma: bool, + array_comma: bool, + buf: String, +} + +impl Json { + fn begin_object(&mut self) { + self.object_comma = false; + self.buf.push('{'); + } + fn end_object(&mut self) { + self.buf.push('}') + } + fn begin_array(&mut self) { + self.array_comma = false; + self.buf.push('['); + } + fn end_array(&mut self) { + self.buf.push(']') + } + fn field(&mut self, name: &str) { + self.object_comma(); + self.string_token(name); + self.buf.push(':'); + } + fn string(&mut self, value: &str) { + self.array_comma(); + self.string_token(value); + } + fn string_token(&mut self, value: &str) { + self.buf.push('"'); + self.buf.extend(value.escape_default()); + self.buf.push('"'); + } + fn number(&mut self, value: f64) { + self.array_comma(); + write!(self.buf, "{}", value).unwrap(); + } + + fn array_comma(&mut self) { + if self.array_comma { + self.buf.push(','); + } + self.array_comma = true; + } + + fn object_comma(&mut self) { + if self.object_comma { + self.buf.push(','); + } + self.object_comma = true; + } +} + +impl fmt::Display for Json { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.buf) + } +}