Skip to content

Commit f75d4a2

Browse files
committed
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.
1 parent 0d32dcd commit f75d4a2

File tree

6 files changed

+385
-14
lines changed

6 files changed

+385
-14
lines changed

database/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,13 @@ pub struct InProgressRequestWithJobs {
12481248
pub parent: Option<(BenchmarkRequest, Vec<BenchmarkJob>)>,
12491249
}
12501250

1251+
#[derive(Debug, PartialEq)]
1252+
pub struct CompletedBenchmarkRequestWithErrors {
1253+
request: BenchmarkRequest,
1254+
/// Benchmark (name) -> error
1255+
errors: HashMap<String, String>,
1256+
}
1257+
12511258
/// The data that can be retrived from the database directly to populate the
12521259
/// status page
12531260
#[derive(Debug, PartialEq)]

database/src/pool.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::selector::CompileTestCase;
22
use crate::{
33
ArtifactCollection, ArtifactId, ArtifactIdNumber, BenchmarkJob, BenchmarkJobConclusion,
44
BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend,
5-
CollectorConfig, CompileBenchmark, PartialStatusPageData, Target,
5+
CollectorConfig, CompileBenchmark, CompletedBenchmarkRequestWithErrors, PartialStatusPageData,
6+
Target,
67
};
78
use crate::{CollectionId, Index, Profile, QueuedCommit, Scenario, Step};
89
use chrono::{DateTime, Utc};
@@ -277,6 +278,15 @@ pub trait Connection: Send + Sync {
277278

278279
async fn get_status_page_data(&self) -> anyhow::Result<PartialStatusPageData>;
279280

281+
/// Return the last `count` completed benchmark requests, along with all errors associated with
282+
/// them.
283+
///
284+
/// The requests will be ordered from most recently to least recently completed.
285+
async fn get_last_n_completed_benchmark_requests(
286+
&self,
287+
count: u64,
288+
) -> anyhow::Result<Vec<CompletedBenchmarkRequestWithErrors>>;
289+
280290
/// Get all of the configuration for all of the collectors
281291
async fn get_collector_configs(&self) -> anyhow::Result<Vec<CollectorConfig>>;
282292

@@ -403,6 +413,7 @@ impl Pool {
403413
mod tests {
404414
use super::*;
405415
use crate::metric::Metric;
416+
use crate::tests::builder::{JobBuilder, RequestBuilder};
406417
use crate::tests::run_postgres_test;
407418
use crate::BenchmarkJobStatus;
408419
use crate::{tests::run_db_test, BenchmarkRequestType, Commit, CommitType, Date};
@@ -1190,4 +1201,80 @@ mod tests {
11901201
})
11911202
.await;
11921203
}
1204+
1205+
#[tokio::test]
1206+
async fn get_last_completed_requests() {
1207+
run_postgres_test(|ctx| async {
1208+
let mut requests = vec![];
1209+
let db = ctx.db();
1210+
1211+
let collector = ctx.add_collector(Default::default()).await;
1212+
1213+
// Create several completed requests
1214+
for id in 1..=3 {
1215+
// Make some space between completions
1216+
tokio::time::sleep(Duration::from_millis(100)).await;
1217+
1218+
requests.push(
1219+
RequestBuilder::master(
1220+
db,
1221+
&format!("sha{}", id),
1222+
&format!("sha{}", id - 1),
1223+
id,
1224+
)
1225+
.await
1226+
.add_job(db, JobBuilder::new())
1227+
.await
1228+
.complete(db, &collector)
1229+
.await,
1230+
);
1231+
}
1232+
1233+
// Create an additional non-completed request
1234+
ctx.insert_master_request("foo", "bar", 1000).await;
1235+
1236+
// Request 1 will have artifact with errors
1237+
let aid1 = ctx.upsert_master_artifact("sha1").await;
1238+
db.record_error(aid1, "crate1", "error1").await;
1239+
db.record_error(aid1, "crate2", "error2").await;
1240+
1241+
// Request 2 will have artifact without errors
1242+
let _aid2 = ctx.upsert_master_artifact("sha2").await;
1243+
1244+
// Request 3 will have no artifact (shouldn't happen in practice, but...)
1245+
1246+
let reqs = db.get_last_n_completed_benchmark_requests(5).await.unwrap();
1247+
assert_eq!(reqs.len(), 3);
1248+
1249+
let expected = [
1250+
("sha3", HashMap::new()),
1251+
("sha2", HashMap::new()),
1252+
(
1253+
"sha1",
1254+
HashMap::from([
1255+
("crate1".to_string(), "error1".to_string()),
1256+
("crate2".to_string(), "error2".to_string()),
1257+
]),
1258+
),
1259+
];
1260+
for ((sha, errors), req) in expected.into_iter().zip(reqs) {
1261+
assert_eq!(
1262+
req.request.tag().unwrap(),
1263+
sha,
1264+
"Request {req:?} does not have expected sha {sha}"
1265+
);
1266+
assert_eq!(
1267+
req.errors, errors,
1268+
"Request {req:?} does not have expected errors {errors:?}"
1269+
);
1270+
}
1271+
1272+
let reqs = db.get_last_n_completed_benchmark_requests(1).await.unwrap();
1273+
assert_eq!(reqs.len(), 1);
1274+
assert_eq!(reqs[0].request.tag().unwrap(), "sha3");
1275+
1276+
Ok(ctx)
1277+
})
1278+
.await;
1279+
}
11931280
}

database/src/pool/postgres.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use crate::{
44
ArtifactCollection, ArtifactId, ArtifactIdNumber, Benchmark, BenchmarkJob,
55
BenchmarkJobConclusion, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestIndex,
66
BenchmarkRequestStatus, BenchmarkRequestType, BenchmarkSet, CodegenBackend, CollectionId,
7-
CollectorConfig, Commit, CommitType, CompileBenchmark, Date, InProgressRequestWithJobs, Index,
8-
PartialStatusPageData, Profile, QueuedCommit, Scenario, Target,
9-
BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR,
7+
CollectorConfig, Commit, CommitType, CompileBenchmark, CompletedBenchmarkRequestWithErrors,
8+
Date, InProgressRequestWithJobs, Index, PartialStatusPageData, Profile, QueuedCommit, Scenario,
9+
Target, BENCHMARK_JOB_STATUS_FAILURE_STR, BENCHMARK_JOB_STATUS_IN_PROGRESS_STR,
1010
BENCHMARK_JOB_STATUS_QUEUED_STR, BENCHMARK_JOB_STATUS_SUCCESS_STR,
1111
BENCHMARK_REQUEST_MASTER_STR, BENCHMARK_REQUEST_RELEASE_STR,
1212
BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR, BENCHMARK_REQUEST_STATUS_COMPLETED_STR,
@@ -471,6 +471,7 @@ pub struct CachedStatements {
471471
get_artifact_size: Statement,
472472
load_benchmark_request_index: Statement,
473473
get_compile_test_cases_with_measurements: Statement,
474+
get_last_n_completed_requests_with_errors: Statement,
474475
}
475476

476477
pub struct PostgresTransaction<'a> {
@@ -667,12 +668,45 @@ impl PostgresConnection {
667668
WHERE aid = $1
668669
)
669670
").await.unwrap(),
671+
get_last_n_completed_requests_with_errors: conn.prepare(&format!("
672+
WITH completed AS (
673+
SELECT {BENCHMARK_REQUEST_COLUMNS}
674+
FROM benchmark_request
675+
WHERE status = $1
676+
-- Select last N completed requests
677+
ORDER BY completed_at DESC
678+
LIMIT $2
679+
), artifacts AS (
680+
SELECT artifact.id, name
681+
FROM artifact
682+
-- Use right join to only return artifacts for selected requests
683+
RIGHT JOIN completed ON artifact.name = completed.tag
684+
), errors AS (
685+
SELECT
686+
artifacts.name AS tag,
687+
error.benchmark,
688+
error.error
689+
FROM error
690+
-- Use right join to only return errors for selected artifacts
691+
RIGHT JOIN artifacts ON error.aid = artifacts.id
692+
)
693+
-- Select request duplicated for each pair of (benchmark, error)
694+
SELECT
695+
completed.*,
696+
errors.benchmark,
697+
errors.error
698+
FROM completed
699+
LEFT JOIN errors ON errors.tag = completed.tag
700+
-- Resort the requests, because the original order may be lost
701+
ORDER BY completed.completed_at DESC;
702+
")).await.unwrap(),
670703
}),
671704
conn,
672705
}
673706
}
674707
}
675708

709+
// `tag` should be kept as the first column
676710
const BENCHMARK_REQUEST_COLUMNS: &str =
677711
"tag, parent_sha, pr, commit_type, status, created_at, completed_at, backends, profiles, commit_date, duration_ms";
678712

@@ -1986,6 +2020,56 @@ where
19862020
Ok(())
19872021
}
19882022

2023+
async fn get_last_n_completed_benchmark_requests(
2024+
&self,
2025+
count: u64,
2026+
) -> anyhow::Result<Vec<CompletedBenchmarkRequestWithErrors>> {
2027+
let rows = self
2028+
.conn()
2029+
.query(
2030+
&self.statements().get_last_n_completed_requests_with_errors,
2031+
&[&BENCHMARK_REQUEST_STATUS_COMPLETED_STR, &(count as i64)],
2032+
)
2033+
.await?;
2034+
2035+
// Iterate through the requests and aggregate their errors
2036+
// Make sure to keep their original order
2037+
let mut requests = vec![];
2038+
// tag -> errors
2039+
let mut errors: HashMap<String, HashMap<String, String>> = Default::default();
2040+
2041+
for row in rows {
2042+
let tag = row.get::<_, &str>(0);
2043+
let error_benchmark = row.get::<_, Option<String>>(11);
2044+
let error_content = row.get::<_, Option<String>>(12);
2045+
2046+
// We already saw this request, just add errors
2047+
if let Some(errors) = errors.get_mut(tag) {
2048+
if let Some(benchmark) = error_benchmark {
2049+
errors.insert(benchmark, error_content.unwrap_or_default());
2050+
}
2051+
} else {
2052+
// We see this request for the first time
2053+
let request = row_to_benchmark_request(&row, None);
2054+
let request_errors = if let Some(benchmark) = error_benchmark {
2055+
HashMap::from([(benchmark, error_content.unwrap_or_default())])
2056+
} else {
2057+
HashMap::new()
2058+
};
2059+
errors.insert(tag.to_string(), request_errors);
2060+
requests.push(request);
2061+
}
2062+
}
2063+
2064+
Ok(requests
2065+
.into_iter()
2066+
.map(|request| {
2067+
let errors = errors.remove(request.tag().unwrap()).unwrap_or_default();
2068+
CompletedBenchmarkRequestWithErrors { request, errors }
2069+
})
2070+
.collect())
2071+
}
2072+
19892073
async fn get_status_page_data(&self) -> anyhow::Result<PartialStatusPageData> {
19902074
let max_completed_requests = 30;
19912075

@@ -2273,7 +2357,9 @@ fn row_to_benchmark_request(row: &Row, row_offset: Option<usize>) -> BenchmarkRe
22732357

22742358
let status =
22752359
BenchmarkRequestStatus::from_str_and_completion_date(status, completed_at, duration_ms)
2276-
.expect("Invalid BenchmarkRequestStatus data in the database");
2360+
.unwrap_or_else(|e| {
2361+
panic!("Invalid BenchmarkRequestStatus data in the database for tag {tag:?}: {e:?}")
2362+
});
22772363

22782364
match commit_type {
22792365
BENCHMARK_REQUEST_TRY_STR => BenchmarkRequest {

database/src/pool/sqlite.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use crate::selector::CompileTestCase;
33
use crate::{
44
ArtifactCollection, ArtifactId, Benchmark, BenchmarkJob, BenchmarkJobConclusion,
55
BenchmarkRequest, BenchmarkRequestIndex, BenchmarkRequestStatus, BenchmarkSet, CodegenBackend,
6-
CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark, Date,
7-
PartialStatusPageData, Profile, Target,
6+
CollectionId, CollectorConfig, Commit, CommitType, CompileBenchmark,
7+
CompletedBenchmarkRequestWithErrors, Date, PartialStatusPageData, Profile, Target,
88
};
99
use crate::{ArtifactIdNumber, Index, QueuedCommit};
1010
use chrono::{DateTime, TimeZone, Utc};
@@ -1382,6 +1382,13 @@ impl Connection for SqliteConnection {
13821382
async fn update_collector_heartbeat(&self, _collector_name: &str) -> anyhow::Result<()> {
13831383
no_queue_implementation_abort!()
13841384
}
1385+
1386+
async fn get_last_n_completed_benchmark_requests(
1387+
&self,
1388+
_count: u64,
1389+
) -> anyhow::Result<Vec<CompletedBenchmarkRequestWithErrors>> {
1390+
no_queue_implementation_abort!()
1391+
}
13851392
}
13861393

13871394
fn parse_artifact_id(ty: &str, sha: &str, date: Option<i64>) -> ArtifactId {

0 commit comments

Comments
 (0)