Skip to content

Commit fc92f51

Browse files
authored
Merge pull request #2394 from Kobzol/collector-info
Show commit SHA and average request duration per collector
2 parents 1057fbf + ad55bf5 commit fc92f51

File tree

7 files changed

+185
-45
lines changed

7 files changed

+185
-45
lines changed

database/src/pool.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ pub trait Connection: Send + Sync {
277277
&self,
278278
) -> anyhow::Result<HashMap<String, Vec<BenchmarkJob>>>;
279279

280+
/// Return all jobs associated with the given benchmark request tags.
281+
async fn get_jobs_of_benchmark_requests(
282+
&self,
283+
tags: &[String],
284+
) -> anyhow::Result<Vec<BenchmarkJob>>;
285+
280286
/// Get all of the configuration for all of the collectors
281287
async fn get_collector_configs(&self) -> anyhow::Result<Vec<CollectorConfig>>;
282288

database/src/pool/postgres.rs

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ pub struct CachedStatements {
526526
get_last_n_completed_requests_with_errors: Statement,
527527
get_jobs_of_in_progress_benchmark_requests: Statement,
528528
load_pending_benchmark_requests: Statement,
529+
get_jobs_of_benchmark_requests: Statement,
529530
}
530531

531532
pub struct PostgresTransaction<'a> {
@@ -763,6 +764,11 @@ impl PostgresConnection {
763764
FROM pending
764765
LEFT JOIN benchmark_request as parent ON parent.tag = pending.parent_sha
765766
")).await.unwrap(),
767+
get_jobs_of_benchmark_requests: conn.prepare("
768+
SELECT *
769+
FROM job_queue
770+
WHERE request_tag = ANY($1)
771+
").await.unwrap(),
766772
}),
767773
conn,
768774
}
@@ -773,6 +779,44 @@ impl PostgresConnection {
773779
const BENCHMARK_REQUEST_COLUMNS: &str =
774780
"tag, parent_sha, pr, commit_type, status, created_at, completed_at, backends, profiles, commit_date, duration_ms, targets";
775781

782+
/// Parse a benchmark job out of a row.
783+
/// Expects to be used with `SELECT * FROM job_queue`.
784+
fn parse_benchmark_job_from_row(row: &Row) -> anyhow::Result<BenchmarkJob> {
785+
let started_at = row.get::<_, Option<DateTime<Utc>>>(7);
786+
let status = row.get::<_, &str>(9);
787+
let collector_name = row.get::<_, Option<String>>(11);
788+
let status = match status {
789+
BENCHMARK_JOB_STATUS_QUEUED_STR => BenchmarkJobStatus::Queued,
790+
BENCHMARK_JOB_STATUS_IN_PROGRESS_STR => BenchmarkJobStatus::InProgress {
791+
started_at: started_at.expect("started_at was null for an in progress job"),
792+
collector_name: collector_name.expect("Collector is missing for an in progress job"),
793+
},
794+
BENCHMARK_JOB_STATUS_FAILURE_STR | BENCHMARK_JOB_STATUS_SUCCESS_STR => {
795+
BenchmarkJobStatus::Completed {
796+
started_at: started_at.expect("started_at was null for a finished job"),
797+
completed_at: row.get::<_, DateTime<Utc>>(8),
798+
collector_name: collector_name
799+
.expect("Collector is missing for an in progress job"),
800+
success: status == BENCHMARK_JOB_STATUS_SUCCESS_STR,
801+
}
802+
}
803+
_ => panic!("Invalid job status {status}"),
804+
};
805+
Ok(BenchmarkJob {
806+
id: row.get::<_, i32>(0) as u32,
807+
request_tag: row.get::<_, String>(1),
808+
target: Target::from_str(row.get::<_, &str>(2)).map_err(|e| anyhow::anyhow!(e))?,
809+
backend: CodegenBackend::from_str(row.get::<_, &str>(3)).map_err(|e| anyhow::anyhow!(e))?,
810+
profile: Profile::from_str(row.get::<_, &str>(4)).map_err(|e| anyhow::anyhow!(e))?,
811+
benchmark_set: BenchmarkSet(row.get::<_, i32>(5) as u32),
812+
created_at: row.get::<_, DateTime<Utc>>(6),
813+
status,
814+
deque_counter: row.get::<_, i32>(10) as u32,
815+
kind: BenchmarkJobKind::from_str(row.get::<_, &str>(12)).map_err(|e| anyhow::anyhow!(e))?,
816+
is_optional: row.get::<_, bool>(13),
817+
})
818+
}
819+
776820
#[async_trait::async_trait]
777821
impl<P> Connection for P
778822
where
@@ -2000,43 +2044,7 @@ where
20002044

20012045
let mut request_to_jobs: HashMap<String, Vec<BenchmarkJob>> = HashMap::new();
20022046
for row in rows {
2003-
let started_at = row.get::<_, Option<DateTime<Utc>>>(7);
2004-
let status = row.get::<_, &str>(9);
2005-
let collector_name = row.get::<_, Option<String>>(11);
2006-
let status = match status {
2007-
BENCHMARK_JOB_STATUS_QUEUED_STR => BenchmarkJobStatus::Queued,
2008-
BENCHMARK_JOB_STATUS_IN_PROGRESS_STR => BenchmarkJobStatus::InProgress {
2009-
started_at: started_at.expect("started_at was null for an in progress job"),
2010-
collector_name: collector_name
2011-
.expect("Collector is missing for an in progress job"),
2012-
},
2013-
BENCHMARK_JOB_STATUS_FAILURE_STR | BENCHMARK_JOB_STATUS_SUCCESS_STR => {
2014-
BenchmarkJobStatus::Completed {
2015-
started_at: started_at.expect("started_at was null for a finished job"),
2016-
completed_at: row.get::<_, DateTime<Utc>>(8),
2017-
collector_name: collector_name
2018-
.expect("Collector is missing for an in progress job"),
2019-
success: status == BENCHMARK_JOB_STATUS_SUCCESS_STR,
2020-
}
2021-
}
2022-
_ => panic!("Invalid job status {status}"),
2023-
};
2024-
let job = BenchmarkJob {
2025-
id: row.get::<_, i32>(0) as u32,
2026-
request_tag: row.get::<_, String>(1),
2027-
target: Target::from_str(row.get::<_, &str>(2)).map_err(|e| anyhow::anyhow!(e))?,
2028-
backend: CodegenBackend::from_str(row.get::<_, &str>(3))
2029-
.map_err(|e| anyhow::anyhow!(e))?,
2030-
profile: Profile::from_str(row.get::<_, &str>(4))
2031-
.map_err(|e| anyhow::anyhow!(e))?,
2032-
benchmark_set: BenchmarkSet(row.get::<_, i32>(5) as u32),
2033-
created_at: row.get::<_, DateTime<Utc>>(6),
2034-
status,
2035-
deque_counter: row.get::<_, i32>(10) as u32,
2036-
kind: BenchmarkJobKind::from_str(row.get::<_, &str>(12))
2037-
.map_err(|e| anyhow::anyhow!(e))?,
2038-
is_optional: row.get::<_, bool>(13),
2039-
};
2047+
let job = parse_benchmark_job_from_row(&row)?;
20402048
request_to_jobs
20412049
.entry(job.request_tag.clone())
20422050
.or_default()
@@ -2045,6 +2053,17 @@ where
20452053
Ok(request_to_jobs)
20462054
}
20472055

2056+
async fn get_jobs_of_benchmark_requests(
2057+
&self,
2058+
tags: &[String],
2059+
) -> anyhow::Result<Vec<BenchmarkJob>> {
2060+
let rows = self
2061+
.conn()
2062+
.query(&self.statements().get_jobs_of_benchmark_requests, &[&tags])
2063+
.await?;
2064+
rows.iter().map(parse_benchmark_job_from_row).collect()
2065+
}
2066+
20482067
async fn get_collector_configs(&self) -> anyhow::Result<Vec<CollectorConfig>> {
20492068
let rows = self
20502069
.conn()

database/src/pool/sqlite.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,13 @@ impl Connection for SqliteConnection {
11931193
no_queue_implementation_abort!()
11941194
}
11951195

1196+
async fn get_jobs_of_benchmark_requests(
1197+
&self,
1198+
_tags: &[String],
1199+
) -> anyhow::Result<Vec<BenchmarkJob>> {
1200+
no_queue_implementation_abort!()
1201+
}
1202+
11961203
async fn get_collector_configs(&self) -> anyhow::Result<Vec<CollectorConfig>> {
11971204
no_queue_implementation_abort!()
11981205
}

site/frontend/src/pages/status/collector.vue

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ function jobDuration(job: BenchmarkJob): string {
146146
const diff = differenceInSeconds(end, start);
147147
return `Job took ${formatSecondsAsDuration(diff)}`;
148148
}
149+
150+
function averageCollectorDuration(collector: CollectorConfig): string {
151+
if (collector.pastRequestDurations.length === 0) {
152+
return "Unknown";
153+
}
154+
const durationSum = collector.pastRequestDurations.reduce(
155+
(acc, req) => acc + req.job_duration_s,
156+
0
157+
);
158+
const averageDuration = durationSum / collector.pastRequestDurations.length;
159+
return formatSecondsAsDuration(averageDuration);
160+
}
149161
</script>
150162

151163
<template>
@@ -180,11 +192,28 @@ function jobDuration(job: BenchmarkJob): string {
180192
<span>{{ formatISODate(collector.lastHeartbeatAt) }}</span>
181193
</div>
182194

183-
<div class="collector-meta">
195+
<div
196+
class="collector-meta"
197+
title="The rustc-perf commit that the collector is currently built from."
198+
>
199+
<span class="collector-meta-name">
200+
<strong>Commit SHA:</strong>
201+
</span>
202+
<span
203+
><a
204+
:href="`https://github.com/rust-lang/rustc-perf/commit/${collector.commitSha}`"
205+
>{{ collector.commitSha }}</a
206+
></span
207+
>
208+
</div>
209+
<div
210+
class="collector-meta"
211+
title="Average duration to compute all jobs for the past few benchmark requests."
212+
>
184213
<span class="collector-meta-name">
185-
<strong>Date Added:</strong>
214+
<strong>Average duration:</strong>
186215
</span>
187-
<span>{{ formatISODate(collector.dateAdded) }}</span>
216+
<span>{{ averageCollectorDuration(collector) }}</span>
188217
</div>
189218
<button @click="toggleShowJobs" class="show-jobs">
190219
<template v-if="showJobs">Hide jobs</template>
@@ -265,7 +294,7 @@ $sm-radius: 8px;
265294
padding: 16px;
266295
display: flex;
267296
box-shadow: 0 1px 2px #0006;
268-
margin: 0px 8px 8px 0px;
297+
margin: 0 8px 8px 0;
269298
}
270299
.collector-name {
271300
font-size: 1.5em;
@@ -282,7 +311,7 @@ $sm-radius: 8px;
282311
283312
.collector-meta-name {
284313
display: block;
285-
min-width: 125px;
314+
min-width: 140px;
286315
}
287316
288317
.collector-left-divider {
@@ -294,7 +323,7 @@ $sm-radius: 8px;
294323
}
295324
296325
.collector-sm-padding-left-right {
297-
padding: 0px $sm-padding;
326+
padding: 0 $sm-padding;
298327
}
299328
.collector-sm-padding-left {
300329
padding-left: $sm-padding;

site/frontend/src/pages/status/data.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,21 @@ export type BenchmarkJob = {
3030
dequeCounter: number;
3131
};
3232

33+
export type PastRequestDuration = {
34+
requestTag: String;
35+
job_duration_s: number;
36+
};
37+
3338
export type CollectorConfig = {
3439
name: string;
3540
target: string;
3641
benchmarkSet: number;
3742
isActive: boolean;
3843
lastHeartbeatAt: string;
3944
dateAdded: string;
45+
commitSha: string;
4046
jobs: BenchmarkJob[];
47+
pastRequestDurations: PastRequestDuration[];
4148
};
4249

4350
export type StatusResponse = {

site/src/api.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ pub mod status {
414414
pub deque_counter: u32,
415415
}
416416

417+
#[derive(Serialize, Debug)]
418+
#[serde(rename_all = "camelCase")]
419+
pub struct PastRequestDuration {
420+
pub request_tag: String,
421+
/// Sum of (completed_at - started_at) for all jobs this collector ran for the request, in seconds.
422+
pub job_duration_s: u64,
423+
}
424+
417425
#[derive(Serialize, Debug)]
418426
#[serde(rename_all = "camelCase")]
419427
pub struct Collector {
@@ -423,7 +431,9 @@ pub mod status {
423431
pub is_active: bool,
424432
pub last_heartbeat_at: DateTime<Utc>,
425433
pub date_added: DateTime<Utc>,
434+
pub commit_sha: String,
426435
pub jobs: Vec<BenchmarkJob>,
436+
pub past_request_durations: Vec<PastRequestDuration>,
427437
}
428438

429439
#[derive(Serialize, Debug)]

site/src/request_handlers/status_page.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::api::status;
2+
use crate::api::status::PastRequestDuration;
23
use crate::job_queue::build_queue;
34
use crate::load::SiteCtxt;
45
use chrono::{DateTime, Utc};
@@ -7,6 +8,7 @@ use database::{
78
BenchmarkRequestType, Connection,
89
};
910
use hashbrown::HashMap;
11+
use std::cmp::Reverse;
1012
use std::sync::Arc;
1113
use std::time::Duration;
1214

@@ -17,6 +19,12 @@ pub async fn handle_status_page(ctxt: Arc<SiteCtxt>) -> anyhow::Result<status::R
1719
let queue = build_queue(&*conn).await?;
1820
let completed = conn.get_last_n_completed_benchmark_requests(10).await?;
1921

22+
let completed_tags = completed
23+
.iter()
24+
.filter_map(|req| req.request.tag())
25+
.map(|t| t.to_owned())
26+
.collect::<Vec<String>>();
27+
2028
// Figure out approximately how long was the most recent master benchmark request
2129
let expected_duration = completed
2230
.iter()
@@ -28,7 +36,11 @@ pub async fn handle_status_page(ctxt: Arc<SiteCtxt>) -> anyhow::Result<status::R
2836
.next()
2937
.unwrap_or(Duration::from_secs(3600));
3038

31-
let in_progress_jobs = conn.get_jobs_of_in_progress_benchmark_requests().await?;
39+
let (in_progress_jobs, past_jobs) = tokio::join!(
40+
conn.get_jobs_of_in_progress_benchmark_requests(),
41+
conn.get_jobs_of_benchmark_requests(&completed_tags)
42+
);
43+
let (in_progress_jobs, past_jobs) = (in_progress_jobs?, past_jobs?);
3244

3345
// Here we compute the estimated end time for queued requests, and convert the requests to their
3446
// frontend representation.
@@ -91,7 +103,7 @@ pub async fn handle_status_page(ctxt: Arc<SiteCtxt>) -> anyhow::Result<status::R
91103
.map(|req| request_to_ui(&req.request, req.errors, None)),
92104
);
93105

94-
let collectors = build_collectors(conn.as_ref(), &in_progress_jobs).await?;
106+
let collectors = build_collectors(conn.as_ref(), &in_progress_jobs, &past_jobs).await?;
95107

96108
Ok(status::Response {
97109
requests,
@@ -102,11 +114,56 @@ pub async fn handle_status_page(ctxt: Arc<SiteCtxt>) -> anyhow::Result<status::R
102114
async fn build_collectors(
103115
conn: &dyn Connection,
104116
in_progress_jobs: &HashMap<String, Vec<BenchmarkJob>>,
117+
past_jobs: &[BenchmarkJob],
105118
) -> anyhow::Result<Vec<status::Collector>> {
119+
// Collector -> request tag -> sum of job durations
120+
let mut collector_request_durations: HashMap<&str, HashMap<&str, Duration>> = HashMap::new();
121+
let mut request_start_time: HashMap<&str, DateTime<Utc>> = HashMap::new();
122+
123+
for job in past_jobs {
124+
let BenchmarkJobStatus::Completed {
125+
started_at,
126+
completed_at,
127+
collector_name,
128+
..
129+
} = job.status()
130+
else {
131+
continue;
132+
};
133+
let start_time = request_start_time.entry(job.request_tag()).or_default();
134+
*start_time = *(&*start_time).min(started_at);
135+
136+
// Accumulate per-collector and per-request job duration
137+
let job_duration = (*completed_at - *started_at).to_std().unwrap_or_default();
138+
*collector_request_durations
139+
.entry(collector_name.as_str())
140+
.or_default()
141+
.entry(job.request_tag())
142+
.or_default() += job_duration;
143+
}
144+
106145
let collectors = conn.get_collector_configs().await?;
107146
let mut collector_map: HashMap<String, status::Collector> = collectors
108147
.into_iter()
109148
.map(|c| {
149+
let mut past_request_durations = collector_request_durations
150+
.remove(c.name())
151+
.unwrap_or_default()
152+
.into_iter()
153+
.map(|(request, collector_duration)| PastRequestDuration {
154+
request_tag: request.to_owned(),
155+
job_duration_s: collector_duration.as_secs(),
156+
})
157+
.collect::<Vec<_>>();
158+
// Sort from the most recent benchmark request
159+
past_request_durations.sort_by_key(|request| {
160+
Reverse(
161+
request_start_time
162+
.get(request.request_tag.as_str())
163+
.copied()
164+
.unwrap_or_default(),
165+
)
166+
});
110167
(
111168
c.name().to_string(),
112169
status::Collector {
@@ -116,7 +173,12 @@ async fn build_collectors(
116173
is_active: c.is_active(),
117174
last_heartbeat_at: c.last_heartbeat_at(),
118175
date_added: c.date_added(),
176+
commit_sha: c
177+
.commit_sha()
178+
.map(|s| s.to_owned())
179+
.unwrap_or_else(|| "<unknown>".to_owned()),
119180
jobs: vec![],
181+
past_request_durations,
120182
},
121183
)
122184
})

0 commit comments

Comments
 (0)