diff options
-rw-r--r-- | .github/workflows/metrics.yaml | 38 | ||||
-rw-r--r-- | xtask/src/lib.rs | 1 | ||||
-rw-r--r-- | xtask/src/main.rs | 2 | ||||
-rw-r--r-- | xtask/src/metrics.rs | 214 |
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 @@ | |||
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 }} | ||
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; | |||
7 | pub mod release; | 7 | pub mod release; |
8 | pub mod dist; | 8 | pub mod dist; |
9 | pub mod pre_commit; | 9 | pub mod pre_commit; |
10 | pub mod metrics; | ||
10 | 11 | ||
11 | pub mod codegen; | 12 | pub mod codegen; |
12 | mod ast_src; | 13 | mod 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 @@ | |||
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 | } | ||