From 80c62af92cf6146d96b82efb8cccae075fb4e771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 14:03:12 +0200 Subject: [PATCH 01/10] Rename `db_client` to `db_pool` --- database/src/pool.rs | 34 +++++++++++++++++----------------- database/src/tests/mod.rs | 2 +- site/src/job_queue/mod.rs | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/database/src/pool.rs b/database/src/pool.rs index 1f5587407..ec7495ddc 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -461,7 +461,7 @@ mod tests { // This is essentially testing the database testing framework is // wired up correctly. Though makes sense that there should be // an empty vector returned if there are no pstats. - let db = ctx.db_client(); + let db = ctx.db_pool(); let result = db.connection().await.get_pstats(&[], &[]).await; let expected: Vec>> = vec![]; @@ -474,7 +474,7 @@ mod tests { #[tokio::test] async fn artifact_storage() { run_db_test(|ctx| async { - let db = ctx.db_client(); + let db = ctx.db_pool(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let artifact_one = ArtifactId::from(create_commit("abc", time, CommitType::Master)); @@ -518,7 +518,7 @@ mod tests { #[tokio::test] async fn multiple_requests_same_sha() { run_postgres_test(|ctx| async { - let db = ctx.db_client(); + let db = ctx.db_pool(); let db = db.connection().await; db.insert_benchmark_request(&BenchmarkRequest::create_master( "a-sha-1", @@ -542,7 +542,7 @@ mod tests { #[tokio::test] async fn multiple_non_completed_try_requests() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let target = Target::X86_64UnknownLinuxGnu; let collector_name = "collector-1"; let benchmark_set = 1; @@ -584,7 +584,7 @@ mod tests { #[tokio::test] async fn multiple_master_requests_same_pr() { run_postgres_test(|ctx| async { - let db = ctx.db_client(); + let db = ctx.db_pool(); let db = db.connection().await; db.insert_benchmark_request(&BenchmarkRequest::create_master( @@ -613,7 +613,7 @@ mod tests { #[tokio::test] async fn load_pending_benchmark_requests() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let target = Target::X86_64UnknownLinuxGnu; let collector_name = "collector-1"; @@ -659,7 +659,7 @@ mod tests { #[tokio::test] async fn attach_shas_to_try_benchmark_request() { run_postgres_test(|ctx| async { - let db = ctx.db_client(); + let db = ctx.db_pool(); let db = db.connection().await; let req = BenchmarkRequest::create_try_without_artifacts(42, "", ""); @@ -699,7 +699,7 @@ mod tests { #[tokio::test] async fn enqueue_benchmark_job() { run_postgres_test(|ctx| async { - let db = ctx.db_client(); + let db = ctx.db_pool(); let db = db.connection().await; let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let benchmark_request = @@ -730,7 +730,7 @@ mod tests { #[tokio::test] async fn get_compile_test_cases_with_data() { run_db_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let collection = db.collection_id("test").await; let artifact = db @@ -789,7 +789,7 @@ mod tests { #[tokio::test] async fn get_collector_config_error_if_not_exist() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let collector_config_result = db.start_collector("collector-1", "foo").await.unwrap(); @@ -803,7 +803,7 @@ mod tests { #[tokio::test] async fn add_collector_config() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let mut inserted_config = db .add_collector_config("collector-1", Target::X86_64UnknownLinuxGnu, 1, true) @@ -828,7 +828,7 @@ mod tests { #[tokio::test] async fn dequeue_benchmark_job_empty_queue() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let benchmark_job_result = db .dequeue_benchmark_job( @@ -849,7 +849,7 @@ mod tests { #[tokio::test] async fn dequeue_benchmark_job() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let collector_config = db @@ -918,7 +918,7 @@ mod tests { #[tokio::test] async fn mark_request_as_complete_empty() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let insert_result = db @@ -943,7 +943,7 @@ mod tests { #[tokio::test] async fn mark_request_as_complete() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let benchmark_set = BenchmarkSet(0u32); let tag = "sha-1"; @@ -1020,7 +1020,7 @@ mod tests { #[tokio::test] async fn get_status_page_data() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let benchmark_set = BenchmarkSet(0u32); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let tag = "sha-1"; @@ -1157,7 +1157,7 @@ mod tests { #[tokio::test] async fn get_collector_configs() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let target = Target::X86_64UnknownLinuxGnu; let benchmark_set_one = BenchmarkSet(0u32); diff --git a/database/src/tests/mod.rs b/database/src/tests/mod.rs index 6b411a9d1..b4c7fe580 100644 --- a/database/src/tests/mod.rs +++ b/database/src/tests/mod.rs @@ -83,7 +83,7 @@ impl TestContext { } } - pub fn db_client(&self) -> &Pool { + pub fn db_pool(&self) -> &Pool { &self.client } diff --git a/site/src/job_queue/mod.rs b/site/src/job_queue/mod.rs index 2e697fd91..12c6e50f1 100644 --- a/site/src/job_queue/mod.rs +++ b/site/src/job_queue/mod.rs @@ -391,7 +391,7 @@ mod tests { #[tokio::test] async fn queue_ordering() { run_postgres_test(|ctx| async { - let db = ctx.db_client().connection().await; + let db = ctx.db_pool().connection().await; let target = Target::X86_64UnknownLinuxGnu; let collector_name = "collector-1"; let benchmark_set = 1; From c48489c3f53a296d421f04f5050f3fd0fc0f7191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 14:05:48 +0200 Subject: [PATCH 02/10] Rename `mark_benchmark_request_as_completed` to `maybe_mark_benchmark_request_as_completed` --- database/src/pool.rs | 10 ++++++---- database/src/pool/postgres.rs | 2 +- database/src/pool/sqlite.rs | 2 +- site/src/job_queue/mod.rs | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/database/src/pool.rs b/database/src/pool.rs index ec7495ddc..c46543032 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -265,7 +265,7 @@ pub trait Connection: Send + Sync { /// Try and mark the benchmark_request as completed. Will return `true` if /// it has been marked as completed else `false` meaning there was no change - async fn mark_benchmark_request_as_completed(&self, tag: &str) -> anyhow::Result; + async fn maybe_mark_benchmark_request_as_completed(&self, tag: &str) -> anyhow::Result; /// Mark the job as completed. Sets the status to 'failed' or 'success' /// depending on the enum's completed state being a success @@ -450,7 +450,7 @@ mod tests { .unwrap(); assert!(db - .mark_benchmark_request_as_completed(request_tag) + .maybe_mark_benchmark_request_as_completed(request_tag) .await .unwrap()); } @@ -932,7 +932,7 @@ mod tests { .await .unwrap(); assert!(db - .mark_benchmark_request_as_completed("sha-1") + .maybe_mark_benchmark_request_as_completed("sha-1") .await .unwrap()); Ok(ctx) @@ -988,7 +988,9 @@ mod tests { .await .unwrap(); - db.mark_benchmark_request_as_completed(tag).await.unwrap(); + db.maybe_mark_benchmark_request_as_completed(tag) + .await + .unwrap(); /* From the status page view we can see that the duration has been * updated. Albeit that it will be a very short duration. */ diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index 1bb6adbf1..8542da619 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -1902,7 +1902,7 @@ where } } - async fn mark_benchmark_request_as_completed(&self, tag: &str) -> anyhow::Result { + async fn maybe_mark_benchmark_request_as_completed(&self, tag: &str) -> anyhow::Result { // Find if the benchmark is completed and update it's status to completed // in one SQL block let row = self diff --git a/database/src/pool/sqlite.rs b/database/src/pool/sqlite.rs index a0e2a19b3..03c4ad679 100644 --- a/database/src/pool/sqlite.rs +++ b/database/src/pool/sqlite.rs @@ -1359,7 +1359,7 @@ impl Connection for SqliteConnection { no_queue_implementation_abort!() } - async fn mark_benchmark_request_as_completed(&self, _tag: &str) -> anyhow::Result { + async fn maybe_mark_benchmark_request_as_completed(&self, _tag: &str) -> anyhow::Result { no_queue_implementation_abort!() } diff --git a/site/src/job_queue/mod.rs b/site/src/job_queue/mod.rs index 12c6e50f1..9693fbdce 100644 --- a/site/src/job_queue/mod.rs +++ b/site/src/job_queue/mod.rs @@ -254,7 +254,7 @@ async fn try_enqueue_next_benchmark_request( } BenchmarkRequestStatus::InProgress => { if conn - .mark_benchmark_request_as_completed(request.tag().unwrap()) + .maybe_mark_benchmark_request_as_completed(request.tag().unwrap()) .await? { index.add_tag(request.tag().unwrap()); @@ -366,7 +366,7 @@ mod tests { .unwrap(); assert!(db - .mark_benchmark_request_as_completed(request_tag) + .maybe_mark_benchmark_request_as_completed(request_tag) .await .unwrap()); } From 0d32dcdc779dc8e3b9c38c0d8be6b36d65ef15ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 14:28:37 +0200 Subject: [PATCH 03/10] Return job ID from `enqueue_benchmark_job` --- database/src/pool.rs | 4 ++-- database/src/pool/postgres.rs | 10 ++++++---- database/src/pool/sqlite.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/database/src/pool.rs b/database/src/pool.rs index c46543032..64922d857 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -214,7 +214,7 @@ pub trait Connection: Send + Sync { commit_date: DateTime, ) -> anyhow::Result<()>; - /// Add a benchmark job to the job queue. + /// Add a benchmark job to the job queue and return its ID. async fn enqueue_benchmark_job( &self, request_tag: &str, @@ -222,7 +222,7 @@ pub trait Connection: Send + Sync { backend: CodegenBackend, profile: Profile, benchmark_set: u32, - ) -> anyhow::Result<()>; + ) -> anyhow::Result; /// Returns a set of compile-time benchmark test cases that were already computed for the /// given artifact. diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index 8542da619..04088ce91 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -1637,9 +1637,10 @@ where backend: CodegenBackend, profile: Profile, benchmark_set: u32, - ) -> anyhow::Result<()> { - self.conn() - .execute( + ) -> anyhow::Result { + let row = self + .conn() + .query_one( r#" INSERT INTO job_queue( request_tag, @@ -1651,6 +1652,7 @@ where ) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING + RETURNING job_queue.id "#, &[ &request_tag, @@ -1663,7 +1665,7 @@ where ) .await .context("failed to insert benchmark_job")?; - Ok(()) + Ok(row.get::<_, i32>(0) as u32) } async fn get_compile_test_cases_with_measurements( diff --git a/database/src/pool/sqlite.rs b/database/src/pool/sqlite.rs index 03c4ad679..26bd8937b 100644 --- a/database/src/pool/sqlite.rs +++ b/database/src/pool/sqlite.rs @@ -1301,7 +1301,7 @@ impl Connection for SqliteConnection { _backend: CodegenBackend, _profile: Profile, _benchmark_set: u32, - ) -> anyhow::Result<()> { + ) -> anyhow::Result { no_queue_implementation_abort!() } From f75d4a2e2132e7d1f378172cebed79066f5f2fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 16:12:13 +0200 Subject: [PATCH 04/10] Add function for getting the last N completed requests The function returns errors associated with the requests, including both the error benchmark/context name and the error content. The query is also prepared, because it should be as fast as possible to not block the status page. Also adds some basic test builders for easier preparation of test state in tests. --- database/src/lib.rs | 7 ++ database/src/pool.rs | 89 ++++++++++++++++++++++++- database/src/pool/postgres.rs | 94 ++++++++++++++++++++++++-- database/src/pool/sqlite.rs | 11 +++- database/src/tests/builder.rs | 121 ++++++++++++++++++++++++++++++++++ database/src/tests/mod.rs | 77 ++++++++++++++++++++-- 6 files changed, 385 insertions(+), 14 deletions(-) create mode 100644 database/src/tests/builder.rs diff --git a/database/src/lib.rs b/database/src/lib.rs index f96df1184..06a9c5965 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -1248,6 +1248,13 @@ pub struct InProgressRequestWithJobs { pub parent: Option<(BenchmarkRequest, Vec)>, } +#[derive(Debug, PartialEq)] +pub struct CompletedBenchmarkRequestWithErrors { + request: BenchmarkRequest, + /// Benchmark (name) -> error + errors: HashMap, +} + /// The data that can be retrived from the database directly to populate the /// status page #[derive(Debug, PartialEq)] diff --git a/database/src/pool.rs b/database/src/pool.rs index 64922d857..d1fe1a5fd 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -2,7 +2,8 @@ use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, ArtifactIdNumber, BenchmarkJob, BenchmarkJobConclusion, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, - CollectorConfig, CompileBenchmark, PartialStatusPageData, Target, + CollectorConfig, CompileBenchmark, CompletedBenchmarkRequestWithErrors, PartialStatusPageData, + Target, }; use crate::{CollectionId, Index, Profile, QueuedCommit, Scenario, Step}; use chrono::{DateTime, Utc}; @@ -277,6 +278,15 @@ pub trait Connection: Send + Sync { async fn get_status_page_data(&self) -> anyhow::Result; + /// Return the last `count` completed benchmark requests, along with all errors associated with + /// them. + /// + /// The requests will be ordered from most recently to least recently completed. + async fn get_last_n_completed_benchmark_requests( + &self, + count: u64, + ) -> anyhow::Result>; + /// Get all of the configuration for all of the collectors async fn get_collector_configs(&self) -> anyhow::Result>; @@ -403,6 +413,7 @@ impl Pool { mod tests { use super::*; use crate::metric::Metric; + use crate::tests::builder::{JobBuilder, RequestBuilder}; use crate::tests::run_postgres_test; use crate::BenchmarkJobStatus; use crate::{tests::run_db_test, BenchmarkRequestType, Commit, CommitType, Date}; @@ -1190,4 +1201,80 @@ mod tests { }) .await; } + + #[tokio::test] + async fn get_last_completed_requests() { + run_postgres_test(|ctx| async { + let mut requests = vec![]; + let db = ctx.db(); + + let collector = ctx.add_collector(Default::default()).await; + + // Create several completed requests + for id in 1..=3 { + // Make some space between completions + tokio::time::sleep(Duration::from_millis(100)).await; + + requests.push( + RequestBuilder::master( + db, + &format!("sha{}", id), + &format!("sha{}", id - 1), + id, + ) + .await + .add_job(db, JobBuilder::new()) + .await + .complete(db, &collector) + .await, + ); + } + + // Create an additional non-completed request + ctx.insert_master_request("foo", "bar", 1000).await; + + // Request 1 will have artifact with errors + let aid1 = ctx.upsert_master_artifact("sha1").await; + db.record_error(aid1, "crate1", "error1").await; + db.record_error(aid1, "crate2", "error2").await; + + // Request 2 will have artifact without errors + let _aid2 = ctx.upsert_master_artifact("sha2").await; + + // Request 3 will have no artifact (shouldn't happen in practice, but...) + + let reqs = db.get_last_n_completed_benchmark_requests(5).await.unwrap(); + assert_eq!(reqs.len(), 3); + + let expected = [ + ("sha3", HashMap::new()), + ("sha2", HashMap::new()), + ( + "sha1", + HashMap::from([ + ("crate1".to_string(), "error1".to_string()), + ("crate2".to_string(), "error2".to_string()), + ]), + ), + ]; + for ((sha, errors), req) in expected.into_iter().zip(reqs) { + assert_eq!( + req.request.tag().unwrap(), + sha, + "Request {req:?} does not have expected sha {sha}" + ); + assert_eq!( + req.errors, errors, + "Request {req:?} does not have expected errors {errors:?}" + ); + } + + let reqs = db.get_last_n_completed_benchmark_requests(1).await.unwrap(); + assert_eq!(reqs.len(), 1); + assert_eq!(reqs[0].request.tag().unwrap(), "sha3"); + + Ok(ctx) + }) + .await; + } } diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index 04088ce91..1aa8ab94a 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -4,9 +4,9 @@ use crate::{ ArtifactCollection, ArtifactId, ArtifactIdNumber, Benchmark, BenchmarkJob, BenchmarkJobConclusion, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkRequestType, BenchmarkSet, CodegenBackend, CollectionId, - CollectorConfig, Commit, CommitType, CompileBenchmark, Date, InProgressRequestWithJobs, Index, - PartialStatusPageData, Profile, QueuedCommit, Scenario, Target, - BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR, + CollectorConfig, Commit, CommitType, CompileBenchmark, CompletedBenchmarkRequestWithErrors, + Date, InProgressRequestWithJobs, Index, PartialStatusPageData, Profile, QueuedCommit, Scenario, + Target, BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR, BENCHMARK_JOB_STATUS_QUEUED_STR, BENCHMARK_JOB_STATUS_SUCCESS_STR, BENCHMARK_REQUEST_MASTER_STR, BENCHMARK_REQUEST_RELEASE_STR, BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR, BENCHMARK_REQUEST_STATUS_COMPLETED_STR, @@ -471,6 +471,7 @@ pub struct CachedStatements { get_artifact_size: Statement, load_benchmark_request_index: Statement, get_compile_test_cases_with_measurements: Statement, + get_last_n_completed_requests_with_errors: Statement, } pub struct PostgresTransaction<'a> { @@ -667,12 +668,45 @@ impl PostgresConnection { WHERE aid = $1 ) ").await.unwrap(), + get_last_n_completed_requests_with_errors: conn.prepare(&format!(" + WITH completed AS ( + SELECT {BENCHMARK_REQUEST_COLUMNS} + FROM benchmark_request + WHERE status = $1 + -- Select last N completed requests + ORDER BY completed_at DESC + LIMIT $2 + ), artifacts AS ( + SELECT artifact.id, name + FROM artifact + -- Use right join to only return artifacts for selected requests + RIGHT JOIN completed ON artifact.name = completed.tag + ), errors AS ( + SELECT + artifacts.name AS tag, + error.benchmark, + error.error + FROM error + -- Use right join to only return errors for selected artifacts + RIGHT JOIN artifacts ON error.aid = artifacts.id + ) + -- Select request duplicated for each pair of (benchmark, error) + SELECT + completed.*, + errors.benchmark, + errors.error + FROM completed + LEFT JOIN errors ON errors.tag = completed.tag + -- Resort the requests, because the original order may be lost + ORDER BY completed.completed_at DESC; + ")).await.unwrap(), }), conn, } } } +// `tag` should be kept as the first column const BENCHMARK_REQUEST_COLUMNS: &str = "tag, parent_sha, pr, commit_type, status, created_at, completed_at, backends, profiles, commit_date, duration_ms"; @@ -1986,6 +2020,56 @@ where Ok(()) } + async fn get_last_n_completed_benchmark_requests( + &self, + count: u64, + ) -> anyhow::Result> { + let rows = self + .conn() + .query( + &self.statements().get_last_n_completed_requests_with_errors, + &[&BENCHMARK_REQUEST_STATUS_COMPLETED_STR, &(count as i64)], + ) + .await?; + + // Iterate through the requests and aggregate their errors + // Make sure to keep their original order + let mut requests = vec![]; + // tag -> errors + let mut errors: HashMap> = Default::default(); + + for row in rows { + let tag = row.get::<_, &str>(0); + let error_benchmark = row.get::<_, Option>(11); + let error_content = row.get::<_, Option>(12); + + // We already saw this request, just add errors + if let Some(errors) = errors.get_mut(tag) { + if let Some(benchmark) = error_benchmark { + errors.insert(benchmark, error_content.unwrap_or_default()); + } + } else { + // We see this request for the first time + let request = row_to_benchmark_request(&row, None); + let request_errors = if let Some(benchmark) = error_benchmark { + HashMap::from([(benchmark, error_content.unwrap_or_default())]) + } else { + HashMap::new() + }; + errors.insert(tag.to_string(), request_errors); + requests.push(request); + } + } + + Ok(requests + .into_iter() + .map(|request| { + let errors = errors.remove(request.tag().unwrap()).unwrap_or_default(); + CompletedBenchmarkRequestWithErrors { request, errors } + }) + .collect()) + } + async fn get_status_page_data(&self) -> anyhow::Result { let max_completed_requests = 30; @@ -2273,7 +2357,9 @@ fn row_to_benchmark_request(row: &Row, row_offset: Option) -> BenchmarkRe let status = BenchmarkRequestStatus::from_str_and_completion_date(status, completed_at, duration_ms) - .expect("Invalid BenchmarkRequestStatus data in the database"); + .unwrap_or_else(|e| { + panic!("Invalid BenchmarkRequestStatus data in the database for tag {tag:?}: {e:?}") + }); match commit_type { BENCHMARK_REQUEST_TRY_STR => BenchmarkRequest { diff --git a/database/src/pool/sqlite.rs b/database/src/pool/sqlite.rs index 26bd8937b..6a7a10078 100644 --- a/database/src/pool/sqlite.rs +++ b/database/src/pool/sqlite.rs @@ -3,8 +3,8 @@ use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, Benchmark, BenchmarkJob, BenchmarkJobConclusion, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, - CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, Date, - PartialStatusPageData, Profile, Target, + CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, + CompletedBenchmarkRequestWithErrors, Date, PartialStatusPageData, Profile, Target, }; use crate::{ArtifactIdNumber, Index, QueuedCommit}; use chrono::{DateTime, TimeZone, Utc}; @@ -1382,6 +1382,13 @@ impl Connection for SqliteConnection { async fn update_collector_heartbeat(&self, _collector_name: &str) -> anyhow::Result<()> { no_queue_implementation_abort!() } + + async fn get_last_n_completed_benchmark_requests( + &self, + _count: u64, + ) -> anyhow::Result> { + no_queue_implementation_abort!() + } } fn parse_artifact_id(ty: &str, sha: &str, date: Option) -> ArtifactId { diff --git a/database/src/tests/builder.rs b/database/src/tests/builder.rs new file mode 100644 index 000000000..58bd21a89 --- /dev/null +++ b/database/src/tests/builder.rs @@ -0,0 +1,121 @@ +use crate::{ + BenchmarkJobConclusion, BenchmarkRequest, BenchmarkSet, CodegenBackend, CollectorConfig, + Connection, Profile, Target, +}; +use chrono::Utc; +use hashbrown::HashMap; + +pub struct RequestBuilder { + request: BenchmarkRequest, + jobs: Vec<(JobBuilder, u32)>, +} + +impl RequestBuilder { + pub async fn master(db: &dyn Connection, tag: &str, parent: &str, pr: u32) -> Self { + let request = BenchmarkRequest::create_master(tag, parent, pr, Utc::now()); + db.insert_benchmark_request(&request).await.unwrap(); + Self { + request, + jobs: vec![], + } + } + + pub async fn add_job(mut self, db: &dyn Connection, job: JobBuilder) -> Self { + let id = db + .enqueue_benchmark_job( + self.request.tag().unwrap(), + job.target, + job.backend, + job.profile, + job.benchmark_set, + ) + .await + .unwrap(); + self.jobs.push((job, id)); + self + } + + /// Continually completes **pending jobs in the DB** until all jobs of this request are + /// completed, and then completes this benchmark request. + pub async fn complete( + self, + db: &dyn Connection, + collector: &CollectorConfig, + ) -> BenchmarkRequest { + assert!(!self.jobs.is_empty()); + + let mut to_complete: HashMap = + self.jobs.into_iter().map(|(job, id)| (id, job)).collect(); + while !to_complete.is_empty() { + // We can't specify which job we dequeue, so we have to iterate them one by one and + // complete them, until we complete all the jobs that we expect + let (target, set) = to_complete + .values() + .map(|j| (j.target, j.benchmark_set)) + .next() + .unwrap(); + let (job, _) = db + .dequeue_benchmark_job(collector.name(), target, BenchmarkSet(set)) + .await + .unwrap() + .unwrap(); + let conclution = if let Some(expected_job) = to_complete.remove(&job.id) { + expected_job.conclution + } else { + BenchmarkJobConclusion::Success + }; + db.mark_benchmark_job_as_completed(job.id, conclution) + .await + .unwrap(); + } + // At this point all jobs of the request should be properly completed, so we can also + // complete the request itself + assert!(db + .maybe_mark_benchmark_request_as_completed(self.request.tag().unwrap()) + .await + .unwrap()); + self.request + } +} + +pub struct JobBuilder { + target: Target, + backend: CodegenBackend, + profile: Profile, + benchmark_set: u32, + conclution: BenchmarkJobConclusion, +} + +impl JobBuilder { + pub fn new() -> Self { + Self::default() + } +} + +impl Default for JobBuilder { + fn default() -> Self { + Self { + target: Target::X86_64UnknownLinuxGnu, + backend: CodegenBackend::Llvm, + profile: Profile::Check, + benchmark_set: 0, + conclution: BenchmarkJobConclusion::Success, + } + } +} + +pub struct CollectorBuilder { + pub name: String, + pub target: Target, + pub benchmark_set: BenchmarkSet, +} + +impl Default for CollectorBuilder { + fn default() -> Self { + Self { + name: "test-collector".to_string(), + target: Target::X86_64UnknownLinuxGnu, + benchmark_set: BenchmarkSet(0), + } + } +} diff --git a/database/src/tests/mod.rs b/database/src/tests/mod.rs index b4c7fe580..91d8bdc54 100644 --- a/database/src/tests/mod.rs +++ b/database/src/tests/mod.rs @@ -1,11 +1,18 @@ #![allow(dead_code)] +pub mod builder; + +use chrono::Utc; use std::future::Future; use tokio_postgres::config::Host; use tokio_postgres::Config; use crate::pool::postgres::make_client; -use crate::Pool; +use crate::tests::builder::CollectorBuilder; +use crate::{ + ArtifactId, ArtifactIdNumber, BenchmarkRequest, CollectorConfig, Commit, CommitType, + Connection, Date, Pool, +}; enum TestDb { Postgres { @@ -20,10 +27,12 @@ enum TestDb { /// a database. pub struct TestContext { test_db: TestDb, - // Pre-cached client to avoid creating unnecessary connections in tests - client: Pool, + pool: Pool, + // Pre-cached DB connection + connection: Box, } +/// Basic lifecycle functions impl TestContext { async fn new_postgres(db_url: &str) -> Self { let config: Config = db_url.parse().expect("Cannot parse connection string"); @@ -65,32 +74,41 @@ impl TestContext { db_name ); let pool = Pool::open(test_db_url.as_str()); + let connection = pool.connection().await; Self { test_db: TestDb::Postgres { original_db_url: db_url.to_string(), db_name, }, - client: pool, + pool, + connection, } } async fn new_sqlite() -> Self { let pool = Pool::open(":memory:"); + let connection = pool.connection().await; Self { test_db: TestDb::SQLite, - client: pool, + pool, + connection, } } pub fn db_pool(&self) -> &Pool { - &self.client + &self.pool + } + + pub fn db(&self) -> &dyn Connection { + self.connection.as_ref() } async fn finish(self) { // Cleanup the test database // First, we need to stop using the database - drop(self.client); + drop(self.connection); + drop(self.pool); match self.test_db { TestDb::Postgres { @@ -111,6 +129,51 @@ impl TestContext { } } +/// Test helpers +impl TestContext { + /// Create a new master benchmark request and add it to the DB. + pub async fn insert_master_request( + &self, + sha: &str, + parent: &str, + pr: u32, + ) -> BenchmarkRequest { + let req = BenchmarkRequest::create_master(sha, parent, pr, Utc::now()); + self.db().insert_benchmark_request(&req).await.unwrap(); + req + } + + pub async fn complete_request(&self, tag: &str) { + // Note: this assumes that there are not non-completed jobs in the DB for the request + self.db() + .maybe_mark_benchmark_request_as_completed(tag) + .await + .unwrap(); + } + + pub async fn upsert_master_artifact(&self, sha: &str) -> ArtifactIdNumber { + self.db() + .artifact_id(&ArtifactId::Commit(Commit { + sha: sha.to_string(), + date: Date(Utc::now()), + r#type: CommitType::Master, + })) + .await + } + + pub async fn add_collector(&self, collector: CollectorBuilder) -> CollectorConfig { + self.db() + .add_collector_config( + &collector.name, + collector.target, + collector.benchmark_set.get_id(), + true, + ) + .await + .unwrap() + } +} + /// Runs a test against an actual postgres database. pub async fn run_postgres_test(f: F) where From 82dcd5d16112f0cf6e3b7e8d1cf9bad7c03b6e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 20:57:26 +0200 Subject: [PATCH 05/10] Add a function for getting jobs of the current in progress requests and their parents --- database/src/lib.rs | 10 - database/src/pool.rs | 246 +++++++++------------- database/src/pool/postgres.rs | 385 +++++++--------------------------- database/src/pool/sqlite.rs | 6 +- database/src/tests/builder.rs | 88 +++++--- 5 files changed, 239 insertions(+), 496 deletions(-) diff --git a/database/src/lib.rs b/database/src/lib.rs index 06a9c5965..b56945198 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -1254,13 +1254,3 @@ pub struct CompletedBenchmarkRequestWithErrors { /// Benchmark (name) -> error errors: HashMap, } - -/// The data that can be retrived from the database directly to populate the -/// status page -#[derive(Debug, PartialEq)] -pub struct PartialStatusPageData { - /// A Vector of; completed requests with any associated errors - pub completed_requests: Vec<(BenchmarkRequest, Vec)>, - /// In progress requests along with their associated jobs - pub in_progress: Vec, -} diff --git a/database/src/pool.rs b/database/src/pool.rs index d1fe1a5fd..c4ba1d7c6 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -2,8 +2,7 @@ use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, ArtifactIdNumber, BenchmarkJob, BenchmarkJobConclusion, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, - CollectorConfig, CompileBenchmark, CompletedBenchmarkRequestWithErrors, PartialStatusPageData, - Target, + CollectorConfig, CompileBenchmark, CompletedBenchmarkRequestWithErrors, Target, }; use crate::{CollectionId, Index, Profile, QueuedCommit, Scenario, Step}; use chrono::{DateTime, Utc}; @@ -276,8 +275,6 @@ pub trait Connection: Send + Sync { conclusion: BenchmarkJobConclusion, ) -> anyhow::Result<()>; - async fn get_status_page_data(&self) -> anyhow::Result; - /// Return the last `count` completed benchmark requests, along with all errors associated with /// them. /// @@ -287,6 +284,12 @@ pub trait Connection: Send + Sync { count: u64, ) -> anyhow::Result>; + /// Return jobs of all requests that are currently in progress, and the jobs of their parents. + /// The keys of the hashmap contain the request tags. + async fn get_jobs_of_in_progress_benchmark_requests( + &self, + ) -> anyhow::Result>>; + /// Get all of the configuration for all of the collectors async fn get_collector_configs(&self) -> anyhow::Result>; @@ -413,9 +416,8 @@ impl Pool { mod tests { use super::*; use crate::metric::Metric; - use crate::tests::builder::{JobBuilder, RequestBuilder}; + use crate::tests::builder::{job, RequestBuilder}; use crate::tests::run_postgres_test; - use crate::BenchmarkJobStatus; use crate::{tests::run_db_test, BenchmarkRequestType, Commit, CommitType, Date}; use chrono::Utc; use std::str::FromStr; @@ -1005,13 +1007,12 @@ mod tests { /* From the status page view we can see that the duration has been * updated. Albeit that it will be a very short duration. */ - let status_page_view = db.get_status_page_data().await.unwrap(); - let req = &status_page_view - .completed_requests + let completed = db.get_last_n_completed_benchmark_requests(1).await.unwrap(); + let req = &completed .iter() - .find(|it| it.0.tag() == Some(tag)) + .find(|it| it.request.tag() == Some(tag)) .unwrap() - .0; + .request; assert!(matches!( req.status(), @@ -1030,143 +1031,6 @@ mod tests { .await; } - #[tokio::test] - async fn get_status_page_data() { - run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; - let benchmark_set = BenchmarkSet(0u32); - let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); - let tag = "sha-1"; - let tag_two = "sha-2"; - let collector_name = "collector-1"; - let target = Target::X86_64UnknownLinuxGnu; - - db.add_collector_config(collector_name, target, benchmark_set.0, true) - .await - .unwrap(); - - let benchmark_request = BenchmarkRequest::create_release(tag, time); - db.insert_benchmark_request(&benchmark_request) - .await - .unwrap(); - - complete_request(&*db, tag, collector_name, benchmark_set.0, target).await; - // record a couple of errors against the tag - let artifact_id = db.artifact_id(&ArtifactId::Tag(tag.to_string())).await; - - db.record_error(artifact_id, "example-1", "This is an error") - .await; - db.record_error(artifact_id, "example-2", "This is another error") - .await; - - let benchmark_request_two = BenchmarkRequest::create_release(tag_two, time); - db.insert_benchmark_request(&benchmark_request_two) - .await - .unwrap(); - - db.enqueue_benchmark_job( - benchmark_request_two.tag().unwrap(), - target, - CodegenBackend::Llvm, - Profile::Opt, - benchmark_set.0, - ) - .await - .unwrap(); - db.enqueue_benchmark_job( - benchmark_request_two.tag().unwrap(), - target, - CodegenBackend::Llvm, - Profile::Debug, - benchmark_set.0, - ) - .await - .unwrap(); - - db.update_benchmark_request_status( - benchmark_request_two.tag().unwrap(), - BenchmarkRequestStatus::InProgress, - ) - .await - .unwrap(); - - let status_page_data = db.get_status_page_data().await.unwrap(); - - assert!(status_page_data.completed_requests.len() == 1); - assert_eq!(status_page_data.completed_requests[0].0.tag().unwrap(), tag); - assert!(matches!( - status_page_data.completed_requests[0].0.status(), - BenchmarkRequestStatus::Completed { .. } - )); - // can't really test duration - // ensure errors are correct - assert_eq!( - status_page_data.completed_requests[0].1[0], - "This is an error".to_string() - ); - assert_eq!( - status_page_data.completed_requests[0].1[1], - "This is another error".to_string() - ); - - assert!(status_page_data.in_progress.len() == 1); - // we should have 2 jobs - assert!(status_page_data.in_progress[0].request.1.len() == 2); - // the request should be in progress - assert!(matches!( - status_page_data.in_progress[0].request.0.status(), - BenchmarkRequestStatus::InProgress - )); - - // Test the first job - assert!(matches!( - status_page_data.in_progress[0].request.1[0].target(), - Target::X86_64UnknownLinuxGnu - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[0].status(), - BenchmarkJobStatus::Queued - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[0].backend(), - CodegenBackend::Llvm - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[0].profile(), - Profile::Opt - )); - assert_eq!( - status_page_data.in_progress[0].request.1[0].benchmark_set(), - benchmark_set - ); - - // test the second job - assert!(matches!( - status_page_data.in_progress[0].request.1[1].target(), - Target::X86_64UnknownLinuxGnu - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[1].status(), - BenchmarkJobStatus::Queued - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[1].backend(), - CodegenBackend::Llvm - )); - assert!(matches!( - status_page_data.in_progress[0].request.1[1].profile(), - Profile::Debug - )); - assert_eq!( - status_page_data.in_progress[0].request.1[1].benchmark_set(), - benchmark_set - ); - - Ok(ctx) - }) - .await; - } - #[tokio::test] async fn get_collector_configs() { run_postgres_test(|ctx| async { @@ -1223,7 +1087,7 @@ mod tests { id, ) .await - .add_job(db, JobBuilder::new()) + .add_job(db, job()) .await .complete(db, &collector) .await, @@ -1277,4 +1141,88 @@ mod tests { }) .await; } + + #[tokio::test] + async fn get_in_progress_jobs() { + run_postgres_test(|ctx| async { + let db = ctx.db(); + + let collector = ctx.add_collector(Default::default()).await; + + // Artifacts ready request, should be ignored + RequestBuilder::master(db, "foo", "bar", 1000).await; + + // Create a completed parent with jobs + let completed = RequestBuilder::master(db, "sha4-parent", "sha0", 1001) + .await + .add_jobs( + db, + &[job().profile(Profile::Doc), job().profile(Profile::Opt)], + ) + .await + .complete(db, &collector) + .await; + + // In progress request without a parent + let req1 = RequestBuilder::master(db, "sha1", "sha0", 1) + .await + .set_in_progress(db) + .await; + + // In progress request with a parent that has no jobs + let req2 = RequestBuilder::master(db, "sha2", "sha1", 2) + .await + .add_jobs( + db, + &[job().profile(Profile::Check), job().profile(Profile::Debug)], + ) + .await + .set_in_progress(db) + .await; + + // In progress request with a parent that has jobs + let req3 = RequestBuilder::master(db, "sha3", "sha2", 3) + .await + .add_jobs( + db, + &[job().profile(Profile::Doc), job().profile(Profile::Opt)], + ) + .await + .set_in_progress(db) + .await; + + // In progress request with a parent that has jobs, but is completed + let req4 = RequestBuilder::master(db, "sha4", completed.tag(), 4) + .await + .add_jobs( + db, + &[job().profile(Profile::Doc), job().profile(Profile::Check)], + ) + .await + .set_in_progress(db) + .await; + + let mut reqs = db + .get_jobs_of_in_progress_benchmark_requests() + .await + .unwrap(); + + // Check that all jobs are unique + let mut job_ids = HashSet::new(); + for job in reqs.values().flatten() { + assert!(job_ids.insert(job.id)); + } + + // Check that all jobs were returned + assert!(!reqs.contains_key(req1.tag())); + req2.assert_has_exact_jobs(&reqs.remove(req2.tag()).unwrap()); + req3.assert_has_exact_jobs(&reqs.remove(req3.tag()).unwrap()); + req4.assert_has_exact_jobs(&reqs.remove(req4.tag()).unwrap()); + completed.assert_has_exact_jobs(&reqs.remove(completed.tag()).unwrap()); + assert!(reqs.is_empty()); + + Ok(ctx) + }) + .await; + } } diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index 1aa8ab94a..d1a3e7f77 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -5,10 +5,9 @@ use crate::{ BenchmarkJobConclusion, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkRequestType, BenchmarkSet, CodegenBackend, CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, CompletedBenchmarkRequestWithErrors, - Date, InProgressRequestWithJobs, Index, PartialStatusPageData, Profile, QueuedCommit, Scenario, - Target, BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR, - BENCHMARK_JOB_STATUS_QUEUED_STR, BENCHMARK_JOB_STATUS_SUCCESS_STR, - BENCHMARK_REQUEST_MASTER_STR, BENCHMARK_REQUEST_RELEASE_STR, + Date, Index, Profile, QueuedCommit, Scenario, Target, BENCHMARK_JOB_STATUS_FAILURE_STR, + BENCHMARK_JOB_STATUS_IN_PROGRESS_STR, BENCHMARK_JOB_STATUS_QUEUED_STR, + BENCHMARK_JOB_STATUS_SUCCESS_STR, BENCHMARK_REQUEST_MASTER_STR, BENCHMARK_REQUEST_RELEASE_STR, BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR, BENCHMARK_REQUEST_STATUS_COMPLETED_STR, BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR, BENCHMARK_REQUEST_STATUS_WAITING_FOR_ARTIFACTS_STR, BENCHMARK_REQUEST_TRY_STR, @@ -472,6 +471,7 @@ pub struct CachedStatements { load_benchmark_request_index: Statement, get_compile_test_cases_with_measurements: Statement, get_last_n_completed_requests_with_errors: Statement, + get_jobs_of_in_progress_benchmark_requests: Statement, } pub struct PostgresTransaction<'a> { @@ -700,6 +700,31 @@ impl PostgresConnection { -- Resort the requests, because the original order may be lost ORDER BY completed.completed_at DESC; ")).await.unwrap(), + get_jobs_of_in_progress_benchmark_requests: conn.prepare(&format!(" + -- Get in progress requests + WITH in_progress AS ( + SELECT tag, parent_sha + FROM benchmark_request + WHERE status = '{BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR}' AND + tag IS NOT NULL + ), + -- Get their parents + parents AS ( + SELECT parent_sha AS tag + FROM in_progress + WHERE parent_sha is not NULL + ), + -- Concatenate them together (without duplicates) + requests AS ( + SELECT tag FROM in_progress + UNION + SELECT tag FROM parents + ) + SELECT job_queue.* + FROM requests + -- Only get requests that have some jobs + RIGHT JOIN job_queue on job_queue.request_tag = requests.tag + ")).await.unwrap(), }), conn, } @@ -2070,221 +2095,59 @@ where .collect()) } - async fn get_status_page_data(&self) -> anyhow::Result { - let max_completed_requests = 30; - - let in_progress_query = format!( - " - WITH in_progress_requests AS ( - SELECT - tag, - parent_sha, - pr, - commit_type, - status, - created_at, - completed_at, - backends, - profiles, - commit_date, - duration_ms - FROM - benchmark_request - WHERE - status = '{BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR}' - ORDER BY - completed_at - ), in_progress_jobs AS ( - SELECT - request_tag AS tag, - ARRAY_AGG( - ROW( - job_queue.id, - job_queue.request_tag, - job_queue.target, - job_queue.backend, - job_queue.profile, - job_queue.benchmark_set, - job_queue.status, - job_queue.created_at, - job_queue.started_at, - job_queue.completed_at, - job_queue.retry, - job_queue.collector_name - )::TEXT - ) AS jobs - FROM - job_queue - LEFT JOIN in_progress_requests ON job_queue.request_tag = in_progress_requests.tag - GROUP BY - job_queue.request_tag - ), parent AS ( - SELECT - benchmark_request.tag AS parent_tag, - benchmark_request.parent_sha AS parent_sha, - benchmark_request.pr AS parent_pr, - benchmark_request.commit_type AS parent_commit_type, - benchmark_request.status AS parent_status, - benchmark_request.created_at AS parent_created_at, - benchmark_request.completed_at AS parent_completed_at, - benchmark_request.backends AS parent_backends, - benchmark_request.profiles AS parent_profiles, - benchmark_request.commit_date AS parent_commit_date, - benchmark_request.duration_ms AS parent_duration_ms, - EXISTS ( - SELECT - 1 - FROM - job_queue - WHERE - job_queue.request_tag = benchmark_request.tag - AND job_queue.status IN ( - '{BENCHMARK_JOB_STATUS_QUEUED_STR}', - '{BENCHMARK_JOB_STATUS_IN_PROGRESS_STR}' - ) - ) AS parent_active - FROM - benchmark_request - LEFT JOIN - in_progress_requests ON benchmark_request.tag = in_progress_requests.parent_sha - ), parent_jobs AS ( - SELECT - request_tag AS parent_tag, - ARRAY_AGG( - ROW( - job_queue.id, - job_queue.request_tag, - job_queue.target, - job_queue.backend, - job_queue.profile, - job_queue.benchmark_set, - job_queue.status, - job_queue.created_at, - job_queue.started_at, - job_queue.completed_at, - job_queue.retry, - job_queue.collector_name - )::TEXT - ) AS parent_jobs - FROM - job_queue - LEFT JOIN parent ON job_queue.request_tag = parent.parent_tag - GROUP BY - job_queue.request_tag - ) - SELECT - in_progress_requests.*, - in_progress_jobs.jobs, - parent.*, - parent_jobs.parent_jobs - FROM - in_progress_requests - LEFT JOIN - in_progress_jobs ON in_progress_requests.tag = in_progress_jobs.tag - LEFT JOIN - parent_jobs ON in_progress_requests.parent_sha = parent_jobs.parent_tag - LEFT JOIN - parent ON in_progress_requests.parent_sha = parent.parent_tag;" - ); - - // Gets requests along with how long the request took (latest job finish - // - earliest job start) and associated errors with the request if they - // exist - let completed_requests_query = format!( - " - WITH completed AS ( - SELECT - {BENCHMARK_REQUEST_COLUMNS} - FROM - benchmark_request - WHERE - status = '{BENCHMARK_REQUEST_STATUS_COMPLETED_STR}' - ORDER BY - completed_at - DESC LIMIT {max_completed_requests} - ), artifacts AS ( - SELECT - artifact.id, - name - FROM - artifact - LEFT JOIN completed ON artifact.name = completed.tag - ), errors AS ( - SELECT - artifacts.name AS tag, - ARRAY_AGG(error) AS errors - FROM - error - LEFT JOIN - artifacts ON error.aid = artifacts.id - GROUP BY - tag + async fn get_jobs_of_in_progress_benchmark_requests( + &self, + ) -> anyhow::Result>> { + let rows = self + .conn() + .query( + &self.statements().get_jobs_of_in_progress_benchmark_requests, + &[], ) - SELECT - completed.*, - errors.errors AS errors - FROM - completed - LEFT JOIN errors ON errors.tag = completed.tag; - " - ); + .await?; - let in_progress: Vec = self - .conn() - .query(&in_progress_query, &[]) - .await? - .iter() - .map(|it| { - let benchmark_request = row_to_benchmark_request(it, None); - let jobs: Vec = it - .get::<_, Vec>("jobs") - .iter() - .map(|it| benchmark_job_str_to_type(it).unwrap()) - .collect(); - - // This is ever-so-slightly grim however it allows us to not - // have to parse the text representation of the jobs. Which - // saves a reasonable amount of time to justify doing this. - let parent_active = it.get::<_, Option>("parent_active"); - - InProgressRequestWithJobs { - request: (benchmark_request, jobs), - parent: if parent_active.unwrap_or(false) { - // The rows values will only be non-null if the `parent_active` - // has been set - let parent_benchmark_request = row_to_benchmark_request(it, Some(12)); - // Only parse the jobs if we need to include the parent - let parent_jobs: Vec = it - .get::<_, Vec>("parent_jobs") - .iter() - .map(|it| benchmark_job_str_to_type(it).unwrap()) - .collect(); - Some((parent_benchmark_request, parent_jobs)) - } else { - None - }, + let mut request_to_jobs: HashMap> = HashMap::new(); + for row in rows { + let started_at = row.get::<_, Option>>(7); + let status = row.get::<_, &str>(9); + let collector_name = row.get::<_, Option>(11); + let status = match status { + BENCHMARK_JOB_STATUS_QUEUED_STR => BenchmarkJobStatus::Queued, + BENCHMARK_JOB_STATUS_IN_PROGRESS_STR => BenchmarkJobStatus::InProgress { + started_at: started_at.expect("started_at was null for an in progress job"), + collector_name: collector_name + .expect("Collector is missing for an in progress job"), + }, + BENCHMARK_JOB_STATUS_FAILURE_STR | BENCHMARK_JOB_STATUS_SUCCESS_STR => { + BenchmarkJobStatus::Completed { + started_at: started_at.expect("started_at was null for a finished job"), + completed_at: row.get::<_, DateTime>(8), + collector_name: collector_name + .expect("Collector is missing for an in progress job"), + success: status == BENCHMARK_JOB_STATUS_SUCCESS_STR, + } } - }) - .collect(); - - let completed_requests: Vec<(BenchmarkRequest, Vec)> = self - .conn() - .query(&completed_requests_query, &[]) - .await? - .iter() - .map(|it| { - ( - row_to_benchmark_request(it, None), - // The errors, if there are none this will be an empty vector - it.get::<_, Option>>(11).unwrap_or_default(), - ) - }) - .collect(); - - Ok(PartialStatusPageData { - completed_requests, - in_progress, - }) + _ => panic!("Invalid job status {status}"), + }; + let job = BenchmarkJob { + id: row.get::<_, i32>(0) as u32, + request_tag: row.get::<_, String>(1), + target: Target::from_str(row.get::<_, &str>(2)).map_err(|e| anyhow::anyhow!(e))?, + backend: CodegenBackend::from_str(row.get::<_, &str>(3)) + .map_err(|e| anyhow::anyhow!(e))?, + profile: Profile::from_str(row.get::<_, &str>(4)) + .map_err(|e| anyhow::anyhow!(e))?, + benchmark_set: BenchmarkSet(row.get::<_, i32>(5) as u32), + created_at: row.get::<_, DateTime>(6), + status, + deque_counter: row.get::<_, i32>(10) as u32, + }; + request_to_jobs + .entry(job.request_tag.clone()) + .or_default() + .push(job); + } + Ok(request_to_jobs) } async fn get_collector_configs(&self) -> anyhow::Result> { @@ -2400,96 +2263,6 @@ fn row_to_benchmark_request(row: &Row, row_offset: Option) -> BenchmarkRe } } -fn parse_timestamp(cell: &str) -> anyhow::Result>> { - if cell.is_empty() { - Ok(None) - } else { - // Massage postgres date string into something we can parse in Rust - // to a date - let raw_date = cell.trim_matches('"').replace(' ', "T") + ":00"; - Ok(Some( - DateTime::parse_from_rfc3339(&raw_date)?.with_timezone(&Utc), - )) - } -} - -fn benchmark_job_str_to_type(src: &str) -> anyhow::Result { - let line = src.trim_start_matches('(').trim_end_matches(')'); - - let mut col = line.split(','); - - let id: u32 = col.next().ok_or_else(|| anyhow::anyhow!("id"))?.parse()?; - let request_tag = col - .next() - .ok_or_else(|| anyhow::anyhow!("request_tag"))? - .to_owned(); - let target = col - .next() - .ok_or_else(|| anyhow::anyhow!("target"))? - .parse::() - .map_err(|e| anyhow::anyhow!(e))?; - let backend = col - .next() - .ok_or_else(|| anyhow::anyhow!("backend"))? - .parse::() - .map_err(|e| anyhow::anyhow!(e))?; - let profile = col - .next() - .ok_or_else(|| anyhow::anyhow!("profile"))? - .parse::() - .map_err(|e| anyhow::anyhow!(e))?; - let benchmark_set = BenchmarkSet( - col.next() - .ok_or_else(|| anyhow::anyhow!("benchmark_set"))? - .parse()?, - ); - - let status_str = col.next().ok_or_else(|| anyhow::anyhow!("status"))?; - let created_at = parse_timestamp(col.next().ok_or_else(|| anyhow::anyhow!("created_at"))?)? - .ok_or_else(|| anyhow::anyhow!("created_at missing"))?; - - let started_at = parse_timestamp(col.next().unwrap_or(""))?; - let completed_at = parse_timestamp(col.next().unwrap_or(""))?; - let retry: u32 = col - .next() - .ok_or_else(|| anyhow::anyhow!("retry"))? - .parse()?; - let collector_name_raw = col.next().unwrap_or("").to_owned(); - - let status = match status_str { - BENCHMARK_JOB_STATUS_QUEUED_STR => BenchmarkJobStatus::Queued, - - BENCHMARK_JOB_STATUS_IN_PROGRESS_STR => BenchmarkJobStatus::InProgress { - started_at: started_at.ok_or_else(|| anyhow::anyhow!("started_at missing"))?, - collector_name: collector_name_raw, - }, - - BENCHMARK_JOB_STATUS_SUCCESS_STR | BENCHMARK_JOB_STATUS_FAILURE_STR => { - BenchmarkJobStatus::Completed { - started_at: started_at.ok_or_else(|| anyhow::anyhow!("started_at missing"))?, - completed_at: completed_at - .ok_or_else(|| anyhow::anyhow!("completed_at missing"))?, - collector_name: collector_name_raw, - success: status_str == BENCHMARK_JOB_STATUS_SUCCESS_STR, - } - } - - _ => anyhow::bail!("unknown status `{status_str}`"), - }; - - Ok(BenchmarkJob { - id, - target, - backend, - profile, - request_tag, - benchmark_set, - created_at, - status, - deque_counter: retry, - }) -} - fn parse_artifact_id(ty: &str, sha: &str, date: Option>) -> ArtifactId { match ty { "master" => ArtifactId::Commit(Commit { diff --git a/database/src/pool/sqlite.rs b/database/src/pool/sqlite.rs index 6a7a10078..b6d758693 100644 --- a/database/src/pool/sqlite.rs +++ b/database/src/pool/sqlite.rs @@ -4,7 +4,7 @@ use crate::{ ArtifactCollection, ArtifactId, Benchmark, BenchmarkJob, BenchmarkJobConclusion, BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, - CompletedBenchmarkRequestWithErrors, Date, PartialStatusPageData, Profile, Target, + CompletedBenchmarkRequestWithErrors, Date, Profile, Target, }; use crate::{ArtifactIdNumber, Index, QueuedCommit}; use chrono::{DateTime, TimeZone, Utc}; @@ -1371,7 +1371,9 @@ impl Connection for SqliteConnection { no_queue_implementation_abort!() } - async fn get_status_page_data(&self) -> anyhow::Result { + async fn get_jobs_of_in_progress_benchmark_requests( + &self, + ) -> anyhow::Result>> { no_queue_implementation_abort!() } diff --git a/database/src/tests/builder.rs b/database/src/tests/builder.rs index 58bd21a89..48679e657 100644 --- a/database/src/tests/builder.rs +++ b/database/src/tests/builder.rs @@ -1,9 +1,9 @@ use crate::{ - BenchmarkJobConclusion, BenchmarkRequest, BenchmarkSet, CodegenBackend, CollectorConfig, - Connection, Profile, Target, + BenchmarkJob, BenchmarkJobConclusion, BenchmarkRequest, BenchmarkRequestStatus, BenchmarkSet, + CodegenBackend, CollectorConfig, Connection, Profile, Target, }; use chrono::Utc; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; pub struct RequestBuilder { request: BenchmarkRequest, @@ -20,32 +20,55 @@ impl RequestBuilder { } } - pub async fn add_job(mut self, db: &dyn Connection, job: JobBuilder) -> Self { - let id = db - .enqueue_benchmark_job( - self.request.tag().unwrap(), - job.target, - job.backend, - job.profile, - job.benchmark_set, - ) + pub fn tag(&self) -> &str { + self.request.tag().unwrap() + } + + pub fn assert_has_exact_jobs(&self, jobs: &[BenchmarkJob]) { + assert_eq!(jobs.len(), self.jobs.len()); + let mut expected: HashSet = self.jobs.iter().map(|(_, id)| *id).collect(); + for job in jobs { + assert!(expected.remove(&job.id)); + } + assert!(expected.is_empty()); + } + + pub async fn add_job(self, db: &dyn Connection, job: JobBuilder) -> Self { + self.add_jobs(db, &[job]).await + } + + pub async fn add_jobs(mut self, db: &dyn Connection, jobs: &[JobBuilder]) -> Self { + for job in jobs { + let id = db + .enqueue_benchmark_job( + self.tag(), + job.target, + job.backend, + job.profile, + job.benchmark_set, + ) + .await + .unwrap(); + self.jobs.push((job.clone(), id)); + } + self + } + + pub async fn set_in_progress(self, db: &dyn Connection) -> Self { + db.update_benchmark_request_status(self.tag(), BenchmarkRequestStatus::InProgress) .await .unwrap(); - self.jobs.push((job, id)); self } /// Continually completes **pending jobs in the DB** until all jobs of this request are /// completed, and then completes this benchmark request. - pub async fn complete( - self, - db: &dyn Connection, - collector: &CollectorConfig, - ) -> BenchmarkRequest { + pub async fn complete(self, db: &dyn Connection, collector: &CollectorConfig) -> Self { assert!(!self.jobs.is_empty()); + let tag = self.tag().to_string(); - let mut to_complete: HashMap = - self.jobs.into_iter().map(|(job, id)| (id, job)).collect(); + let mut to_complete: HashMap = + self.jobs.iter().map(|(job, id)| (*id, job)).collect(); while !to_complete.is_empty() { // We can't specify which job we dequeue, so we have to iterate them one by one and // complete them, until we complete all the jobs that we expect @@ -59,36 +82,39 @@ impl RequestBuilder { .await .unwrap() .unwrap(); - let conclution = if let Some(expected_job) = to_complete.remove(&job.id) { - expected_job.conclution + let conclusion = if let Some(expected_job) = to_complete.remove(&job.id) { + expected_job.conclusion.clone() } else { BenchmarkJobConclusion::Success }; - db.mark_benchmark_job_as_completed(job.id, conclution) + db.mark_benchmark_job_as_completed(job.id, conclusion) .await .unwrap(); } // At this point all jobs of the request should be properly completed, so we can also // complete the request itself assert!(db - .maybe_mark_benchmark_request_as_completed(self.request.tag().unwrap()) + .maybe_mark_benchmark_request_as_completed(&tag) .await .unwrap()); - self.request + drop(to_complete); + self } } +#[derive(Clone)] pub struct JobBuilder { target: Target, backend: CodegenBackend, profile: Profile, benchmark_set: u32, - conclution: BenchmarkJobConclusion, + conclusion: BenchmarkJobConclusion, } impl JobBuilder { - pub fn new() -> Self { - Self::default() + pub fn profile(mut self, profile: Profile) -> Self { + self.profile = profile; + self } } @@ -99,11 +125,15 @@ impl Default for JobBuilder { backend: CodegenBackend::Llvm, profile: Profile::Check, benchmark_set: 0, - conclution: BenchmarkJobConclusion::Success, + conclusion: BenchmarkJobConclusion::Success, } } } +pub fn job() -> JobBuilder { + JobBuilder::default() +} + pub struct CollectorBuilder { pub name: String, pub target: Target, From e9c0447d1958076e45a4c01a267714466b5b4e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 21:02:01 +0200 Subject: [PATCH 06/10] Remove `db_pool` function --- database/src/pool.rs | 45 +++++++++++++++++---------------------- database/src/tests/mod.rs | 4 ---- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/database/src/pool.rs b/database/src/pool.rs index c4ba1d7c6..492fc8381 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -474,8 +474,7 @@ mod tests { // This is essentially testing the database testing framework is // wired up correctly. Though makes sense that there should be // an empty vector returned if there are no pstats. - let db = ctx.db_pool(); - let result = db.connection().await.get_pstats(&[], &[]).await; + let result = ctx.db().get_pstats(&[], &[]).await; let expected: Vec>> = vec![]; assert_eq!(result, expected); @@ -487,21 +486,19 @@ mod tests { #[tokio::test] async fn artifact_storage() { run_db_test(|ctx| async { - let db = ctx.db_pool(); + let db = ctx.db(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let artifact_one = ArtifactId::from(create_commit("abc", time, CommitType::Master)); let artifact_two = ArtifactId::Tag("nightly-2025-05-14".to_string()); - let artifact_one_id_number = db.connection().await.artifact_id(&artifact_one).await; - let artifact_two_id_number = db.connection().await.artifact_id(&artifact_two).await; + let artifact_one_id_number = db.artifact_id(&artifact_one).await; + let artifact_two_id_number = db.artifact_id(&artifact_two).await; // We cannot arbitrarily add random sizes to the artifact size // table, as there is a constraint that the artifact must actually // exist before attaching something to it. - let db = db.connection().await; - // Artifact one inserts db.record_artifact_size(artifact_one_id_number, "llvm.so", 32) .await; @@ -531,8 +528,8 @@ mod tests { #[tokio::test] async fn multiple_requests_same_sha() { run_postgres_test(|ctx| async { - let db = ctx.db_pool(); - let db = db.connection().await; + let db = ctx.db(); + db.insert_benchmark_request(&BenchmarkRequest::create_master( "a-sha-1", "parent-sha-1", @@ -555,7 +552,7 @@ mod tests { #[tokio::test] async fn multiple_non_completed_try_requests() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let target = Target::X86_64UnknownLinuxGnu; let collector_name = "collector-1"; let benchmark_set = 1; @@ -597,8 +594,7 @@ mod tests { #[tokio::test] async fn multiple_master_requests_same_pr() { run_postgres_test(|ctx| async { - let db = ctx.db_pool(); - let db = db.connection().await; + let db = ctx.db(); db.insert_benchmark_request(&BenchmarkRequest::create_master( "a-sha-1", @@ -626,7 +622,7 @@ mod tests { #[tokio::test] async fn load_pending_benchmark_requests() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let target = Target::X86_64UnknownLinuxGnu; let collector_name = "collector-1"; @@ -672,8 +668,7 @@ mod tests { #[tokio::test] async fn attach_shas_to_try_benchmark_request() { run_postgres_test(|ctx| async { - let db = ctx.db_pool(); - let db = db.connection().await; + let db = ctx.db(); let req = BenchmarkRequest::create_try_without_artifacts(42, "", ""); @@ -712,8 +707,8 @@ mod tests { #[tokio::test] async fn enqueue_benchmark_job() { run_postgres_test(|ctx| async { - let db = ctx.db_pool(); - let db = db.connection().await; + let db = ctx.db(); + let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let benchmark_request = BenchmarkRequest::create_master("sha-1", "parent-sha-1", 42, time); @@ -743,7 +738,7 @@ mod tests { #[tokio::test] async fn get_compile_test_cases_with_data() { run_db_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let collection = db.collection_id("test").await; let artifact = db @@ -802,7 +797,7 @@ mod tests { #[tokio::test] async fn get_collector_config_error_if_not_exist() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let collector_config_result = db.start_collector("collector-1", "foo").await.unwrap(); @@ -816,7 +811,7 @@ mod tests { #[tokio::test] async fn add_collector_config() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let mut inserted_config = db .add_collector_config("collector-1", Target::X86_64UnknownLinuxGnu, 1, true) @@ -841,7 +836,7 @@ mod tests { #[tokio::test] async fn dequeue_benchmark_job_empty_queue() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let benchmark_job_result = db .dequeue_benchmark_job( @@ -862,7 +857,7 @@ mod tests { #[tokio::test] async fn dequeue_benchmark_job() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let collector_config = db @@ -931,7 +926,7 @@ mod tests { #[tokio::test] async fn mark_request_as_complete_empty() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let insert_result = db @@ -956,7 +951,7 @@ mod tests { #[tokio::test] async fn mark_request_as_complete() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap(); let benchmark_set = BenchmarkSet(0u32); let tag = "sha-1"; @@ -1034,7 +1029,7 @@ mod tests { #[tokio::test] async fn get_collector_configs() { run_postgres_test(|ctx| async { - let db = ctx.db_pool().connection().await; + let db = ctx.db(); let target = Target::X86_64UnknownLinuxGnu; let benchmark_set_one = BenchmarkSet(0u32); diff --git a/database/src/tests/mod.rs b/database/src/tests/mod.rs index 91d8bdc54..6beacd32b 100644 --- a/database/src/tests/mod.rs +++ b/database/src/tests/mod.rs @@ -96,10 +96,6 @@ impl TestContext { } } - pub fn db_pool(&self) -> &Pool { - &self.pool - } - pub fn db(&self) -> &dyn Connection { self.connection.as_ref() } From 1e0b75cea81128b6357498eed5495a1c90bcc9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 21:57:06 +0200 Subject: [PATCH 07/10] Simplify backend code of the new status page --- database/src/lib.rs | 10 +- database/src/pool.rs | 6 +- database/src/pool/postgres.rs | 10 +- database/src/pool/sqlite.rs | 8 +- site/src/api.rs | 86 ++--- site/src/request_handlers/status_page_new.rs | 338 +++++++------------ site/src/server.rs | 2 +- 7 files changed, 172 insertions(+), 288 deletions(-) diff --git a/database/src/lib.rs b/database/src/lib.rs index b56945198..cad0d9937 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -986,6 +986,10 @@ impl BenchmarkRequest { self.commit_date } + pub fn commit_type(&self) -> &BenchmarkRequestType { + &self.commit_type + } + pub fn is_master(&self) -> bool { matches!(self.commit_type, BenchmarkRequestType::Master { .. }) } @@ -1249,8 +1253,8 @@ pub struct InProgressRequestWithJobs { } #[derive(Debug, PartialEq)] -pub struct CompletedBenchmarkRequestWithErrors { - request: BenchmarkRequest, +pub struct BenchmarkRequestWithErrors { + pub request: BenchmarkRequest, /// Benchmark (name) -> error - errors: HashMap, + pub errors: HashMap, } diff --git a/database/src/pool.rs b/database/src/pool.rs index 492fc8381..2072922ec 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -1,8 +1,8 @@ use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, ArtifactIdNumber, BenchmarkJob, BenchmarkJobConclusion, - BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, - CollectorConfig, CompileBenchmark, CompletedBenchmarkRequestWithErrors, Target, + BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkRequestWithErrors, + BenchmarkSet, CodegenBackend, CollectorConfig, CompileBenchmark, Target, }; use crate::{CollectionId, Index, Profile, QueuedCommit, Scenario, Step}; use chrono::{DateTime, Utc}; @@ -282,7 +282,7 @@ pub trait Connection: Send + Sync { async fn get_last_n_completed_benchmark_requests( &self, count: u64, - ) -> anyhow::Result>; + ) -> anyhow::Result>; /// Return jobs of all requests that are currently in progress, and the jobs of their parents. /// The keys of the hashmap contain the request tags. diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index d1a3e7f77..ebfb5f53c 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -3,9 +3,9 @@ use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, ArtifactIdNumber, Benchmark, BenchmarkJob, BenchmarkJobConclusion, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestIndex, - BenchmarkRequestStatus, BenchmarkRequestType, BenchmarkSet, CodegenBackend, CollectionId, - CollectorConfig, Commit, CommitType, CompileBenchmark, CompletedBenchmarkRequestWithErrors, - Date, Index, Profile, QueuedCommit, Scenario, Target, BENCHMARK_JOB_STATUS_FAILURE_STR, + BenchmarkRequestStatus, BenchmarkRequestType, BenchmarkRequestWithErrors, BenchmarkSet, + CodegenBackend, CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, Date, + Index, Profile, QueuedCommit, Scenario, Target, BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR, BENCHMARK_JOB_STATUS_QUEUED_STR, BENCHMARK_JOB_STATUS_SUCCESS_STR, BENCHMARK_REQUEST_MASTER_STR, BENCHMARK_REQUEST_RELEASE_STR, BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR, BENCHMARK_REQUEST_STATUS_COMPLETED_STR, @@ -2048,7 +2048,7 @@ where async fn get_last_n_completed_benchmark_requests( &self, count: u64, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let rows = self .conn() .query( @@ -2090,7 +2090,7 @@ where .into_iter() .map(|request| { let errors = errors.remove(request.tag().unwrap()).unwrap_or_default(); - CompletedBenchmarkRequestWithErrors { request, errors } + BenchmarkRequestWithErrors { request, errors } }) .collect()) } diff --git a/database/src/pool/sqlite.rs b/database/src/pool/sqlite.rs index b6d758693..1d69e133d 100644 --- a/database/src/pool/sqlite.rs +++ b/database/src/pool/sqlite.rs @@ -2,9 +2,9 @@ use crate::pool::{Connection, ConnectionManager, ManagedConnection, Transaction} use crate::selector::CompileTestCase; use crate::{ ArtifactCollection, ArtifactId, Benchmark, BenchmarkJob, BenchmarkJobConclusion, - BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend, - CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, - CompletedBenchmarkRequestWithErrors, Date, Profile, Target, + BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkRequestWithErrors, + BenchmarkSet, CodegenBackend, CollectionId, CollectorConfig, Commit, CommitType, + CompileBenchmark, Date, Profile, Target, }; use crate::{ArtifactIdNumber, Index, QueuedCommit}; use chrono::{DateTime, TimeZone, Utc}; @@ -1388,7 +1388,7 @@ impl Connection for SqliteConnection { async fn get_last_n_completed_benchmark_requests( &self, _count: u64, - ) -> anyhow::Result> { + ) -> anyhow::Result> { no_queue_implementation_abort!() } } diff --git a/site/src/api.rs b/site/src/api.rs index 47b292213..b62a9e814 100644 --- a/site/src/api.rs +++ b/site/src/api.rs @@ -393,102 +393,78 @@ pub mod status { pub mod status_new { use chrono::{DateTime, Utc}; - use database::BenchmarkSet; use hashbrown::HashMap; use serde::Serialize; #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkRequestStatusUi { - pub state: String, - pub completed_at: Option>, - pub duration_s: Option, + pub enum BenchmarkRequestStatus { + Queued, + InProgress, + Completed, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkRequestTypeUi { - pub r#type: String, - pub tag: Option, - pub parent_sha: Option, - pub pr: Option, + pub enum BenchmarkRequestType { + Release, + Master, + Try, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkRequestUi { - pub status: BenchmarkRequestStatusUi, - pub request_type: BenchmarkRequestTypeUi, - pub commit_date: Option>, + pub struct BenchmarkRequest { + pub tag: String, + pub status: BenchmarkRequestStatus, + pub request_type: BenchmarkRequestType, pub created_at: DateTime, - pub backends: Vec, - pub profiles: String, - pub errors: Vec, + pub completed_at: Option>, + pub duration_s: Option, + pub errors: HashMap, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkJobStatusUi { - pub state: String, - pub started_at: Option>, - pub completed_at: Option>, - pub collector_name: Option, + pub enum BenchmarkJobStatus { + Queued, + InProgress, + Success, + Failed, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkJobUi { + pub struct BenchmarkJob { + pub request_tag: String, pub target: String, pub backend: String, pub profile: String, - pub request_tag: String, - pub benchmark_set: BenchmarkSet, + pub benchmark_set: u32, pub created_at: DateTime, - pub status: BenchmarkJobStatusUi, + pub status: BenchmarkJobStatus, pub deque_counter: u32, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] - pub struct BenchmarkInProgressUi { - pub request: BenchmarkRequestUi, - pub jobs: Vec, - } - - #[derive(Serialize, Debug, Clone)] - #[serde(rename_all = "camelCase")] - pub struct CollectorConfigUi { + pub struct Collector { pub name: String, pub target: String, - pub benchmark_set: BenchmarkSet, + pub benchmark_set: u32, pub is_active: bool, pub last_heartbeat_at: DateTime, pub date_added: DateTime, - } - - #[derive(Serialize, Debug, Clone)] - #[serde(rename_all = "camelCase")] - pub struct CollectorInfo { - /// Configuration for the collector - pub config: CollectorConfigUi, - /// Jobs that are assigned to the collector from the currently inprogress - /// request and possibly that request's parent. - pub job_ids: Vec, + pub jobs: Vec, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Response { - /// The current queue, ordered `in_progress`, ... the queue, `completed` - pub queue_request_tags: Vec, - /// Hash table of request tags to requests - pub requests_map: HashMap, - /// Hash table of job ids to jobs - pub job_map: HashMap, - /// Hash table of benchmark set ids to CollectorInfo - pub collector_work_map: HashMap, - /// Request tag to a vector of job ids - pub tag_to_jobs: HashMap>, + /// The current queue, starting from the queued request that will be benchmarked at the + /// latest time, then the `in_progress` requests, and then the `completed` requests. + pub requests: Vec, + pub collectors: Vec, } } diff --git a/site/src/request_handlers/status_page_new.rs b/site/src/request_handlers/status_page_new.rs index 61d19548c..2b8899b3e 100644 --- a/site/src/request_handlers/status_page_new.rs +++ b/site/src/request_handlers/status_page_new.rs @@ -1,244 +1,148 @@ use std::sync::Arc; -use crate::api::status_new::{ - BenchmarkJobStatusUi, BenchmarkJobUi, BenchmarkRequestStatusUi, BenchmarkRequestTypeUi, - BenchmarkRequestUi, CollectorConfigUi, CollectorInfo, -}; -use crate::api::{status_new, ServerResult}; +use crate::api::status_new; use crate::job_queue::build_queue; use crate::load::SiteCtxt; use database::{ - BenchmarkJob, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestStatus, CollectorConfig, + BenchmarkJob, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestStatus, + BenchmarkRequestType, Connection, }; use hashbrown::HashMap; -fn benchmark_request_status_to_ui(status: BenchmarkRequestStatus) -> BenchmarkRequestStatusUi { - let (completed_at, duration_s) = match status { - BenchmarkRequestStatus::Completed { - duration, - completed_at, - } => (Some(completed_at), Some(duration.as_secs())), - _ => (None, None), - }; +pub async fn handle_status_page_new(ctxt: Arc) -> anyhow::Result { + let conn = ctxt.conn().await; - BenchmarkRequestStatusUi { - state: status.as_str().to_owned(), - completed_at, - duration_s, - } + let index = conn.load_benchmark_request_index().await?; + + // The queue contains any in-progress request(s) and then the following requests in queue order + // We reverse so that it starts with the request that will be benchmarked the latest + let mut queue: Vec = build_queue(&*conn, &index) + .await? + .into_iter() + .map(|req| request_to_ui(&req, HashMap::new())) + .collect(); + queue.reverse(); + // And then we add N most recently completed requests to it + let completed = conn.get_last_n_completed_benchmark_requests(10).await?; + queue.extend( + completed + .into_iter() + .map(|req| request_to_ui(&req.request, req.errors)), + ); + + let collectors = build_collectors(conn.as_ref()).await?; + + Ok(status_new::Response { + requests: queue, + collectors, + }) } -fn benchmark_request_type_to_ui(req: &BenchmarkRequest) -> BenchmarkRequestTypeUi { - BenchmarkRequestTypeUi { - r#type: match (req.is_release(), req.is_master()) { - (true, _) => "Release", - (_, true) => "Master", - _ => "Try", +async fn build_collectors(conn: &dyn Connection) -> anyhow::Result> { + let in_progress_jobs = conn.get_jobs_of_in_progress_benchmark_requests().await?; + let collectors = conn.get_collector_configs().await?; + let mut collector_map: HashMap = collectors + .into_iter() + .map(|c| { + ( + c.name().to_string(), + status_new::Collector { + name: c.name().to_string(), + target: c.target().to_string(), + benchmark_set: c.benchmark_set().get_id(), + is_active: c.is_active(), + last_heartbeat_at: c.last_heartbeat_at(), + date_added: c.date_added(), + jobs: vec![], + }, + ) + }) + .collect(); + + // This map is used to guess a future collector for jobs that haven't been dequeued yet + // (target, benchmark_set) -> collector name + let collector_guess_map: HashMap<(String, u32), String> = collector_map + .iter() + .map(|(name, collector)| { + ( + (collector.target.to_string(), collector.benchmark_set), + name.clone(), + ) + }) + .collect(); + + // Map jobs to collectors. Even if a collector has not started a job yet, we can guess if it + // will execute it or not, based on its target and benchmark set + for job in in_progress_jobs.values().flatten() { + let Some(collector_name) = job.collector_name().or_else(|| { + collector_guess_map + .get(&(job.target().to_string(), job.benchmark_set().get_id())) + .map(|n| n.as_str()) + }) else { + continue; + }; + if let Some(collector) = collector_map.get_mut(collector_name) { + collector.jobs.push(job_to_ui(job)); } - .to_owned(), - tag: req.tag().map(|it| it.to_owned()), - parent_sha: req.parent_sha().map(|it| it.to_owned()), - pr: req.pr().copied(), } + let mut collectors: Vec = collector_map.into_values().collect(); + collectors.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + Ok(collectors) } -fn benchmark_request_to_ui( +fn request_to_ui( req: &BenchmarkRequest, - errors: Vec, -) -> anyhow::Result { - Ok(BenchmarkRequestUi { - status: benchmark_request_status_to_ui(req.status()), - request_type: benchmark_request_type_to_ui(req), - commit_date: req.commit_date(), - created_at: req.created_at(), - backends: req.backends()?.iter().map(|it| it.to_string()).collect(), - profiles: req.profiles()?.iter().map(|it| it.to_string()).collect(), - errors, - }) -} - -fn benchmark_job_status_to_ui(status: &BenchmarkJobStatus) -> BenchmarkJobStatusUi { - let (started_at, completed_at, collector_name_ref) = match status { - BenchmarkJobStatus::Queued => (None, None, None), - BenchmarkJobStatus::InProgress { - started_at, - collector_name, - } => (Some(*started_at), None, Some(collector_name)), - BenchmarkJobStatus::Completed { - started_at, + errors: HashMap, +) -> status_new::BenchmarkRequest { + let (completed_at, duration_s) = match req.status() { + BenchmarkRequestStatus::WaitingForArtifacts => (None, None), + BenchmarkRequestStatus::ArtifactsReady => (None, None), + BenchmarkRequestStatus::InProgress => (None, None), + BenchmarkRequestStatus::Completed { completed_at, - collector_name, - .. - } => (Some(*started_at), Some(*completed_at), Some(collector_name)), + duration, + } => (Some(completed_at), Some(duration.as_secs())), }; - - BenchmarkJobStatusUi { - state: status.as_str().to_owned(), - started_at, + status_new::BenchmarkRequest { + tag: req.tag().expect("Missing request tag").to_string(), + status: match req.status() { + BenchmarkRequestStatus::WaitingForArtifacts => unreachable!(), + BenchmarkRequestStatus::ArtifactsReady => status_new::BenchmarkRequestStatus::Queued, + BenchmarkRequestStatus::InProgress => status_new::BenchmarkRequestStatus::InProgress, + BenchmarkRequestStatus::Completed { .. } => { + status_new::BenchmarkRequestStatus::Completed + } + }, + request_type: match req.commit_type() { + BenchmarkRequestType::Try { .. } => status_new::BenchmarkRequestType::Try, + BenchmarkRequestType::Master { .. } => status_new::BenchmarkRequestType::Master, + BenchmarkRequestType::Release { .. } => status_new::BenchmarkRequestType::Release, + }, + created_at: req.created_at(), completed_at, - collector_name: collector_name_ref.cloned(), + duration_s, + errors, } } -fn benchmark_job_to_ui(job: &BenchmarkJob) -> BenchmarkJobUi { - BenchmarkJobUi { - target: job.target().as_str().to_owned(), - backend: job.backend().as_str().to_owned(), - profile: job.profile().as_str().to_owned(), - request_tag: job.request_tag().to_owned(), - benchmark_set: job.benchmark_set(), +fn job_to_ui(job: &BenchmarkJob) -> status_new::BenchmarkJob { + status_new::BenchmarkJob { + request_tag: job.request_tag().to_string(), + target: job.target().as_str().to_string(), + backend: job.backend().as_str().to_string(), + profile: job.profile().as_str().to_string(), + benchmark_set: job.benchmark_set().get_id(), created_at: job.created_at(), - status: benchmark_job_status_to_ui(job.status()), - deque_counter: job.deque_count(), - } -} - -fn collector_config_to_ui(config: &CollectorConfig) -> CollectorConfigUi { - CollectorConfigUi { - name: config.name().to_owned(), - target: config.target().as_str().to_owned(), - benchmark_set: config.benchmark_set(), - is_active: config.is_active(), - last_heartbeat_at: config.last_heartbeat_at(), - date_added: config.date_added(), - } -} - -pub async fn handle_status_page_new(ctxt: Arc) -> ServerResult { - let conn = ctxt.conn().await; - - let error_to_string = |e: anyhow::Error| e.to_string(); - - let collector_configs: Vec = conn - .get_collector_configs() - .await - .map_err(error_to_string)? - .iter() - .map(collector_config_to_ui) - .collect(); - // The query gives us `max_completed_requests` number of completed requests - // and all inprogress requests without us needing to specify - // - // @TODO; for `in_progress` requests we could look at the the completed - // `requests`, then use the `duration_ms` to display an estimated job - // finish time. Could also do that on the frontend but probably makes - // sense to do in SQL. - let partial_data = conn.get_status_page_data().await.map_err(error_to_string)?; - - let index = conn - .load_benchmark_request_index() - .await - .map_err(error_to_string)?; - - // We add the in_progress_tags first, then the queue, then completed - let mut queue_request_tags: Vec = vec![]; - let mut requests_map: HashMap = HashMap::new(); - let mut job_map: HashMap = HashMap::new(); - let mut collector_work_map: HashMap = HashMap::new(); - let mut tag_to_jobs: HashMap> = HashMap::new(); - - for it in collector_configs.iter() { - // Multiple collectors cannot be part of the same set - collector_work_map.insert( - it.benchmark_set.0, - CollectorInfo { - config: it.clone(), - job_ids: vec![], - }, - ); - } - - let mut jobs: Vec<&BenchmarkJob> = vec![]; - - for it in partial_data.in_progress.iter() { - let tag = it.request.0.tag().unwrap().to_string(); - queue_request_tags.push(tag.clone()); - requests_map.insert( - tag.clone(), - benchmark_request_to_ui(&it.request.0, vec![]).map_err(error_to_string)?, - ); - - for job in it.request.1.iter() { - if let Some(jobs) = tag_to_jobs.get_mut(&tag) { - jobs.push(job.id()); - } else { - tag_to_jobs.insert(tag.clone(), vec![job.id()]); + status: match job.status() { + BenchmarkJobStatus::Queued => status_new::BenchmarkJobStatus::Queued, + BenchmarkJobStatus::InProgress { .. } => status_new::BenchmarkJobStatus::InProgress, + BenchmarkJobStatus::Completed { success: true, .. } => { + status_new::BenchmarkJobStatus::Success } - jobs.push(job); - } - - if let Some(parent) = &it.parent { - let parent_tag = parent.0.tag().unwrap().to_string(); - queue_request_tags.push(parent_tag.clone()); - requests_map.insert( - parent_tag.clone(), - benchmark_request_to_ui(&parent.0, vec![]).map_err(error_to_string)?, - ); - - for parent_job in parent.1.iter() { - if let Some(jobs) = tag_to_jobs.get_mut(&parent_tag) { - jobs.push(parent_job.id()); - } else { - tag_to_jobs.insert(parent_tag.clone(), vec![parent_job.id()]); - } - jobs.push(parent_job); + BenchmarkJobStatus::Completed { success: false, .. } => { + status_new::BenchmarkJobStatus::Failed } - } - } - - // Create the queue - let queue = build_queue(&*conn, &index).await.map_err(error_to_string)?; - - for it in queue.iter() { - // We have already added the inprogress tags to the queue from the above - // transformative loop. We do that as the queue will not clock that a - // parent request is going to have work despite having a status of - // `complete`. - let tag = it.tag().unwrap().to_string(); - if !matches!(it.status(), BenchmarkRequestStatus::InProgress) { - queue_request_tags.push(tag.clone()); - requests_map.insert( - tag, - benchmark_request_to_ui(it, vec![]).map_err(error_to_string)?, - ); - } - } - - for it in partial_data.completed_requests { - let tag = it.0.tag().unwrap().to_string(); - // To get the requests for the queue in the front end we iterate over - // the tags array then index requests map. - requests_map.insert( - tag, - benchmark_request_to_ui(&it.0, it.1).map_err(error_to_string)?, - ); - } - - // sort the jobs - jobs.sort_by_key(|job| { - ( - match job.status() { - BenchmarkJobStatus::InProgress { .. } => 0, - BenchmarkJobStatus::Queued => 1, - BenchmarkJobStatus::Completed { .. } => 2, - }, - job.created_at(), - ) - }); - - for it in jobs.iter() { - let ui_job = benchmark_job_to_ui(it); - job_map.insert(it.id(), ui_job); - if let Some(collector) = collector_work_map.get_mut(&it.benchmark_set().0) { - collector.job_ids.push(it.id()); - } + }, + deque_counter: job.deque_count(), } - - Ok(status_new::Response { - queue_request_tags, - requests_map, - job_map, - collector_work_map, - tag_to_jobs, - }) } diff --git a/site/src/server.rs b/site/src/server.rs index cc507957f..426f907c0 100644 --- a/site/src/server.rs +++ b/site/src/server.rs @@ -394,7 +394,7 @@ async fn serve_req(server: Server, req: Request) -> Result Date: Fri, 29 Aug 2025 22:28:27 +0200 Subject: [PATCH 08/10] Bump release/master commit cut-off --- site/src/job_queue/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/job_queue/mod.rs b/site/src/job_queue/mod.rs index 9693fbdce..6b1c1b9a3 100644 --- a/site/src/job_queue/mod.rs +++ b/site/src/job_queue/mod.rs @@ -27,7 +27,7 @@ async fn create_benchmark_request_master_commits( ) -> anyhow::Result<()> { let master_commits = &ctxt.get_master_commits().commits; // TODO; delete at some point in the future - let cutoff: chrono::DateTime = chrono::DateTime::from_str("2025-07-24T00:00:00.000Z")?; + let cutoff: chrono::DateTime = chrono::DateTime::from_str("2025-08-27T00:00:00.000Z")?; for master_commit in master_commits { // We don't want to add masses of obsolete data @@ -59,7 +59,7 @@ async fn create_benchmark_request_releases( .text() .await?; // TODO; delete at some point in the future - let cutoff: chrono::DateTime = chrono::DateTime::from_str("2025-06-01T00:00:00.000Z")?; + let cutoff: chrono::DateTime = chrono::DateTime::from_str("2025-08-27T00:00:00.000Z")?; let releases: Vec<_> = releases .lines() From a76ae7cf515b0911b948c8dcd8255855989e2449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 29 Aug 2025 22:46:16 +0200 Subject: [PATCH 09/10] Simplify loading of backend data in new status page --- database/src/lib.rs | 4 +- database/src/pool.rs | 8 +- database/src/pool/postgres.rs | 2 +- .../src/pages/status_new/collector.vue | 41 ++--- site/frontend/src/pages/status_new/data.ts | 153 ++-------------- site/frontend/src/pages/status_new/page.vue | 165 +++++++++--------- site/src/api.rs | 8 +- site/src/job_queue/mod.rs | 4 +- site/src/request_handlers/status_page_new.rs | 40 +++++ 9 files changed, 165 insertions(+), 260 deletions(-) diff --git a/database/src/lib.rs b/database/src/lib.rs index cad0d9937..9633044bc 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -957,10 +957,10 @@ impl BenchmarkRequest { } } - pub fn pr(&self) -> Option<&u32> { + pub fn pr(&self) -> Option { match &self.commit_type { BenchmarkRequestType::Try { pr, .. } | BenchmarkRequestType::Master { pr, .. } => { - Some(pr) + Some(*pr) } BenchmarkRequestType::Release { .. } => None, } diff --git a/database/src/pool.rs b/database/src/pool.rs index 2072922ec..571b4510e 100644 --- a/database/src/pool.rs +++ b/database/src/pool.rs @@ -575,8 +575,8 @@ mod tests { .await .unwrap(); - complete_request(&*db, "sha-parent-1", collector_name, benchmark_set, target).await; - complete_request(&*db, "sha1", collector_name, benchmark_set, target).await; + complete_request(db, "sha-parent-1", collector_name, benchmark_set, target).await; + complete_request(db, "sha1", collector_name, benchmark_set, target).await; // This should be fine, req_a was completed db.insert_benchmark_request(&req_b).await.unwrap(); @@ -647,7 +647,7 @@ mod tests { db.insert_benchmark_request(req).await.unwrap(); } - complete_request(&*db, "1.79.0", collector_name, benchmark_set, target).await; + complete_request(db, "1.79.0", collector_name, benchmark_set, target).await; db.update_benchmark_request_status("sha-2", BenchmarkRequestStatus::InProgress) .await @@ -697,7 +697,7 @@ mod tests { assert_eq!(req_db.tag(), Some("sha1")); assert_eq!(req_db.parent_sha(), Some("sha-parent-1")); - assert_eq!(req_db.pr(), Some(&42)); + assert_eq!(req_db.pr(), Some(42)); Ok(ctx) }) diff --git a/database/src/pool/postgres.rs b/database/src/pool/postgres.rs index ebfb5f53c..f681a6877 100644 --- a/database/src/pool/postgres.rs +++ b/database/src/pool/postgres.rs @@ -1565,7 +1565,7 @@ where &[ &benchmark_request.tag(), &benchmark_request.parent_sha(), - &benchmark_request.pr().map(|it| *it as i32), + &benchmark_request.pr().map(|it| it as i32), &benchmark_request.commit_type, &benchmark_request.status.as_str(), &benchmark_request.created_at, diff --git a/site/frontend/src/pages/status_new/collector.vue b/site/frontend/src/pages/status_new/collector.vue index 3123ecf00..68a8f5d00 100644 --- a/site/frontend/src/pages/status_new/collector.vue +++ b/site/frontend/src/pages/status_new/collector.vue @@ -1,18 +1,13 @@ diff --git a/site/frontend/src/pages/status_new/data.ts b/site/frontend/src/pages/status_new/data.ts index 4066b4934..27b9aa81b 100644 --- a/site/frontend/src/pages/status_new/data.ts +++ b/site/frontend/src/pages/status_new/data.ts @@ -1,142 +1,28 @@ -export const BenchmarkRequestCompleteStr = "completed"; -export const BenchmarkRequestInProgressStr = "in_progress"; -export const BenchmarkRequestArtifactsReadyStr = "artifacts_ready"; +export type BenchmarkRequestType = "Release" | "Master" | "Try"; +export type BenchmarkRequestStatus = "Queued" | "InProgress" | "Completed"; -type BenchmarkRequestStatusComplete = { - state: typeof BenchmarkRequestCompleteStr; - completedAt: string; - duration_s: number; -}; - -type BenchmarkRequestStatusInProgress = { - state: typeof BenchmarkRequestInProgressStr; -}; - -type BenchmarkRequestStatusArtifactsReady = { - state: typeof BenchmarkRequestArtifactsReadyStr; -}; - -export type BenchmarkRequestStatus = - | BenchmarkRequestStatusComplete - | BenchmarkRequestStatusInProgress - | BenchmarkRequestStatusArtifactsReady; - -export const TryCommit = "Try"; -export const MasterCommit = "Master"; -export const ReleaseCommit = "Release"; - -type BenchmarkRequestTypeTry = { - type: typeof TryCommit; - tag: string | null; - parent_sha: string | null; - pr: number; -}; - -type BenchmarkRequestTypeMaster = { - type: typeof MasterCommit; +export type BenchmarkRequest = { tag: string; - parent_sha: string; - pr: number; -}; - -type BenchmarkRequestTypeRelease = { - type: typeof ReleaseCommit; - tag: string; -}; - -export type BenchmarkRequestTypeStr = - | typeof ReleaseCommit - | typeof MasterCommit - | typeof TryCommit; - -export type BenchmarkRequestType = - | BenchmarkRequestTypeTry - | BenchmarkRequestTypeMaster - | BenchmarkRequestTypeRelease; - -export type BenchmarkRequestComplete = { - status: BenchmarkRequestStatusComplete; - requestType: BenchmarkRequestType; - commitDate: string | null; - createdAt: string | null; - backends: string[]; - profiles: string; - errors: string[]; -}; - -export type BenchmarkRequestInProgress = { - status: BenchmarkRequestStatusInProgress; + pr: number | null; + status: BenchmarkRequestStatus; requestType: BenchmarkRequestType; - commitDate: string | null; - createdAt: string | null; - backends: string[]; - profiles: string; - errors: string[]; -}; - -export type BenchmarkRequestArtifactsReady = { - status: BenchmarkRequestStatusArtifactsReady; - requestType: BenchmarkRequestType; - commitDate: string | null; - createdAt: string | null; - backends: string[]; - profiles: string; - errors: string[]; -}; - -export type BenchmarkRequest = - | BenchmarkRequestComplete - | BenchmarkRequestInProgress - | BenchmarkRequestArtifactsReady; - -export const BenchmarkJobQueued = "queued"; -export const BenchmarkJobInProgress = "in_progres"; -export const BenchmarkJobFailed = "failed"; -export const BenchmarkJobSuccess = "success"; - -export type BenchmarkJobStatusQueued = { - state: typeof BenchmarkJobQueued; -}; - -export type BenchmarkJobStatusInProgress = { - state: typeof BenchmarkJobInProgress; - startedAt: string; - collectorName: string; -}; - -export type BenchmarkJobStatusFailed = { - state: typeof BenchmarkJobFailed; - startedAt: string; - completedAt: string; - collectorName: string; -}; - -export type BenchmarkJobStatusSuccess = { - state: typeof BenchmarkJobSuccess; - startedAt: string; - completedAt: string; - collectorName: string; + createdAt: string; + completedAt: string | null; + durationS: number | null; + errors: Dict; }; -export type BenchmarkJobStatusStr = - | typeof BenchmarkJobQueued - | typeof BenchmarkJobInProgress - | typeof BenchmarkJobFailed - | typeof BenchmarkJobSuccess; - -export type BenchmarkJobStatus = - | BenchmarkJobStatusSuccess - | BenchmarkJobStatusFailed - | BenchmarkJobStatusInProgress - | BenchmarkJobStatusQueued; +export type BenchmarkJobStatus = "Queued" | "InProgress" | "Success" | "Failed"; export type BenchmarkJob = { + requestTag: string; target: string; backend: string; profile: string; - requestTag: string; benchmarkSet: number; createdAt: string; + startedAt: string | null; + completedAt: string | null; status: BenchmarkJobStatus; dequeCounter: number; }; @@ -148,17 +34,10 @@ export type CollectorConfig = { isActive: boolean; lastHeartbeatAt: string; dateAdded: string; -}; - -export type CollectorInfo = { - config: CollectorConfig; - jobIds: number[]; + jobs: BenchmarkJob[]; }; export type StatusResponse = { - queueRequestTags: string[]; - requestsMap: Dict; - jobMap: Dict; - collectorWorkMap: Dict; - tagToJobs: Dict; + requests: BenchmarkRequest[]; + collectors: CollectorConfig[]; }; diff --git a/site/frontend/src/pages/status_new/page.vue b/site/frontend/src/pages/status_new/page.vue index f245ee152..1487ec1d9 100644 --- a/site/frontend/src/pages/status_new/page.vue +++ b/site/frontend/src/pages/status_new/page.vue @@ -6,50 +6,29 @@ import {STATUS_DATA_NEW_URL} from "../../urls"; import {withLoading} from "../../utils/loading"; import {formatSecondsAsDuration} from "../../utils/formatting"; import { - StatusResponse, - BenchmarkRequestType, BenchmarkRequest, - BenchmarkJob, - CollectorInfo, - ReleaseCommit, - BenchmarkRequestCompleteStr, - BenchmarkRequestInProgressStr, + BenchmarkRequestStatus, + CollectorConfig, + StatusResponse, } from "./data"; import Collector from "./collector.vue"; const loading = ref(true); -const dataNew: Ref<{ - queueLength: number; +const data: Ref<{ timeline: BenchmarkRequestWithWaterLine[]; - requestsMap: Dict; - jobMap: Dict; - collectorWorkMap: Dict; - tagToJobs: Dict; + queueLength: number; + collectors: CollectorConfig[]; } | null> = ref(null); -type BenchmarkRequestWithWaterLine = BenchmarkRequest & {isWaterLine: boolean}; +type BenchmarkRequestWithWaterLine = BenchmarkRequest & { + isLastInProgress: boolean; + hasPendingJobs: boolean; +}; -function requestIsInProgress(req: BenchmarkRequest, tagToJobs: Dict) { - switch (req.status.state) { - case BenchmarkRequestCompleteStr: - if (req.requestType.tag in tagToJobs) { - return true; - } - return false; - case BenchmarkRequestInProgressStr: - return true; - default: - return false; - } -} - -function getRequestRowClassName( - req: BenchmarkRequestWithWaterLine, - tagToJobs: Dict -) { - const inProgress = requestIsInProgress(req, tagToJobs); - if (inProgress && req.isWaterLine) { +function getRequestRowClassName(req: BenchmarkRequestWithWaterLine) { + const inProgress = req.status === "InProgress"; + if (inProgress && req.isLastInProgress) { return "timeline-waterline"; } else if (inProgress) { return "timeline-row-bold"; @@ -57,79 +36,95 @@ function getRequestRowClassName( return ""; } -async function loadStatusNew(loading: Ref) { - dataNew.value = await withLoading(loading, async () => { - let d: StatusResponse = await getJson(STATUS_DATA_NEW_URL); +async function loadStatusData(loading: Ref) { + data.value = await withLoading(loading, async () => { + let resp: StatusResponse = await getJson( + STATUS_DATA_NEW_URL + ); let timeline: BenchmarkRequestWithWaterLine[] = []; - // figure out where to draw the line. - for (let i = 1; i < d.queueRequestTags.length; ++i) { - let req = d.requestsMap[d.queueRequestTags[i - 1]]; - let nextReq = d.requestsMap[d.queueRequestTags[i]]; - let isWaterLine = false; - if ( - requestIsInProgress(req, d.tagToJobs) && - !requestIsInProgress(nextReq, d.tagToJobs) - ) { - isWaterLine = true; + + let queueLength = 0; + + let requests_with_pending_jobs = new Set(); + for (const job of resp.collectors.flatMap((c) => c.jobs)) { + if (job.status === "Queued" || job.status === "InProgress") { + requests_with_pending_jobs.add(job.requestTag); } + } + + // Figure out where to draw the line. + for (let i = 0; i < resp.requests.length; i++) { + let request = resp.requests[i]; + let isLastInProgress = + request.status === "InProgress" && + (i == resp.requests.length - 1 || + resp.requests[i + 1].status !== "InProgress"); timeline.push({ - ...req, - isWaterLine, + ...request, + isLastInProgress, + hasPendingJobs: requests_with_pending_jobs.has(request.tag), }); + + if (request.status !== "Completed") { + queueLength += 1; + } } + return { - queueLength: d.queueRequestTags.length, timeline, - requestsMap: d.requestsMap, - jobMap: d.jobMap, - collectorWorkMap: d.collectorWorkMap, - tagToJobs: d.tagToJobs, + collectors: resp.collectors, + queueLength, }; }); } -function getCreatedAt(request: BenchmarkRequest): string { - if (request.status.state == BenchmarkRequestCompleteStr) { - return request.status.completedAt; +function getDuration(request: BenchmarkRequest): string { + if (request.status === "Completed") { + return formatSecondsAsDuration(request.durationS); } return ""; } -function getDuration(request: BenchmarkRequest): string { - if (request.status.state == BenchmarkRequestCompleteStr) { - return formatSecondsAsDuration(request.status.duration_s); +function formatStatus(status: BenchmarkRequestStatus): string { + if (status === "Completed") { + return "Finished"; + } else if (status === "InProgress") { + return "In progress"; + } else if (status === "Queued") { + return "Queued"; + } else { + return "Unknown"; } - return ""; } -function PullRequestLink({requestType}: {requestType: BenchmarkRequestType}) { - if (requestType.type === ReleaseCommit) { +function PullRequestLink({request}: {request: BenchmarkRequest}) { + if (request.requestType === "Release") { return ""; } return ( - - #{requestType.pr} + + #{request.pr} ); } -loadStatusNew(loading); +loadStatusData(loading);