From cf8ab2884fb3c61ecdf2dc3721ddab37f1e1428a Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 13 Aug 2023 21:42:57 +0200 Subject: [PATCH] Show finished runs --- ...95c8ed1fef2d1aa76749b660228cd38a84283.json | 62 +++++++ ...8d74f63be6af7977f73ddd647a2b86e78b49f.json | 26 +++ ...cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json | 44 +++++ src/server/util.rs | 25 ++- src/server/web.rs | 3 +- src/server/web/pages.rs | 1 + src/server/web/pages/run.rs | 158 ++++++++++++++++++ templates/pages/run_finished.html | 65 +++++++ 8 files changed, 377 insertions(+), 7 deletions(-) create mode 100644 .sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json create mode 100644 .sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json create mode 100644 .sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json create mode 100644 src/server/web/pages/run.rs create mode 100644 templates/pages/run_finished.html diff --git a/.sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json b/.sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json new file mode 100644 index 0000000..e151c36 --- /dev/null +++ b/.sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json @@ -0,0 +1,62 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, hash, bench_method, start AS \"start: time::OffsetDateTime\", end AS \"end: time::OffsetDateTime\", exit_code, message, reachable FROM runs JOIN commits USING (hash) WHERE id = ? ", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "hash", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "bench_method", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "start: time::OffsetDateTime", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "end: time::OffsetDateTime", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "exit_code", + "ordinal": 5, + "type_info": "Int64" + }, + { + "name": "message", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "reachable", + "ordinal": 7, + "type_info": "Int64" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283" +} diff --git a/.sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json b/.sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json new file mode 100644 index 0000000..ab05ac2 --- /dev/null +++ b/.sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT source, text FROM run_output WHERE id = ? ORDER BY idx ASC ", + "describe": { + "columns": [ + { + "name": "source", + "ordinal": 0, + "type_info": "Int64" + }, + { + "name": "text", + "ordinal": 1, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f" +} diff --git a/.sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json b/.sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json new file mode 100644 index 0000000..f3bbdc3 --- /dev/null +++ b/.sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT name, value, stddev, unit, direction FROM run_measurements WHERE id = ? ORDER BY name ASC ", + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "value", + "ordinal": 1, + "type_info": "Float" + }, + { + "name": "stddev", + "ordinal": 2, + "type_info": "Float" + }, + { + "name": "unit", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "direction", + "ordinal": 4, + "type_info": "Int64" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true, + true, + true + ] + }, + "hash": "b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2" +} diff --git a/src/server/util.rs b/src/server/util.rs index f76407a..37b5210 100644 --- a/src/server/util.rs +++ b/src/server/util.rs @@ -10,7 +10,15 @@ pub fn time_to_offset_datetime(time: Time) -> somehow::Result { .to_offset(UtcOffset::from_whole_seconds(time.offset)?)) } -pub fn format_delta(delta: time::Duration) -> String { +pub fn format_duration(duration: time::Duration) -> String { + let seconds = duration.unsigned_abs().as_secs(); // To nearest second + let formatted = humantime::format_duration(Duration::from_secs(seconds)); + format!("{formatted}") +} + +pub fn format_delta_from_now(time: OffsetDateTime) -> String { + let now = OffsetDateTime::now_utc(); + let delta = time - now; let seconds = delta.unsigned_abs().as_secs(); let seconds = seconds + 30 - (seconds + 30) % 60; // To nearest minute if seconds == 0 { @@ -24,11 +32,6 @@ pub fn format_delta(delta: time::Duration) -> String { } } -pub fn format_delta_from_now(time: OffsetDateTime) -> String { - let now = OffsetDateTime::now_utc(); - format_delta(time - now) -} - pub fn format_time(time: OffsetDateTime) -> String { let formatted_time = time .format(format_description!( @@ -62,3 +65,13 @@ pub fn format_commit_short(hash: &str, message: &str) -> String { let summary = format_commit_summary(message); format!("{short_hash} ({summary})") } + +pub fn format_value(value: f64) -> String { + if value.abs() >= 1e6 { + format!("{value:.3e}") + } else if value.fract() == 0.0 { + format!("{value:.0}") + } else { + format!("{value:.2}") + } +} diff --git a/src/server/web.rs b/src/server/web.rs index 71ceffe..8ddd557 100644 --- a/src/server/web.rs +++ b/src/server/web.rs @@ -21,7 +21,7 @@ use self::{ post_api_worker_status, }, index::get_index, - pages::commit::get_commit_by_hash, + pages::{commit::get_commit_by_hash, run::get_run_by_id}, queue::{get_queue, get_queue_inner}, worker::get_worker_by_name, }; @@ -38,6 +38,7 @@ pub async fn run(server: Server) -> somehow::Result<()> { .typed_get(get_index) .typed_get(get_queue) .typed_get(get_queue_inner) + .typed_get(get_run_by_id) .typed_get(get_worker_by_name) .typed_post(post_admin_queue_add) .typed_post(post_api_worker_status) diff --git a/src/server/web/pages.rs b/src/server/web/pages.rs index 17e223d..af3cdf9 100644 --- a/src/server/web/pages.rs +++ b/src/server/web/pages.rs @@ -1 +1,2 @@ pub mod commit; +pub mod run; diff --git a/src/server/web/pages/run.rs b/src/server/web/pages/run.rs new file mode 100644 index 0000000..8241a91 --- /dev/null +++ b/src/server/web/pages/run.rs @@ -0,0 +1,158 @@ +use askama::Template; +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use futures::TryStreamExt; +use sqlx::SqlitePool; + +use crate::{ + config::Config, + server::{ + util, + web::{ + base::{Base, Tab}, + link::LinkCommit, + paths::PathRunById, + }, + }, + somehow, +}; + +struct Measurement { + name: String, + value: String, + stddev: String, + unit: String, + direction: &'static str, +} + +struct Line { + err: bool, + text: String, +} + +#[derive(Template)] +#[template(path = "pages/run_finished.html")] +struct PageFinished { + base: Base, + + summary: String, + id: String, + commit: LinkCommit, + bench_method: String, + start: String, + end: String, + duration: String, + exit_code: i64, + measurements: Vec, + output: Vec, +} + +async fn from_finished_run( + id: &str, + config: &'static Config, + db: &SqlitePool, +) -> somehow::Result> { + let Some(run) = sqlx::query!( + "\ + SELECT \ + id, \ + hash, \ + bench_method, \ + start AS \"start: time::OffsetDateTime\", \ + end AS \"end: time::OffsetDateTime\", \ + exit_code, \ + message, \ + reachable \ + FROM runs \ + JOIN commits USING (hash) \ + WHERE id = ? \ + ", + id, + ) + .fetch_optional(db) + .await? + else { + return Ok(None); + }; + + let measurements = sqlx::query!( + "\ + SELECT \ + name, \ + value, \ + stddev, \ + unit, \ + direction \ + FROM run_measurements \ + WHERE id = ? \ + ORDER BY name ASC \ + ", + id, + ) + .fetch(db) + .map_ok(|r| Measurement { + name: r.name, + value: util::format_value(r.value), + stddev: r.stddev.map(util::format_value).unwrap_or_default(), + unit: r.unit.unwrap_or_default(), + direction: match r.direction { + Some(..=-1) => "less is better", + Some(0) => "neutral", + Some(1..) => "more is better", + None => "", + }, + }) + .try_collect::>() + .await?; + + let output = sqlx::query!( + "\ + SELECT source, text FROM run_output \ + WHERE id = ? \ + ORDER BY idx ASC \ + ", + id, + ) + .fetch(db) + .map_ok(|r| Line { + err: r.source != 1, + text: r.text, + }) + .try_collect::>() + .await?; + + let base = Base::new(config, Tab::None); + Ok(Some( + PageFinished { + summary: util::format_commit_summary(&run.message), + id: run.id, + commit: LinkCommit::new(&base, run.hash, &run.message, run.reachable), + bench_method: run.bench_method, + start: util::format_time(run.start), + end: util::format_time(run.end), + duration: util::format_duration(run.end - run.start), + exit_code: run.exit_code, + measurements, + output, + + base, + } + .into_response(), + )) +} + +pub async fn get_run_by_id( + path: PathRunById, + State(config): State<&'static Config>, + State(db): State, +) -> somehow::Result { + if let Some(response) = from_finished_run(&path.id, config, &db).await? { + Ok(response) + } else { + // TODO Show unfinished runs + Ok(StatusCode::NOT_FOUND.into_response()) + } +} diff --git a/templates/pages/run_finished.html b/templates/pages/run_finished.html new file mode 100644 index 0000000..0f9a310 --- /dev/null +++ b/templates/pages/run_finished.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} + +{% block title %}Run of {{ summary }}{% endblock %} + +{% block body %} + +

Run

+ +
+ run {{ id }} +
+
Commit:
+
{{ commit|safe }}
+ +
Benchmark:
+
{{ bench_method }}
+ +
Start:
+
{{ start }}
+ +
End:
+
{{ end }}
+ +
Duration:
+
{{ duration }}
+ +
Exit code:
+
{{ exit_code }}
+
+
+ +

Measurements

+ + + + + + + + + + + + + {% for mm in measurements %} + + + + + + + + {% endfor %} + +
metricvaluestddevunitdirection
{{ mm.name }}{{ mm.value }}{{ mm.stddev }}{{ mm.unit }}{{ mm.direction }}
+ +

Output

+ +
+ {% for line in output %} +
{{ line.text }}
+ {% endfor %} +
+ +{% endblock %}