summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2023-07-04 14:21:58 -0700
committeriximeow <me@iximeow.net>2023-07-04 14:21:58 -0700
commitd0e845d3ac4530cf281e90d8a3634355d153c8be (patch)
tree6d679b58170adf6b9c8bdc2e53e836413c6458ed
parent0f97747c2e3e6f3178e414adc5b6022ead275601 (diff)
adjust metrics to show multi-host summaries?
-rw-r--r--src/dbctx.rs28
-rw-r--r--src/main.rs120
-rw-r--r--src/sql.rs20
3 files changed, 151 insertions, 17 deletions
diff --git a/src/dbctx.rs b/src/dbctx.rs
index ae06d21..0c7d488 100644
--- a/src/dbctx.rs
+++ b/src/dbctx.rs
@@ -666,6 +666,34 @@ impl DbCtx {
.map_err(|e| e.to_string())
}
+ pub fn host_model_info(&self, host_id: u64) -> Result<(String, String, String, String), String> {
+ let conn = self.conn.lock().unwrap();
+ conn
+ .query_row("select hostname, cpu_vendor_id, cpu_family, cpu_model from hosts;", [host_id], |row| {
+ Ok((
+ row.get(0).unwrap(),
+ row.get(1).unwrap(),
+ row.get(2).unwrap(),
+ row.get(3).unwrap(),
+ ))
+ })
+ .map_err(|e| e.to_string())
+ }
+
+ pub fn runs_for_job_one_per_host(&self, job_id: u64) -> Result<Vec<Run>, String> {
+ let conn = self.conn.lock().unwrap();
+ let mut runs_query = conn.prepare(crate::sql::RUNS_FOR_JOB).unwrap();
+ let mut runs_results = runs_query.query([job_id]).unwrap();
+
+ let mut results = Vec::new();
+
+ while let Some(row) = runs_results.next().unwrap() {
+ results.push(crate::sql::row2run(row));
+ }
+
+ Ok(results)
+ }
+
pub fn last_run_for_job(&self, job_id: u64) -> Result<Option<Run>, String> {
let conn = self.conn.lock().unwrap();
diff --git a/src/main.rs b/src/main.rs
index b6e744b..2658e9d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@
use chrono::{Utc, TimeZone};
use lazy_static::lazy_static;
use std::sync::RwLock;
+use std::collections::HashMap;
use serde_derive::{Deserialize, Serialize};
use tokio::spawn;
use std::path::PathBuf;
@@ -612,22 +613,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
}
}
- let metrics = ctx.dbctx.metrics_for_run(run.id).unwrap();
- let metrics_section = if metrics.len() > 0 {
- let mut section = String::new();
- section.push_str("<div>");
- section.push_str("<h3>metrics</h3>");
- section.push_str("<table style='font-family: monospace;'>");
- section.push_str("<tr><th>name</th><th>value</th></tr>");
- for metric in metrics {
- section.push_str(&format!("<tr><td>{}</td><td>{}</td></tr>", &metric.name, &metric.value));
- }
- section.push_str("</table>");
- section.push_str("</div>");
- Some(section)
- } else {
- None
- };
+ let metrics = summarize_job_metrics(&ctx.dbctx, run.id, run.job_id).unwrap();
let mut html = String::new();
html.push_str("<html>\n");
@@ -646,7 +632,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
html.push_str(" <div>artifacts</div>\n");
html.push_str(&artifacts_fragment);
}
- if let Some(metrics) = metrics_section {
+ if let Some(metrics) = metrics {
html.push_str(&metrics);
}
html.push_str(" </body>\n");
@@ -655,6 +641,86 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
(StatusCode::OK, Html(html))
}
+fn summarize_job_metrics(dbctx: &Arc<DbCtx>, run_id: u64, job_id: u64) -> Result<Option<String>, String> {
+ let runs = dbctx.runs_for_job_one_per_host(job_id)?;
+
+ let mut section = String::new();
+ section.push_str("<div>\n");
+ section.push_str("<h3>metrics</h3>\n");
+ section.push_str("<table style='font-family: monospace;'>\n");
+
+ if runs.len() == 1 {
+ let metrics = dbctx.metrics_for_run(run_id).unwrap();
+ if metrics.is_empty() {
+ return Ok(None);
+ }
+
+ section.push_str("<tr><th>name</th><th>value</th></tr>");
+ for metric in metrics {
+ section.push_str(&format!("<tr><td>{}</td><td>{}</td></tr>", &metric.name, &metric.value));
+ }
+ } else {
+ // very silly ordering issue: need an authoritative ordering of metrics to display metrics
+ // in a consistent order across runs (though they SHOULD all be ordered the same).
+ //
+ // the first run might not have all metrics (first run could be on the slowest build host
+ // f.ex), so for now just assume that metrics *will* be consistently ordered and build a
+ // list of metrics from the longest list of metrics we've seen. builders do not support
+ // concurrency so at least the per-run metrics-order-consistency assumption should hold..
+ let mut all_names: Vec<String> = Vec::new();
+
+ let all_metrics: Vec<(HashMap<String, String>, HostDesc)> = runs.iter().map(|run| {
+ let metrics = dbctx.metrics_for_run(run.id).unwrap();
+
+ let mut metrics_map = HashMap::new();
+ for metric in metrics.into_iter() {
+ if !all_names.contains(&metric.name) {
+ all_names.push(metric.name.clone());
+ }
+ metrics_map.insert(metric.name, metric.value);
+ }
+
+ let (hostname, cpu_vendor_id, cpu_family, cpu_model) = match run.host_id {
+ Some(host_id) => {
+ dbctx.host_model_info(host_id).unwrap()
+ }
+ None => {
+ ("unknown".to_string(), "unknown".to_string(), "0".to_string(), "0".to_string())
+ }
+ };
+
+ (metrics_map, HostDesc::from_parts(hostname, cpu_vendor_id, cpu_family, cpu_model))
+ }).collect();
+
+ if all_metrics.is_empty() {
+ return Ok(None);
+ }
+
+ let mut header = "<tr><th>name</th>".to_string();
+ for (_, host) in all_metrics.iter() {
+ header.push_str(&format!("<th>{} - {}</th>", &host.hostname, &host.cpu_desc));
+ }
+ header.push_str("</tr>\n");
+ section.push_str(&header);
+
+ for name in all_names.iter() {
+ let mut row = format!("<tr><td>{}</td>", &name);
+ for (metrics, _) in all_metrics.iter() {
+ let value = metrics.get(name)
+ .map(|x| x.clone())
+ .unwrap_or_else(String::new);
+ row.push_str(&format!("<td>{}</td>", value));
+ }
+ row.push_str("</tr>\n");
+ section.push_str(&row);
+ }
+ };
+ section.push_str("</table>\n");
+ section.push_str("</div>\n");
+
+ Ok(Some(section))
+}
+
async fn handle_get_artifact(Path(path): Path<(String, String)>, State(ctx): State<WebserverState>) -> impl IntoResponse {
eprintln!("get artifact, run={}, artifact={}", path.0, path.1);
let run: u64 = path.0.parse().unwrap();
@@ -1002,3 +1068,23 @@ async fn main() {
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
}
}
+
+struct HostDesc {
+ hostname: String,
+ cpu_desc: String,
+}
+impl HostDesc {
+ fn from_parts(hostname: String, vendor_id: String, cpu_family: String, model: String) -> Self {
+ let cpu_desc = match (vendor_id.as_str(), cpu_family.as_str(), model.as_str()) {
+ ("Arm Limited", "8", "0xd03") => "aarch64 A53".to_string(),
+ ("GenuineIntel", "6", "85") => "x86_64 Skylake".to_string(),
+ ("AuthenticAMD", "23", "113") => "x86_64 Matisse".to_string(),
+ (vendor, family, model) => format!("unknown {}:{}:{}", vendor, family, model)
+ };
+
+ HostDesc {
+ hostname,
+ cpu_desc,
+ }
+ }
+}
diff --git a/src/sql.rs b/src/sql.rs
index 89dc381..34bc8aa 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -176,6 +176,12 @@ pub const JOB_BY_ID: &'static str = "\
pub const METRICS_FOR_RUN: &'static str = "\
select * from metrics where run_id=?1 order by id asc;";
+pub const METRICS_FOR_JOB: &'static str = "\
+ select metrics.id, metrics.run_id, metrics.name, metrics.value from metrics \
+ join runs on runs.id=metrics.run_id \
+ where runs.job_id=?1 \
+ order by metrics.run_id desc, metrics.id desc;";
+
pub const COMMIT_TO_ID: &'static str = "\
select id from commits where sha=?1;";
@@ -202,6 +208,20 @@ pub const LAST_RUN_FOR_JOB: &'static str = "\
build_result,
final_status from runs where job_id=?1 order by started_time desc limit 1;";
+pub const RUNS_FOR_JOB: &'static str = "\
+ select id,
+ job_id,
+ artifacts_path,
+ state,
+ host_id,
+ build_token,
+ created_time,
+ started_time,
+ complete_time,
+ run_timeout,
+ build_result,
+ final_status from runs where job_id=?1 group by host_id order by started_time desc, state asc;";
+
pub const SELECT_ALL_RUNS_WITH_JOB_INFO: &'static str = "\
select jobs.id as job_id, runs.id as run_id, runs.state, runs.created_time, jobs.commit_id, jobs.run_preferences
from jobs join runs on jobs.id=runs.job_id