Show finished runs
This commit is contained in:
parent
98132cc00b
commit
cf8ab2884f
8 changed files with 377 additions and 7 deletions
62
.sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json
generated
Normal file
62
.sqlx/query-26bf8f43e0fa607ddfabb0611ee95c8ed1fef2d1aa76749b660228cd38a84283.json
generated
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
26
.sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json
generated
Normal file
26
.sqlx/query-6ee70f3a692ecb2a4fadfdd28778d74f63be6af7977f73ddd647a2b86e78b49f.json
generated
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
44
.sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json
generated
Normal file
44
.sqlx/query-b65eb154a77e79de390220d3675cc2b1ebe8cb706d2b0e1a0377c88fd6a175c2.json
generated
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,15 @@ pub fn time_to_offset_datetime(time: Time) -> somehow::Result<OffsetDateTime> {
|
||||||
.to_offset(UtcOffset::from_whole_seconds(time.offset)?))
|
.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 = delta.unsigned_abs().as_secs();
|
||||||
let seconds = seconds + 30 - (seconds + 30) % 60; // To nearest minute
|
let seconds = seconds + 30 - (seconds + 30) % 60; // To nearest minute
|
||||||
if seconds == 0 {
|
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 {
|
pub fn format_time(time: OffsetDateTime) -> String {
|
||||||
let formatted_time = time
|
let formatted_time = time
|
||||||
.format(format_description!(
|
.format(format_description!(
|
||||||
|
|
@ -62,3 +65,13 @@ pub fn format_commit_short(hash: &str, message: &str) -> String {
|
||||||
let summary = format_commit_summary(message);
|
let summary = format_commit_summary(message);
|
||||||
format!("{short_hash} ({summary})")
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use self::{
|
||||||
post_api_worker_status,
|
post_api_worker_status,
|
||||||
},
|
},
|
||||||
index::get_index,
|
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},
|
queue::{get_queue, get_queue_inner},
|
||||||
worker::get_worker_by_name,
|
worker::get_worker_by_name,
|
||||||
};
|
};
|
||||||
|
|
@ -38,6 +38,7 @@ pub async fn run(server: Server) -> somehow::Result<()> {
|
||||||
.typed_get(get_index)
|
.typed_get(get_index)
|
||||||
.typed_get(get_queue)
|
.typed_get(get_queue)
|
||||||
.typed_get(get_queue_inner)
|
.typed_get(get_queue_inner)
|
||||||
|
.typed_get(get_run_by_id)
|
||||||
.typed_get(get_worker_by_name)
|
.typed_get(get_worker_by_name)
|
||||||
.typed_post(post_admin_queue_add)
|
.typed_post(post_admin_queue_add)
|
||||||
.typed_post(post_api_worker_status)
|
.typed_post(post_api_worker_status)
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
pub mod commit;
|
pub mod commit;
|
||||||
|
pub mod run;
|
||||||
|
|
|
||||||
158
src/server/web/pages/run.rs
Normal file
158
src/server/web/pages/run.rs
Normal file
|
|
@ -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<Measurement>,
|
||||||
|
output: Vec<Line>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_finished_run(
|
||||||
|
id: &str,
|
||||||
|
config: &'static Config,
|
||||||
|
db: &SqlitePool,
|
||||||
|
) -> somehow::Result<Option<Response>> {
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.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::<Vec<_>>()
|
||||||
|
.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<SqlitePool>,
|
||||||
|
) -> somehow::Result<Response> {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
65
templates/pages/run_finished.html
Normal file
65
templates/pages/run_finished.html
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Run of {{ summary }}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<h2>Run</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>run {{ id }}</span>
|
||||||
|
<dl>
|
||||||
|
<dt>Commit:</dt>
|
||||||
|
<dd>{{ commit|safe }}</dd>
|
||||||
|
|
||||||
|
<dt>Benchmark:</dt>
|
||||||
|
<dd>{{ bench_method }}</dd>
|
||||||
|
|
||||||
|
<dt>Start:</dt>
|
||||||
|
<dd>{{ start }}</dd>
|
||||||
|
|
||||||
|
<dt>End:</dt>
|
||||||
|
<dd>{{ end }}</dd>
|
||||||
|
|
||||||
|
<dt>Duration:</dt>
|
||||||
|
<dd>{{ duration }}</dd>
|
||||||
|
|
||||||
|
<dt>Exit code:</dt>
|
||||||
|
<dd>{{ exit_code }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Measurements</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>metric</th>
|
||||||
|
<th>value</th>
|
||||||
|
<th>stddev</th>
|
||||||
|
<th>unit</th>
|
||||||
|
<th>direction</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for mm in measurements %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ mm.name }}</td>
|
||||||
|
<td>{{ mm.value }}</td>
|
||||||
|
<td>{{ mm.stddev }}</td>
|
||||||
|
<td>{{ mm.unit }}</td>
|
||||||
|
<td>{{ mm.direction }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Output</h2>
|
||||||
|
|
||||||
|
<div class="run-output">
|
||||||
|
{% for line in output %}
|
||||||
|
<pre>{{ line.text }}</pre>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue