Compare commits

...

20 commits

Author SHA1 Message Date
95480163c9
Update README.md 2025-08-03 17:27:10 +02:00
557edb1abd
Update README.md 2025-08-03 16:44:28 +02:00
316a40442e Add metrics endpoint 2024-03-31 15:44:39 +02:00
13f89b68ef
Update Dockerfile 2023-08-27 14:24:07 +02:00
8bd8676314 update 2023-08-27 13:54:40 +02:00
89f533e357
Update Dockerfile 2023-08-27 13:49:41 +02:00
e4d1c77422
Merge pull request #2 from danielhenrymantilla/patch-1
Try <!-- [skip ci] --> it
2023-01-17 17:47:20 +01:00
Daniel Henry-Mantilla
ae0ea732e9
Try <!-- [skip ci] --> it 2023-01-17 17:46:10 +01:00
9a2f8ecdbc add github link 2022-11-26 12:17:37 +01:00
4bb561df9b remove toolchains sometimes 2022-11-26 12:09:45 +01:00
43e63d28d2
use the query parameter to store the bisection id to allow refreshes 2022-11-25 22:54:09 +01:00
edcda4d5b5
tracing 2022-11-08 20:36:15 +01:00
5d3b9c99d0
Fix stupid url path thing
@danielhenrymantilla for you
2022-11-08 19:47:18 +01:00
80c6b2a8a2
make into form 2022-09-29 22:12:09 +02:00
ae9f992ef8
time and error handling 2022-09-29 21:52:56 +02:00
41396f19c7
fmt 2022-09-29 20:19:14 +02:00
e84b52a4ab Merge branch 'main' of github.com:Nilstrieb/cargo-bisect-rustc-service 2022-09-29 20:18:10 +02:00
b95c9d4b44 improvements 2022-09-29 20:14:17 +02:00
f391b59f79 run.sh 2022-09-29 18:24:32 +02:00
de7eca4ab1
Update main.rs 2022-09-29 08:25:07 +02:00
13 changed files with 1101 additions and 384 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

966
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,12 +7,16 @@ edition = "2021"
[dependencies] [dependencies]
axum = "0.5.16" axum = "0.5.16"
chrono = { version = "0.4.22", features = ["serde"] } chrono = { version = "0.4.22", features = ["serde", "clock"] }
color-eyre = "0.6.2" 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"] } serde = { version = "1.0.145", features = ["derive"] }
tempdir = "0.3.7" tempdir = "0.3.7"
tokio = { version = "1.21.2", features = ["full"] } tokio = { version = "1.21.2", features = ["full"] }
tower-http = { version = "0.3.4", features = ["trace"] }
tracing = "0.1.36" tracing = "0.1.36"
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
uuid = { version = "1.1.2", features = ["serde", "v4"] } uuid = { version = "1.1.2", features = ["serde", "v4"] }

View file

@ -1,4 +1,4 @@
FROM rust as build FROM rust:1.77 as build
RUN rustup toolchain install nightly RUN rustup toolchain install nightly
RUN rustup default nightly RUN rustup default nightly
@ -12,22 +12,30 @@ COPY Cargo.toml Cargo.lock ./
RUN mkdir src RUN mkdir src
RUN echo "fn main() {}" > src/main.rs RUN echo "fn main() {}" > src/main.rs
RUN cargo build --release -Zsparse-registry RUN cargo build --release
COPY src ./src COPY src ./src
COPY index.html index.html COPY index.html index.html
# now rebuild with the proper main # now rebuild with the proper main
RUN touch src/main.rs RUN touch src/main.rs
RUN cargo build --release -Zsparse-registry RUN cargo build --release
### RUN ### RUN
FROM rust FROM rust:1.77
RUN cargo install cargo-bisect-rustc RUN cargo install cargo-bisect-rustc
WORKDIR /app 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 COPY --from=build /app/target/release/cargo-bisect-rustc-service cargo-bisect-rustc-service
CMD ["/app/cargo-bisect-rustc-service"] CMD ["/app/cargo-bisect-rustc-service"]

View file

@ -1,4 +1,9 @@
# cargo-bisect-rustc-service # 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 a small web service that bisects rustc for you
https://nilstrieb.dev/bisect-rustc https://bisect-rustc.noratrieb.dev

View file

@ -19,6 +19,10 @@
columns: 100; columns: 100;
} }
.error {
color: red;
}
.bisect-btn { .bisect-btn {
width: 200px; width: 200px;
height: 50px; height: 50px;
@ -28,22 +32,45 @@
<body> <body>
<h1>Bisect rustc</h1> <h1>Bisect rustc</h1>
<textarea <form name="bisect" id="bisect-form">
id="code" <label for="code">Code</label>
rows="30" <br />
cols="80" <textarea
placeholder="// Rust code goes here..." id="code"
> rows="30"
fn main() {}</textarea cols="80"
> placeholder="// Rust code goes here..."
<br /> name="code"
<label for="start">Start</label> >
<input id="start" value="2022-01-01" /> struct Struct&lt;T&gt;(T);
<label for="end">End (optional)</label>
<input id="end" /> impl&lt;T&gt; Struct&lt;T&gt; {
<br /> const CONST: fn() = || {
<br /> struct _Obligation where T:;
<button class="bisect-btn" onclick="bisect()">Bisect!</button> };
}
</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 />
<br /> <br />
@ -57,66 +84,179 @@ fn main() {}</textarea
readonly readonly
></textarea> ></textarea>
<div>
Made by Nilstrieb
<a href="https://github.com/Nilstrieb/cargo-bisect-rustc-service"
>Github</a
>
</div>
<script> <script>
const BASE_URL = `${document.location}`; let BASE_URL = `${document.location}`.split("?")[0];
const code = document.getElementById("code"); if (!BASE_URL.endsWith("/")) {
const status = document.getElementById("status"); // ugh, i hate url paths and their subtle interactions with things
const result = document.getElementById("result"); BASE_URL = `${BASE_URL}/`;
const start = document.getElementById("start"); }
const end = document.getElementById("end");
// 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; 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) { if (bisecting) {
return; return;
} }
if (!start.value.trim()) { fetches.forEach((fetchId) => clearTimeout(fetchId));
alert("Must provide a start");
return;
}
bisecting = true; bisecting = true;
status.classList.remove("hidden"); htmlStatus.classList.remove("error");
result.classList.add("hidden"); htmlStatus.classList.remove("hidden");
status.innerText = "Sending request..."; htmlResult.classList.add("hidden");
htmlStatus.innerText = "Sending request...";
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append("start", start.value.trim());
if (end.value.trim()) { params.append("start", options.start);
params.append("end", end.value.trim()); params.append("kind", options.kind);
if (options.end) {
params.append("end", options.end);
} }
const fetched = await fetch(`${BASE_URL}bisect?${params}`, { const body = options.code;
method: "POST",
body: code.value,
});
const { job_id } = await fetched.json();
async function tryFetch() { let jobId;
const fetched = await fetch(`${BASE_URL}bisect/${job_id}`); try {
const response = await fetched.json(); const fetched = await fetch(`${BASE_URL}bisect?${params}`, {
method: "POST",
if (response.status.status !== "InProgress") { body,
bisecting = false; headers: {
console.log(response.status.output); "Content-Type": "application/text",
},
status.classList.add("hidden"); });
result.classList.remove("hidden"); if (!fetched.ok) {
console.error(fetched);
result.value = response.status.output; fatal("Failed to fetch", fetched);
status.innerHTML = `Bisected job ${job_id}`; return;
} else {
console.log("Waiting for bisection", response.status.status);
setTimeout(tryFetch, 3000);
} }
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> </script>
</body> </body>

4
run.sh Normal file
View 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
View file

@ -0,0 +1,3 @@
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
packages = with pkgs; [ rustup ];
}

View file

@ -1,11 +1,12 @@
use std::process::Output; use std::process::Output;
use std::{fs, process::Command, sync::mpsc}; use std::{fs, process::Command, sync::mpsc};
use chrono::{DateTime, Utc};
use color_eyre::eyre::{Context, ContextCompat}; use color_eyre::eyre::{Context, ContextCompat};
use color_eyre::Result; use color_eyre::Result;
use rusqlite::Connection; use rusqlite::Connection;
use serde::Serialize; use serde::Serialize;
use tracing::{error, info}; use tracing::{error, info, trace};
use uuid::Uuid; use uuid::Uuid;
use crate::{db, Options}; use crate::{db, Options};
@ -22,6 +23,7 @@ pub enum BisectStatus {
pub struct Bisection { pub struct Bisection {
pub id: Uuid, pub id: Uuid,
pub code: String, pub code: String,
pub time: DateTime<Utc>,
pub status: BisectStatus, pub status: BisectStatus,
} }
@ -67,41 +69,51 @@ pub fn bisect_worker(jobs: mpsc::Receiver<Job>, conn: Connection) {
Err(_) => return, Err(_) => return,
}; };
info!(id = %job.id, "Starting bisection job"); match process_job(job, &conn) {
Ok(()) => {}
let mut bisect = Bisection { Err(err) => {
id: job.id, error!(?err, "error processing bisection")
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"),
}
} }
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> { 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"); info!(state = %state.status(), "Bisection finished");
process_result(output, state).wrap_err("process result") process_result(output, state).wrap_err("process result")
@ -113,7 +125,9 @@ fn process_result(output: Output, state: JobState) -> Result<BisectStatus> {
match state { match state {
JobState::Failed => { 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"); info!(?output, "output");
Ok(BisectStatus::Error { output }) Ok(BisectStatus::Error { output })
} }
@ -127,11 +141,7 @@ fn process_result(output: Output, state: JobState) -> Result<BisectStatus> {
} }
} }
fn run_bisect_for_file( fn run_bisect_for_file(input: String, options: &Options) -> Result<(Output, JobState)> {
input: String,
start: chrono::NaiveDate,
end: Option<chrono::NaiveDate>,
) -> Result<(Output, JobState)> {
let temp_dir = tempdir::TempDir::new("bisect").wrap_err("creating tempdir")?; let temp_dir = tempdir::TempDir::new("bisect").wrap_err("creating tempdir")?;
let mut cargo_new = Command::new("cargo"); let mut cargo_new = Command::new("cargo");
cargo_new cargo_new
@ -156,12 +166,18 @@ fn run_bisect_for_file(
bisect.arg("--timeout").arg("30"); // don't hang bisect.arg("--timeout").arg("30"); // don't hang
bisect.current_dir(&cargo_dir); 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("--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")?; let output = bisect.output().wrap_err("spawning cargo-bisect-rustc")?;
if output.status.success() { if output.status.success() {

View file

@ -11,6 +11,7 @@ pub fn setup(conn: &Connection) -> color_eyre::Result<()> {
job_id STRING PRIMARY KEY, job_id STRING PRIMARY KEY,
code STRING NOT NULL, code STRING NOT NULL,
status INTEGER NOT NULL DEFAULT 0, status INTEGER NOT NULL DEFAULT 0,
time TIME NOT NULL,
stdout_stderr STRING -- stdout or stderr depending on the status 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); let (status, stdout_stderr) = status_to_sql(&bisect.status);
conn.execute( conn.execute(
"INSERT INTO bisect (job_id, code, status, stdout_stderr) VALUES (?1, ?2, ?3, ?4)", "INSERT INTO bisect (job_id, code, status, time, stdout_stderr) VALUES (?1, ?2, ?3, ?4, ?5)",
(bisect.id, &bisect.code, status, stdout_stderr), (bisect.id, &bisect.code, status, &bisect.time, stdout_stderr),
) )
.wrap_err("insert into database") .wrap_err("insert into database")
.map(drop) .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>> { pub fn get_bisections(conn: &Connection) -> color_eyre::Result<Vec<Bisection>> {
let mut select = conn 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")?; .wrap_err("preparing select")?;
let iter = select let iter = select
@ -65,13 +66,14 @@ pub fn get_bisections(conn: &Connection) -> color_eyre::Result<Vec<Bisection>> {
status: match row.get(2)? { status: match row.get(2)? {
0 => BisectStatus::InProgress, 0 => BisectStatus::InProgress,
1 => BisectStatus::Error { 1 => BisectStatus::Error {
output: row.get(3)?, output: row.get(4)?,
}, },
2 => BisectStatus::Success { 2 => BisectStatus::Success {
output: row.get(3)?, output: row.get(4)?,
}, },
_ => return Err(rusqlite::Error::InvalidQuery), // actually not lol _ => return Err(rusqlite::Error::InvalidQuery), // actually not lol
}, },
time: row.get(3)?,
}) })
}) })
.wrap_err("getting bisections from db query")?; .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>> { pub fn get_bisection(conn: &Connection, id: Uuid) -> color_eyre::Result<Option<Bisection>> {
let mut select = conn 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")?; .wrap_err("preparing select")?;
let mut iter = 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)? { status: match row.get(2)? {
0 => BisectStatus::InProgress, 0 => BisectStatus::InProgress,
1 => BisectStatus::Error { 1 => BisectStatus::Error {
output: row.get(3)?, output: row.get(4)?,
}, },
2 => BisectStatus::Success { 2 => BisectStatus::Success {
output: row.get(3)?, output: row.get(4)?,
}, },
_ => return Err(rusqlite::Error::InvalidQuery), // actually not lol _ => return Err(rusqlite::Error::InvalidQuery), // actually not lol
}, },
time: row.get(3)?,
}) })
}) })
.wrap_err("getting bisections from db query")?; .wrap_err("getting bisections from db query")?;

View file

@ -2,9 +2,14 @@
mod bisect; mod bisect;
mod db; mod db;
mod toolchain;
use std::sync::{mpsc, Arc, Mutex}; use std::{
future,
sync::{mpsc, Arc, Mutex},
};
use crate::bisect::Job;
use axum::{ use axum::{
extract::{Path, Query}, extract::{Path, Query},
http::StatusCode, http::StatusCode,
@ -12,16 +17,17 @@ use axum::{
routing::{get, post, Router}, routing::{get, post, Router},
Extension, Json, Extension, Json,
}; };
use bisect::Job;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
use rusqlite::Connection; use rusqlite::Connection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env; use std::env;
use tower_http::trace::TraceLayer;
use tracing::{error, info, metadata::LevelFilter}; use tracing::{error, info, metadata::LevelFilter};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use uuid::Uuid; use uuid::Uuid;
type SendChannel = Arc<Mutex<mpsc::Sender<Job>>>; type SendChannel = Arc<Mutex<mpsc::SyncSender<Job>>>;
type Conn = Arc<Mutex<Connection>>; type Conn = Arc<Mutex<Connection>>;
#[tokio::main] #[tokio::main]
@ -36,7 +42,23 @@ async fn main() -> color_eyre::Result<()> {
) )
.init(); .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()); 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")?; db::setup(&worker_conn).wrap_err("db setup")?;
toolchain::clean_toolchains()?;
let app = Router::new() let app = Router::new()
.route("/", get(|| async { index_html() })) .route("/", get(|| async { index_html() }))
.route("/bisect/:id", get(get_bisection)) .route("/bisect/:id", get(get_bisection))
@ -56,11 +80,12 @@ async fn main() -> color_eyre::Result<()> {
.route("/bisect", post(do_bisection)) .route("/bisect", post(do_bisection))
// this is really stupid and hacky // this is really stupid and hacky
.layer(Extension(Arc::new(Mutex::new(job_queue_send)))) .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)); 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()) axum::Server::bind(&"0.0.0.0:4000".parse().unwrap())
.serve(app.into_make_service()) .serve(app.into_make_service())
@ -97,6 +122,7 @@ async fn get_bisections(Extension(conn): Extension<Conn>) -> impl IntoResponse {
pub struct Options { pub struct Options {
start: chrono::NaiveDate, start: chrono::NaiveDate,
end: Option<chrono::NaiveDate>, end: Option<chrono::NaiveDate>,
kind: Option<String>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -109,6 +135,7 @@ async fn do_bisection(
body: String, body: String,
send_channel: Extension<SendChannel>, send_channel: Extension<SendChannel>,
) -> impl IntoResponse { ) -> impl IntoResponse {
metrics::counter!("bisections").increment(1);
let job_id = Uuid::new_v4(); let job_id = Uuid::new_v4();
let job = Job::new(job_id, body, options.0); 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
View 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))
}