mirror of
https://github.com/Noratrieb/cargo-bisect-rustc-service.git
synced 2026-01-15 16:45:02 +01:00
Compare commits
20 commits
v0.1-alpha
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 95480163c9 | |||
| 557edb1abd | |||
| 316a40442e | |||
| 13f89b68ef | |||
| 8bd8676314 | |||
| 89f533e357 | |||
| e4d1c77422 | |||
|
|
ae0ea732e9 | ||
| 9a2f8ecdbc | |||
| 4bb561df9b | |||
| 43e63d28d2 | |||
| edcda4d5b5 | |||
| 5d3b9c99d0 | |||
| 80c6b2a8a2 | |||
| ae9f992ef8 | |||
| 41396f19c7 | |||
| e84b52a4ab | |||
| b95c9d4b44 | |||
| f391b59f79 | |||
| de7eca4ab1 |
13 changed files with 1101 additions and 384 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use nix
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,2 @@
|
|||
/target
|
||||
bisect.sqlite
|
||||
bisect.sqlite
|
||||
|
|
|
|||
966
Cargo.lock
generated
966
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,12 +7,16 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
axum = "0.5.16"
|
||||
chrono = { version = "0.4.22", features = ["serde"] }
|
||||
chrono = { version = "0.4.22", features = ["serde", "clock"] }
|
||||
color-eyre = "0.6.2"
|
||||
rusqlite = { version = "0.28.0", features = ["bundled", "uuid"] }
|
||||
metrics = "0.22.3"
|
||||
metrics-exporter-prometheus = { version = "0.14.0", default-features = false, features = ["http-listener"] }
|
||||
once_cell = "1.19.0"
|
||||
rusqlite = { version = "0.28.0", features = ["bundled", "uuid", "chrono"] }
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
tempdir = "0.3.7"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
tower-http = { version = "0.3.4", features = ["trace"] }
|
||||
tracing = "0.1.36"
|
||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
|
|
|
|||
16
Dockerfile
16
Dockerfile
|
|
@ -1,4 +1,4 @@
|
|||
FROM rust as build
|
||||
FROM rust:1.77 as build
|
||||
|
||||
RUN rustup toolchain install nightly
|
||||
RUN rustup default nightly
|
||||
|
|
@ -12,22 +12,30 @@ COPY Cargo.toml Cargo.lock ./
|
|||
RUN mkdir src
|
||||
RUN echo "fn main() {}" > src/main.rs
|
||||
|
||||
RUN cargo build --release -Zsparse-registry
|
||||
RUN cargo build --release
|
||||
|
||||
COPY src ./src
|
||||
COPY index.html index.html
|
||||
|
||||
# now rebuild with the proper main
|
||||
RUN touch src/main.rs
|
||||
RUN cargo build --release -Zsparse-registry
|
||||
RUN cargo build --release
|
||||
|
||||
### RUN
|
||||
FROM rust
|
||||
FROM rust:1.77
|
||||
|
||||
RUN cargo install cargo-bisect-rustc
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# random user
|
||||
RUN useradd --create-home bisector
|
||||
USER bisector
|
||||
|
||||
# this server listens on port 4000 and 4001 (metrics)
|
||||
EXPOSE 4000
|
||||
EXPOSE 4001
|
||||
|
||||
COPY --from=build /app/target/release/cargo-bisect-rustc-service cargo-bisect-rustc-service
|
||||
|
||||
CMD ["/app/cargo-bisect-rustc-service"]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
# cargo-bisect-rustc-service
|
||||
|
||||
**⚠️ this service has been turned off due to lack of usage ⚠️**
|
||||
|
||||
if you think it's useful you should tell me so i can turn it on again
|
||||
|
||||
a small web service that bisects rustc for you
|
||||
|
||||
https://nilstrieb.dev/bisect-rustc
|
||||
https://bisect-rustc.noratrieb.dev
|
||||
|
|
|
|||
252
index.html
252
index.html
|
|
@ -19,6 +19,10 @@
|
|||
columns: 100;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bisect-btn {
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
|
|
@ -28,22 +32,45 @@
|
|||
<body>
|
||||
<h1>Bisect rustc</h1>
|
||||
|
||||
<textarea
|
||||
id="code"
|
||||
rows="30"
|
||||
cols="80"
|
||||
placeholder="// Rust code goes here..."
|
||||
>
|
||||
fn main() {}</textarea
|
||||
>
|
||||
<br />
|
||||
<label for="start">Start</label>
|
||||
<input id="start" value="2022-01-01" />
|
||||
<label for="end">End (optional)</label>
|
||||
<input id="end" />
|
||||
<br />
|
||||
<br />
|
||||
<button class="bisect-btn" onclick="bisect()">Bisect!</button>
|
||||
<form name="bisect" id="bisect-form">
|
||||
<label for="code">Code</label>
|
||||
<br />
|
||||
<textarea
|
||||
id="code"
|
||||
rows="30"
|
||||
cols="80"
|
||||
placeholder="// Rust code goes here..."
|
||||
name="code"
|
||||
>
|
||||
struct Struct<T>(T);
|
||||
|
||||
impl<T> Struct<T> {
|
||||
const CONST: fn() = || {
|
||||
struct _Obligation where T:;
|
||||
};
|
||||
}
|
||||
</textarea
|
||||
>
|
||||
<br />
|
||||
<label for="start">Start</label>
|
||||
<input id="start" value="2022-01-01" name="start" />
|
||||
|
||||
<label for="end">End (optional)</label>
|
||||
<input id="end" value="2022-06-01" name="end" />
|
||||
|
||||
<label for="kind">Regression Kind</label>
|
||||
<select id="kind" name="select">
|
||||
<option>error</option>
|
||||
<option>success</option>
|
||||
<option selected="selected">ice</option>
|
||||
<option>non-ice</option>
|
||||
<option>non-error</option>
|
||||
</select>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<input type="submit" class="bisect-btn" value="Bisect!" />
|
||||
</form>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
|
@ -57,66 +84,179 @@ fn main() {}</textarea
|
|||
readonly
|
||||
></textarea>
|
||||
|
||||
<div>
|
||||
Made by Nilstrieb
|
||||
<a href="https://github.com/Nilstrieb/cargo-bisect-rustc-service"
|
||||
>Github</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const BASE_URL = `${document.location}`;
|
||||
const code = document.getElementById("code");
|
||||
const status = document.getElementById("status");
|
||||
const result = document.getElementById("result");
|
||||
const start = document.getElementById("start");
|
||||
const end = document.getElementById("end");
|
||||
let BASE_URL = `${document.location}`.split("?")[0];
|
||||
if (!BASE_URL.endsWith("/")) {
|
||||
// ugh, i hate url paths and their subtle interactions with things
|
||||
BASE_URL = `${BASE_URL}/`;
|
||||
}
|
||||
|
||||
// The important HTML elements.
|
||||
const htmlStatus = document.getElementById("status");
|
||||
const htmlResult = document.getElementById("result");
|
||||
const htmlCode = document.getElementById("code");
|
||||
|
||||
// A list of all ongoing fetches in fetchAndUpdate.
|
||||
const fetches = [];
|
||||
|
||||
let bisecting = false;
|
||||
|
||||
async function bisect() {
|
||||
initialLoad();
|
||||
|
||||
document.getElementById("bisect-form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
submitForm();
|
||||
});
|
||||
|
||||
async function initialLoad() {
|
||||
const params = document.location.search;
|
||||
const urlParams = new URLSearchParams(params);
|
||||
const bisection = urlParams.get("bisection");
|
||||
|
||||
if (bisection) {
|
||||
const response = await fetchAndUpdate(bisection);
|
||||
if (!response) {
|
||||
fatal(`Bisection with ID \`${bisection}\` not found.`);
|
||||
return;
|
||||
}
|
||||
code.value = response.code;
|
||||
}
|
||||
}
|
||||
|
||||
// Report a fatal error to the console and screen.
|
||||
function fatal(err, ...extra) {
|
||||
console.error(err, ...(extra || []));
|
||||
htmlStatus.innerHTML = `ERROR: ${err}`;
|
||||
htmlStatus.classList.remove("hidden");
|
||||
htmlStatus.classList.add("error");
|
||||
bisecting = false;
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
const form = document.forms.bisect;
|
||||
const options = {
|
||||
code: form.code.value,
|
||||
start: form.start.value?.trim(),
|
||||
end: form.end.value?.trim(),
|
||||
kind: form.kind.value,
|
||||
};
|
||||
console.log("Options", options);
|
||||
|
||||
if (!options.start) {
|
||||
fatal("Must provide a start");
|
||||
return;
|
||||
}
|
||||
|
||||
bisect(options);
|
||||
}
|
||||
|
||||
async function bisect(options) {
|
||||
if (bisecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start.value.trim()) {
|
||||
alert("Must provide a start");
|
||||
return;
|
||||
}
|
||||
fetches.forEach((fetchId) => clearTimeout(fetchId));
|
||||
|
||||
bisecting = true;
|
||||
|
||||
status.classList.remove("hidden");
|
||||
result.classList.add("hidden");
|
||||
status.innerText = "Sending request...";
|
||||
htmlStatus.classList.remove("error");
|
||||
htmlStatus.classList.remove("hidden");
|
||||
htmlResult.classList.add("hidden");
|
||||
htmlStatus.innerText = "Sending request...";
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append("start", start.value.trim());
|
||||
if (end.value.trim()) {
|
||||
params.append("end", end.value.trim());
|
||||
|
||||
params.append("start", options.start);
|
||||
params.append("kind", options.kind);
|
||||
|
||||
if (options.end) {
|
||||
params.append("end", options.end);
|
||||
}
|
||||
|
||||
const fetched = await fetch(`${BASE_URL}bisect?${params}`, {
|
||||
method: "POST",
|
||||
body: code.value,
|
||||
});
|
||||
const { job_id } = await fetched.json();
|
||||
const body = options.code;
|
||||
|
||||
async function tryFetch() {
|
||||
const fetched = await fetch(`${BASE_URL}bisect/${job_id}`);
|
||||
const response = await fetched.json();
|
||||
|
||||
if (response.status.status !== "InProgress") {
|
||||
bisecting = false;
|
||||
console.log(response.status.output);
|
||||
|
||||
status.classList.add("hidden");
|
||||
result.classList.remove("hidden");
|
||||
|
||||
result.value = response.status.output;
|
||||
status.innerHTML = `Bisected job ${job_id}`;
|
||||
} else {
|
||||
console.log("Waiting for bisection", response.status.status);
|
||||
setTimeout(tryFetch, 3000);
|
||||
let jobId;
|
||||
try {
|
||||
const fetched = await fetch(`${BASE_URL}bisect?${params}`, {
|
||||
method: "POST",
|
||||
body,
|
||||
headers: {
|
||||
"Content-Type": "application/text",
|
||||
},
|
||||
});
|
||||
if (!fetched.ok) {
|
||||
console.error(fetched);
|
||||
fatal("Failed to fetch", fetched);
|
||||
return;
|
||||
}
|
||||
const data = await fetched.json();
|
||||
jobId = data?.job_id;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.set("bisection", jobId);
|
||||
window.location.search = urlParams;
|
||||
|
||||
if (!jobId) {
|
||||
fatal("Received invalid response", data);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
fatal(err);
|
||||
return;
|
||||
}
|
||||
|
||||
tryFetch();
|
||||
fetchAndUpdate(jobId);
|
||||
}
|
||||
|
||||
status.innerHTML = `Waiting for result, job id=${job_id}`;
|
||||
async function fetchAndUpdate(jobId) {
|
||||
htmlStatus.innerHTML = `Waiting for result, job id=${jobId}`;
|
||||
htmlStatus.classList.remove("hidden");
|
||||
|
||||
let response;
|
||||
try {
|
||||
const fetched = await fetch(`${BASE_URL}bisect/${jobId}`);
|
||||
if (!fetched.ok) {
|
||||
fatal("Failed to fetch bisection.", fetched);
|
||||
return;
|
||||
}
|
||||
response = await fetched.json();
|
||||
} catch (e) {
|
||||
fatal(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
console.log("Waiting for bisection", response.status.status);
|
||||
const timeout = setTimeout(() => fetchAndUpdate(jobId), 3000);
|
||||
fetches.push(timeout);
|
||||
return response;
|
||||
}
|
||||
|
||||
const status = response.status;
|
||||
|
||||
if (response.status.status !== "InProgress") {
|
||||
bisecting = false;
|
||||
console.log(response.status.output);
|
||||
|
||||
htmlStatus.classList.add("hidden");
|
||||
htmlResult.classList.remove("hidden");
|
||||
|
||||
htmlResult.value = response.status.output;
|
||||
htmlStatus.innerHTML = `Bisected job ${jobId}`;
|
||||
} else {
|
||||
console.log("Waiting for bisection", response.status.status);
|
||||
const timeout = setTimeout(() => fetchAndUpdate(jobId), 3000);
|
||||
fetches.push(timeout);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
4
run.sh
Normal file
4
run.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
docker run -d --name cargo-bisect-rustc-service --net=internal --restart=always \
|
||||
"-v=/apps/cargo-bisect-rustc-service/db:/app/db" \
|
||||
"-e=SQLITE_DB=/app/db/db.sqlite" \
|
||||
docker.nilstrieb.dev/cargo-bisect-rustc-service:1.3
|
||||
3
shell.nix
Normal file
3
shell.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
|
||||
packages = with pkgs; [ rustup ];
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
use std::process::Output;
|
||||
use std::{fs, process::Command, sync::mpsc};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::eyre::{Context, ContextCompat};
|
||||
use color_eyre::Result;
|
||||
use rusqlite::Connection;
|
||||
use serde::Serialize;
|
||||
use tracing::{error, info};
|
||||
use tracing::{error, info, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{db, Options};
|
||||
|
|
@ -22,6 +23,7 @@ pub enum BisectStatus {
|
|||
pub struct Bisection {
|
||||
pub id: Uuid,
|
||||
pub code: String,
|
||||
pub time: DateTime<Utc>,
|
||||
pub status: BisectStatus,
|
||||
}
|
||||
|
||||
|
|
@ -67,41 +69,51 @@ pub fn bisect_worker(jobs: mpsc::Receiver<Job>, conn: Connection) {
|
|||
Err(_) => return,
|
||||
};
|
||||
|
||||
info!(id = %job.id, "Starting bisection job");
|
||||
|
||||
let mut bisect = Bisection {
|
||||
id: job.id,
|
||||
code: job.code.clone(),
|
||||
status: BisectStatus::InProgress,
|
||||
};
|
||||
|
||||
match db::add_bisection(&conn, &bisect).wrap_err("insert bisection") {
|
||||
Ok(()) => {
|
||||
let status = match bisect_job(job) {
|
||||
Ok(status) => status,
|
||||
Err(err) => {
|
||||
error!(?err, "error processing bisection");
|
||||
BisectStatus::Error {
|
||||
output: err.to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bisect.status = status;
|
||||
|
||||
match db::update_bisection_status(&conn, &bisect) {
|
||||
Ok(()) => {}
|
||||
Err(err) => error!(?err, "error updating bisection"),
|
||||
}
|
||||
match process_job(job, &conn) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!(?err, "error processing bisection")
|
||||
}
|
||||
Err(err) => error!(?err, "error inserting bisection"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(job), fields(id = %job.id))]
|
||||
#[tracing::instrument(skip(job, conn), fields(id = %job.id))]
|
||||
pub fn process_job(job: Job, conn: &Connection) -> Result<()> {
|
||||
info!(id = %job.id, "Starting bisection job");
|
||||
|
||||
let mut bisect = Bisection {
|
||||
id: job.id,
|
||||
code: job.code.clone(),
|
||||
time: Utc::now(),
|
||||
status: BisectStatus::InProgress,
|
||||
};
|
||||
|
||||
db::add_bisection(&conn, &bisect).wrap_err("insert bisection")?;
|
||||
|
||||
let status = match bisect_job(job) {
|
||||
Ok(status) => status,
|
||||
Err(err) => {
|
||||
error!(?err, "error processing bisection");
|
||||
BisectStatus::Error {
|
||||
output: format!("Internal error"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bisect.status = status;
|
||||
|
||||
db::update_bisection_status(&conn, &bisect).wrap_err("writing bisection result")?;
|
||||
|
||||
trace!(?bisect, "Finished bisection job");
|
||||
|
||||
crate::toolchain::clean_toolchains()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bisect_job(job: Job) -> Result<BisectStatus> {
|
||||
let (output, state) = run_bisect_for_file(job.code, job.options.start, job.options.end)?;
|
||||
let (output, state) = run_bisect_for_file(job.code, &job.options)?;
|
||||
info!(state = %state.status(), "Bisection finished");
|
||||
|
||||
process_result(output, state).wrap_err("process result")
|
||||
|
|
@ -113,7 +125,9 @@ fn process_result(output: Output, state: JobState) -> Result<BisectStatus> {
|
|||
|
||||
match state {
|
||||
JobState::Failed => {
|
||||
let output = stderr.lines().rev().take(10).collect::<String>();
|
||||
let mut output = stderr.lines().rev().take(30).collect::<Vec<_>>();
|
||||
output.reverse();
|
||||
let output = output.join("\n");
|
||||
info!(?output, "output");
|
||||
Ok(BisectStatus::Error { output })
|
||||
}
|
||||
|
|
@ -127,11 +141,7 @@ fn process_result(output: Output, state: JobState) -> Result<BisectStatus> {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_bisect_for_file(
|
||||
input: String,
|
||||
start: chrono::NaiveDate,
|
||||
end: Option<chrono::NaiveDate>,
|
||||
) -> Result<(Output, JobState)> {
|
||||
fn run_bisect_for_file(input: String, options: &Options) -> Result<(Output, JobState)> {
|
||||
let temp_dir = tempdir::TempDir::new("bisect").wrap_err("creating tempdir")?;
|
||||
let mut cargo_new = Command::new("cargo");
|
||||
cargo_new
|
||||
|
|
@ -156,12 +166,18 @@ fn run_bisect_for_file(
|
|||
bisect.arg("--timeout").arg("30"); // don't hang
|
||||
bisect.current_dir(&cargo_dir);
|
||||
|
||||
bisect.arg("--start").arg(start.to_string());
|
||||
bisect.arg("--start").arg(options.start.to_string());
|
||||
|
||||
if let Some(end) = end {
|
||||
if let Some(end) = options.end {
|
||||
bisect.arg("--end").arg(end.to_string());
|
||||
}
|
||||
|
||||
bisect
|
||||
.arg("--regress")
|
||||
.arg(options.kind.as_deref().unwrap_or("ice"));
|
||||
|
||||
bisect.env("RUST_LOG", "error"); // overwrite RUST_LOG
|
||||
|
||||
let output = bisect.output().wrap_err("spawning cargo-bisect-rustc")?;
|
||||
|
||||
if output.status.success() {
|
||||
|
|
|
|||
19
src/db.rs
19
src/db.rs
|
|
@ -11,6 +11,7 @@ pub fn setup(conn: &Connection) -> color_eyre::Result<()> {
|
|||
job_id STRING PRIMARY KEY,
|
||||
code STRING NOT NULL,
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
time TIME NOT NULL,
|
||||
stdout_stderr STRING -- stdout or stderr depending on the status
|
||||
)",
|
||||
(),
|
||||
|
|
@ -34,8 +35,8 @@ pub fn add_bisection(conn: &Connection, bisect: &Bisection) -> color_eyre::Resul
|
|||
let (status, stdout_stderr) = status_to_sql(&bisect.status);
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO bisect (job_id, code, status, stdout_stderr) VALUES (?1, ?2, ?3, ?4)",
|
||||
(bisect.id, &bisect.code, status, stdout_stderr),
|
||||
"INSERT INTO bisect (job_id, code, status, time, stdout_stderr) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
(bisect.id, &bisect.code, status, &bisect.time, stdout_stderr),
|
||||
)
|
||||
.wrap_err("insert into database")
|
||||
.map(drop)
|
||||
|
|
@ -54,7 +55,7 @@ pub fn update_bisection_status(conn: &Connection, bisect: &Bisection) -> color_e
|
|||
|
||||
pub fn get_bisections(conn: &Connection) -> color_eyre::Result<Vec<Bisection>> {
|
||||
let mut select = conn
|
||||
.prepare("SELECT job_id, code, status, stdout_stderr FROM bisect")
|
||||
.prepare("SELECT job_id, code, status, time, stdout_stderr FROM bisect")
|
||||
.wrap_err("preparing select")?;
|
||||
|
||||
let iter = select
|
||||
|
|
@ -65,13 +66,14 @@ pub fn get_bisections(conn: &Connection) -> color_eyre::Result<Vec<Bisection>> {
|
|||
status: match row.get(2)? {
|
||||
0 => BisectStatus::InProgress,
|
||||
1 => BisectStatus::Error {
|
||||
output: row.get(3)?,
|
||||
output: row.get(4)?,
|
||||
},
|
||||
2 => BisectStatus::Success {
|
||||
output: row.get(3)?,
|
||||
output: row.get(4)?,
|
||||
},
|
||||
_ => return Err(rusqlite::Error::InvalidQuery), // actually not lol
|
||||
},
|
||||
time: row.get(3)?,
|
||||
})
|
||||
})
|
||||
.wrap_err("getting bisections from db query")?;
|
||||
|
|
@ -82,7 +84,7 @@ pub fn get_bisections(conn: &Connection) -> color_eyre::Result<Vec<Bisection>> {
|
|||
|
||||
pub fn get_bisection(conn: &Connection, id: Uuid) -> color_eyre::Result<Option<Bisection>> {
|
||||
let mut select = conn
|
||||
.prepare("SELECT job_id, code, status, stdout_stderr FROM bisect WHERE job_id = ?1")
|
||||
.prepare("SELECT job_id, code, status, time, stdout_stderr FROM bisect WHERE job_id = ?1")
|
||||
.wrap_err("preparing select")?;
|
||||
|
||||
let mut iter = select
|
||||
|
|
@ -93,13 +95,14 @@ pub fn get_bisection(conn: &Connection, id: Uuid) -> color_eyre::Result<Option<B
|
|||
status: match row.get(2)? {
|
||||
0 => BisectStatus::InProgress,
|
||||
1 => BisectStatus::Error {
|
||||
output: row.get(3)?,
|
||||
output: row.get(4)?,
|
||||
},
|
||||
2 => BisectStatus::Success {
|
||||
output: row.get(3)?,
|
||||
output: row.get(4)?,
|
||||
},
|
||||
_ => return Err(rusqlite::Error::InvalidQuery), // actually not lol
|
||||
},
|
||||
time: row.get(3)?,
|
||||
})
|
||||
})
|
||||
.wrap_err("getting bisections from db query")?;
|
||||
|
|
|
|||
42
src/main.rs
42
src/main.rs
|
|
@ -2,9 +2,14 @@
|
|||
|
||||
mod bisect;
|
||||
mod db;
|
||||
mod toolchain;
|
||||
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::{
|
||||
future,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::bisect::Job;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
http::StatusCode,
|
||||
|
|
@ -12,16 +17,17 @@ use axum::{
|
|||
routing::{get, post, Router},
|
||||
Extension, Json,
|
||||
};
|
||||
use bisect::Job;
|
||||
use color_eyre::eyre::Context;
|
||||
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
|
||||
use rusqlite::Connection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{error, info, metadata::LevelFilter};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use uuid::Uuid;
|
||||
|
||||
type SendChannel = Arc<Mutex<mpsc::Sender<Job>>>;
|
||||
type SendChannel = Arc<Mutex<mpsc::SyncSender<Job>>>;
|
||||
type Conn = Arc<Mutex<Connection>>;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -36,7 +42,23 @@ async fn main() -> color_eyre::Result<()> {
|
|||
)
|
||||
.init();
|
||||
|
||||
let (job_queue_send, job_queue_recv) = mpsc::channel();
|
||||
let metrics_app = async {
|
||||
let prom_handle = setup_metrics();
|
||||
let router: Router =
|
||||
Router::new().route("/metrics", get(move || future::ready(prom_handle.render())));
|
||||
info!("Starting up metrics server on port 4001");
|
||||
axum::Server::bind(&"0.0.0.0:4001".parse().unwrap())
|
||||
.serve(router.into_make_service())
|
||||
.await
|
||||
.wrap_err("failed to start server")
|
||||
};
|
||||
|
||||
tokio::try_join!(metrics_app, main_server())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn main_server() -> color_eyre::Result<()> {
|
||||
let (job_queue_send, job_queue_recv) = mpsc::sync_channel(10);
|
||||
|
||||
let sqlite_db = env::var("SQLITE_DB").unwrap_or_else(|_| "bisect.sqlite".to_string());
|
||||
|
||||
|
|
@ -49,6 +71,8 @@ async fn main() -> color_eyre::Result<()> {
|
|||
|
||||
db::setup(&worker_conn).wrap_err("db setup")?;
|
||||
|
||||
toolchain::clean_toolchains()?;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { index_html() }))
|
||||
.route("/bisect/:id", get(get_bisection))
|
||||
|
|
@ -56,11 +80,12 @@ async fn main() -> color_eyre::Result<()> {
|
|||
.route("/bisect", post(do_bisection))
|
||||
// this is really stupid and hacky
|
||||
.layer(Extension(Arc::new(Mutex::new(job_queue_send))))
|
||||
.layer(Extension(main_conn));
|
||||
.layer(Extension(main_conn))
|
||||
.layer(TraceLayer::new_for_http());
|
||||
|
||||
std::thread::spawn(|| bisect::bisect_worker(job_queue_recv, worker_conn));
|
||||
|
||||
info!("Starting up server");
|
||||
info!("Starting up server on port 4000");
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:4000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
|
|
@ -97,6 +122,7 @@ async fn get_bisections(Extension(conn): Extension<Conn>) -> impl IntoResponse {
|
|||
pub struct Options {
|
||||
start: chrono::NaiveDate,
|
||||
end: Option<chrono::NaiveDate>,
|
||||
kind: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -109,6 +135,7 @@ async fn do_bisection(
|
|||
body: String,
|
||||
send_channel: Extension<SendChannel>,
|
||||
) -> impl IntoResponse {
|
||||
metrics::counter!("bisections").increment(1);
|
||||
let job_id = Uuid::new_v4();
|
||||
|
||||
let job = Job::new(job_id, body, options.0);
|
||||
|
|
@ -124,3 +151,6 @@ async fn do_bisection(
|
|||
)),
|
||||
}
|
||||
}
|
||||
pub fn setup_metrics() -> PrometheusHandle {
|
||||
PrometheusBuilder::new().install_recorder().unwrap()
|
||||
}
|
||||
|
|
|
|||
73
src/toolchain.rs
Normal file
73
src/toolchain.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use std::process::Command;
|
||||
|
||||
use color_eyre::{
|
||||
eyre::{eyre, Context},
|
||||
Result,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
const MAX_BISECTOR_TOOLCHAINS: usize = 15;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn clean_toolchains() -> Result<()> {
|
||||
let toolchains = get_toolchains()?;
|
||||
let for_removal = filter_toolchain_for_removal(toolchains);
|
||||
if !for_removal.is_empty() {
|
||||
remove_toolchains(&for_removal)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_toolchain_for_removal(mut toolchains: Vec<String>) -> Vec<String> {
|
||||
toolchains.retain(|toolchain| toolchain.starts_with("bisector-"));
|
||||
|
||||
let amount = toolchains.len();
|
||||
if amount <= MAX_BISECTOR_TOOLCHAINS {
|
||||
debug!(%amount, "No toolchains removed");
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let to_remove = amount - MAX_BISECTOR_TOOLCHAINS;
|
||||
|
||||
toolchains.into_iter().take(to_remove).collect()
|
||||
}
|
||||
|
||||
fn get_toolchains() -> Result<Vec<String>> {
|
||||
let mut command = Command::new("rustup");
|
||||
command.args(["toolchain", "list"]);
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
.wrap_err("running `rustup toolchain list`")?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout =
|
||||
String::from_utf8(output.stdout).wrap_err("rustup returned non-utf-8 bytes")?;
|
||||
|
||||
let toolchains = stdout.lines().map(ToOwned::to_owned).collect();
|
||||
|
||||
Ok(toolchains)
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||
Err(eyre!("`rustup toolchain list` failed").wrap_err(stderr))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_toolchains(toolchains: &[String]) -> Result<()> {
|
||||
debug!(?toolchains, "Removing toolchains");
|
||||
let mut command = Command::new("rustup");
|
||||
command.args(["toolchain", "remove"]);
|
||||
command.args(toolchains);
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
.wrap_err("running `rustup toolchain remove`")?;
|
||||
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||
Err(eyre!("`rustup toolchain remove` failed").wrap_err(stderr))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue