diff options
author | iximeow <git@iximeow.net> | 2022-12-29 19:08:01 +0000 |
---|---|---|
committer | iximeow <git@iximeow.net> | 2022-12-29 19:08:01 +0000 |
commit | 0832c96c116f4c3a28bd22a0cdd287a3b0764c7b (patch) | |
tree | 9b58beaf733a7ded11eb8519d37d41653f1e77fe | |
parent | e28b277980763b88d2828812bff2c0b9546d3d25 (diff) |
record and present metrics
-rw-r--r-- | src/ci_driver.rs | 11 | ||||
-rw-r--r-- | src/ci_runner.rs | 3 | ||||
-rw-r--r-- | src/dbctx.rs | 35 | ||||
-rw-r--r-- | src/main.rs | 21 | ||||
-rw-r--r-- | src/sql.rs | 11 |
5 files changed, 80 insertions, 1 deletions
diff --git a/src/ci_driver.rs b/src/ci_driver.rs index 47892d6..d0e56cb 100644 --- a/src/ci_driver.rs +++ b/src/ci_driver.rs @@ -243,6 +243,17 @@ impl ClientJob { "object_id": "10", })).await.unwrap(); }, + "metric" => { + let name = msg.as_object().unwrap().get("name").unwrap().as_str().unwrap(); + let value = msg.as_object().unwrap().get("value").unwrap().as_str().unwrap(); + + self.dbctx.insert_metric(self.job.id, name, value) + .expect("TODO handle metric insert error?"); + } + "command" => { + // record information about commands, start/stop, etc. probably also allow + // artifacts to be attached to commands and default to attaching stdout/stderr? + } other => { eprintln!("unhandled message kind {:?} ({:?})", msg_kind, msg); } diff --git a/src/ci_runner.rs b/src/ci_runner.rs index a47d298..97d8505 100644 --- a/src/ci_runner.rs +++ b/src/ci_runner.rs @@ -82,7 +82,8 @@ impl RunningJob { async fn send_metric(&mut self, name: &str, value: String) -> Result<(), String> { self.client.send(serde_json::json!({ "kind": "metric", - "value": value.to_string(), + "name": name, + "value": value, })).await .map_err(|e| format!("failed to send metric {}: {:?})", name, e)) } diff --git a/src/dbctx.rs b/src/dbctx.rs index 804f083..67740e8 100644 --- a/src/dbctx.rs +++ b/src/dbctx.rs @@ -39,6 +39,14 @@ pub enum TokenValidity { } #[derive(Debug, Clone, PartialEq, Eq)] +pub struct MetricRecord { + pub id: u64, + pub job_id: u64, + pub name: String, + pub value: String +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ArtifactRecord { pub id: u64, pub job_id: u64, @@ -58,6 +66,7 @@ impl DbCtx { let conn = self.conn.lock().unwrap(); conn.execute(sql::CREATE_ARTIFACTS_TABLE, ()).unwrap(); conn.execute(sql::CREATE_JOBS_TABLE, ()).unwrap(); + conn.execute(sql::CREATE_METRICS_TABLE, ()).unwrap(); conn.execute(sql::CREATE_COMMITS_TABLE, ()).unwrap(); conn.execute(sql::CREATE_REPOS_TABLE, ()).unwrap(); conn.execute(sql::CREATE_REPO_NAME_INDEX, ()).unwrap(); @@ -67,6 +76,17 @@ impl DbCtx { Ok(()) } + pub fn insert_metric(&self, job_id: u64, name: &str, value: &str) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + conn + .execute( + "insert into metrics (job_id, name, value) values (?1, ?2, ?3) on conflict (job_id, name) do update set value=excluded.value", + (job_id, name, value) + ) + .expect("can upsert"); + Ok(()) + } + pub fn new_commit(&self, sha: &str) -> Result<u64, String> { let conn = self.conn.lock().unwrap(); conn @@ -218,6 +238,21 @@ impl DbCtx { Ok(conn.last_insert_rowid() as u64) } + pub fn metrics_for_job(&self, job: u64) -> Result<Vec<MetricRecord>, String> { + let conn = self.conn.lock().unwrap(); + + let mut metrics_query = conn.prepare(sql::METRICS_FOR_JOB).unwrap(); + let mut result = metrics_query.query([job]).unwrap(); + let mut metrics = Vec::new(); + + while let Some(row) = result.next().unwrap() { + let (id, job_id, name, value): (u64, u64, String, String) = row.try_into().unwrap(); + metrics.push(MetricRecord { id, job_id, name, value }); + } + + Ok(metrics) + } + pub fn artifacts_for_job(&self, job: u64) -> Result<Vec<ArtifactRecord>, String> { let conn = self.conn.lock().unwrap(); diff --git a/src/main.rs b/src/main.rs index a96c5e4..f14b325 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use hmac::{Hmac, Mac}; use sha2::Sha256; +mod io; mod sql; mod notifier; mod dbctx; @@ -245,6 +246,23 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State( None }; + let metrics = ctx.metrics_for_job(job_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 mut html = String::new(); html.push_str("<html>\n"); html.push_str(&format!(" {}\n", head)); @@ -262,6 +280,9 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State( html.push_str(" <div>last build output</div>\n"); html.push_str(&format!(" {}\n", output)); } + if let Some(metrics) = metrics_section { + html.push_str(&metrics); + } html.push_str(" </body>\n"); html.push_str("</html>"); @@ -50,6 +50,14 @@ pub const CREATE_JOBS_TABLE: &'static str = "\ build_result INTEGER, final_status TEXT);"; +pub const CREATE_METRICS_TABLE: &'static str = "\ + CREATE TABLE IF NOT EXISTS metrics (id INTEGER PRIMARY KEY AUTOINCREMENT, + job_id INTEGER, + name TEXT, + value TEXT, + UNIQUE(job_id, name) + );"; + pub const CREATE_COMMITS_TABLE: &'static str = "\ CREATE TABLE IF NOT EXISTS commits (id INTEGER PRIMARY KEY AUTOINCREMENT, sha TEXT UNIQUE);"; @@ -90,6 +98,9 @@ pub const PENDING_JOBS: &'static str = "\ pub const LAST_ARTIFACTS_FOR_JOB: &'static str = "\ select * from artifacts where job_id=?1 and (name like \"%(stderr)%\" or name like \"%(stdout)%\") order by id desc limit 2;"; +pub const METRICS_FOR_JOB: &'static str = "\ + select * from metrics where job_id=?1 order by id asc;"; + pub const COMMIT_TO_ID: &'static str = "\ select id from commits where sha=?1;"; |