diff --git a/Cargo.lock b/Cargo.lock index 5109cfe..1827aca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -528,6 +528,9 @@ name = "deranged" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] [[package]] name = "digest" @@ -2382,6 +2385,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -2813,6 +2827,7 @@ dependencies = [ "rand", "rust-embed", "serde", + "serde_repr", "sqlx", "time", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 013b3a9..d427ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ mime_guess = "2.0.4" rand = "0.8.5" rust-embed = { version = "6.8.1", features = ["interpolate-folder-path"] } serde = { version = "1.0.181", features = ["derive"] } +serde_repr = "0.1.16" sqlx = { version = "0.7.1", features = ["runtime-tokio", "sqlite", "time"] } -time = { version = "0.3.25", features = ["formatting", "macros", "parsing"] } tokio = { version = "1.29.1", features = ["full"] } toml = "0.7.6" tracing = "0.1.37" @@ -30,6 +30,10 @@ version = "0.51.0" default-features = false features = ["max-performance-safe", "extras"] +[dependencies.time] +version = "0.3.25" +features = ["formatting", "parsing", "macros", "serde-human-readable"] + [build-dependencies] vergen = { version = "8.2.4", features = ["git", "gitcl"] } walkdir = "2.3.3" diff --git a/src/main.rs b/src/main.rs index 7fabdbe..7cf839a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod config; mod runner; mod server; mod somehow; +mod shared; use std::{io, process}; diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..a5112f8 --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,129 @@ +//! Data structures modelling the communication between server and runner. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use time::OffsetDateTime; + +#[derive(Serialize_repr, Deserialize_repr)] +#[repr(i8)] +pub enum Direction { + LessIsBetter = -1, + Neutral = 0, + MoreIsBetter = 1, +} + +#[derive(Serialize, Deserialize)] +pub struct Measurement { + pub value: f64, + pub stddev: Option, + pub unit: Option, + pub direction: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "snake_case")] +#[serde(tag = "type")] +pub enum Line { + Stdout(String), + Stderr(String), +} + +#[derive(Serialize, Deserialize)] +pub struct FinishedRun { + pub id: String, + pub start: OffsetDateTime, + pub end: OffsetDateTime, + pub output: Vec, + pub exit_code: i32, + pub measurements: HashMap, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "snake_case")] +#[serde(tag = "type")] +pub enum RunnerStatus { + /// The runner is not performing any work. + Idle, + /// The runner is performing work for another server. + Busy, + /// The runner is performing work for the current server. + Working { + id: String, + hash: String, + since: OffsetDateTime, + last_lines: Vec, + }, +} + +#[derive(Serialize, Deserialize)] +pub struct Request { + /// The runner's name. + /// + /// This name is shown to the user in the UI and used to identify the runner + /// in URLs. Because of this, only these characters are allowed: + /// + /// - Letters from `a` to `z`, both lowercase and uppercase + /// - Digits from `0` to `9` + /// - The hyphen `-`, underscore `_`, and dot `.` characters + /// + /// Additionally, the name must be at least one character long. + pub name: String, + + /// Additional free-form info about the runner. + /// + /// This could for example be used to describe the runner's system specs. + pub info: Option, + + /// Secret for preventing name collisions. + pub secret: String, + + /// What the runner is currently working on. + pub status: RunnerStatus, + + /// Whether the runner wants new work from the server. + /// + /// If the server has a commit available, it should respond with a non-null + /// [`Response::work`]. + pub request_work: bool, + + /// The runner has finished a run and wants to submit the results. + pub submit_work: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "snake_case")] +#[serde(tag = "type")] +pub enum BenchMethod { + /// Use internal (deterministic) benchmarking code. + Internal, + /// Use a commit from a bench repo. + BenchRepo { hash: String }, +} + +#[derive(Serialize, Deserialize)] +pub struct Work { + /// Hash of commit to benchmark. + pub hash: String, + /// How to benchmark the commit. + pub bench: BenchMethod, +} + +#[derive(Serialize, Deserialize)] +pub struct Response { + /// Work the runner requested using [`Request::request_work]. + /// + /// The runner may ignore this work and do something else. However, until + /// the next update request sent by the runner, the server will consider the + /// runner as preparing to work on the commit, and will not give out the + /// same commit to other runners. + pub work: Option, + + /// The runner should abort the current run. + /// + /// The server may send this because it detected the runner is benchmarking + /// the same commit as another runner and has broken the tie in favor of the + /// other runner. The runner may continue the run despite this flag. + pub abort_work: bool, +}