Skip to content

Commit 4e9a6bb

Browse files
authored
Merge pull request #2250 from Kobzol/status-page
Estimate when requests will end in the new status page
2 parents 70ddca7 + 32326d8 commit 4e9a6bb

File tree

5 files changed

+99
-20
lines changed

5 files changed

+99
-20
lines changed

database/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,10 @@ impl BenchmarkRequest {
10341034
pub fn is_completed(&self) -> bool {
10351035
matches!(self.status, BenchmarkRequestStatus::Completed { .. })
10361036
}
1037+
1038+
pub fn is_in_progress(&self) -> bool {
1039+
matches!(self.status, BenchmarkRequestStatus::InProgress { .. })
1040+
}
10371041
}
10381042

10391043
/// Cached information about benchmark requests in the DB

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type BenchmarkRequest = {
88
requestType: BenchmarkRequestType;
99
createdAt: string;
1010
completedAt: string | null;
11+
endEstimated: boolean;
1112
durationS: number | null;
1213
errors: Dict<string>;
1314
};

site/frontend/src/pages/status_new/page.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,13 @@ loadStatusData(loading);
158158
req.status === "Completed" && req.hasPendingJobs ? "*" : ""
159159
}}
160160
</td>
161-
<td v-html="formatISODate(req.completedAt)"></td>
162-
<td v-html="getDuration(req)"></td>
161+
<td>
162+
{{ formatISODate(req.completedAt) }}
163+
<span v-if="req.endEstimated">(est.)</span>
164+
</td>
165+
<td>
166+
{{ getDuration(req) }}
167+
</td>
163168

164169
<td v-if="hasErrors(req.errors)">
165170
<button @click="toggleExpandedErrors(req.tag)">

site/src/api.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,8 @@ pub mod status_new {
419419
pub request_type: BenchmarkRequestType,
420420
pub created_at: DateTime<Utc>,
421421
pub completed_at: Option<DateTime<Utc>>,
422+
// If true, then `completed_at` is only an estimation of when will the request complete
423+
pub end_estimated: bool,
422424
pub duration_s: Option<u64>,
423425
pub errors: HashMap<String, String>,
424426
}

site/src/request_handlers/status_page_new.rs

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,110 @@
1-
use std::sync::Arc;
2-
31
use crate::api::status_new;
42
use crate::job_queue::build_queue;
53
use crate::load::SiteCtxt;
4+
use chrono::{DateTime, Utc};
65
use database::{
76
BenchmarkJob, BenchmarkJobStatus, BenchmarkRequest, BenchmarkRequestStatus,
87
BenchmarkRequestType, Connection,
98
};
109
use hashbrown::HashMap;
10+
use std::sync::Arc;
11+
use std::time::Duration;
1112

1213
pub async fn handle_status_page_new(ctxt: Arc<SiteCtxt>) -> anyhow::Result<status_new::Response> {
1314
let conn = ctxt.conn().await;
1415

1516
let index = conn.load_benchmark_request_index().await?;
1617

1718
// The queue contains any in-progress request(s) and then the following requests in queue order
18-
// We reverse so that it starts with the request that will be benchmarked the latest
19-
let mut queue: Vec<status_new::BenchmarkRequest> = build_queue(&*conn, &index)
20-
.await?
19+
let queue = build_queue(&*conn, &index).await?;
20+
let completed = conn.get_last_n_completed_benchmark_requests(10).await?;
21+
22+
// Figure out approximately how long was the most recent master benchmark request
23+
let expected_duration = completed
24+
.iter()
25+
.filter(|req| req.request.is_master())
26+
.filter_map(|req| match req.request.status() {
27+
BenchmarkRequestStatus::Completed { duration, .. } => Some(duration),
28+
_ => None,
29+
})
30+
.next()
31+
.unwrap_or(Duration::from_secs(3600));
32+
33+
let in_progress_jobs = conn.get_jobs_of_in_progress_benchmark_requests().await?;
34+
35+
// Here we compute the estimated end time for queued requests, and convert the requests to their
36+
// frontend representation.
37+
// We assume that at most a single request is in progress
38+
39+
let now = Utc::now();
40+
41+
// The estimated start time of the current in-progress request
42+
let current_request_start = if let Some(req) = queue.first().take_if(|req| req.is_in_progress())
43+
{
44+
// Here we need to somehow guess when did the current in-progress request actually start,
45+
// as we do not have that information readily available
46+
let request_jobs = in_progress_jobs
47+
.get(req.tag().expect("In progress request without a tag"))
48+
.map(|jobs| jobs.as_slice())
49+
.unwrap_or(&[]);
50+
51+
// Take the earliest start time, if some job has already started
52+
// If there are no started jobs yet, just fall back to the current time (we guess that a
53+
// job will start "any time now")
54+
request_jobs
55+
.iter()
56+
.filter_map(|job| match job.status() {
57+
BenchmarkJobStatus::Queued => None,
58+
BenchmarkJobStatus::InProgress { started_at, .. }
59+
| BenchmarkJobStatus::Completed { started_at, .. } => Some(*started_at),
60+
})
61+
.min()
62+
.unwrap_or(now)
63+
} else {
64+
// Assume that the next request (if any) will start at any given moment
65+
now
66+
};
67+
68+
// Estimate when the current in-progress request should end
69+
// This ignores the fact that different kinds of requests (e.g. release ones) can have different
70+
// durations, but these are rare and it's not worth the complexity to have multiple estimates
71+
// here.
72+
let current_request_end = current_request_start + expected_duration;
73+
74+
let mut requests: Vec<status_new::BenchmarkRequest> = queue
2175
.into_iter()
22-
.map(|req| request_to_ui(&req, HashMap::new()))
76+
.enumerate()
77+
.map(|(index, req)| {
78+
let estimated_end = if req.is_in_progress() {
79+
current_request_end
80+
} else {
81+
current_request_end + expected_duration * (index as u32)
82+
};
83+
request_to_ui(&req, HashMap::default(), Some(estimated_end))
84+
})
2385
.collect();
24-
queue.reverse();
25-
// And then we add N most recently completed requests to it
26-
let completed = conn.get_last_n_completed_benchmark_requests(10).await?;
27-
queue.extend(
86+
87+
// We reverse the queued requests so that they start with the request that will be benchmarked the latest
88+
requests.reverse();
89+
// And then we add the completed requests
90+
requests.extend(
2891
completed
2992
.into_iter()
30-
.map(|req| request_to_ui(&req.request, req.errors)),
93+
.map(|req| request_to_ui(&req.request, req.errors, None)),
3194
);
3295

33-
let collectors = build_collectors(conn.as_ref()).await?;
96+
let collectors = build_collectors(conn.as_ref(), &in_progress_jobs).await?;
3497

3598
Ok(status_new::Response {
36-
requests: queue,
99+
requests,
37100
collectors,
38101
})
39102
}
40103

41-
async fn build_collectors(conn: &dyn Connection) -> anyhow::Result<Vec<status_new::Collector>> {
42-
let in_progress_jobs = conn.get_jobs_of_in_progress_benchmark_requests().await?;
104+
async fn build_collectors(
105+
conn: &dyn Connection,
106+
in_progress_jobs: &HashMap<String, Vec<BenchmarkJob>>,
107+
) -> anyhow::Result<Vec<status_new::Collector>> {
43108
let collectors = conn.get_collector_configs().await?;
44109
let mut collector_map: HashMap<String, status_new::Collector> = collectors
45110
.into_iter()
@@ -120,11 +185,12 @@ fn job_status_to_priority(status: status_new::BenchmarkJobStatus) -> u32 {
120185
fn request_to_ui(
121186
req: &BenchmarkRequest,
122187
errors: HashMap<String, String>,
188+
estimated_end: Option<DateTime<Utc>>,
123189
) -> status_new::BenchmarkRequest {
124190
let (completed_at, duration_s) = match req.status() {
125-
BenchmarkRequestStatus::WaitingForArtifacts => (None, None),
126-
BenchmarkRequestStatus::ArtifactsReady => (None, None),
127-
BenchmarkRequestStatus::InProgress => (None, None),
191+
BenchmarkRequestStatus::WaitingForArtifacts => (estimated_end, None),
192+
BenchmarkRequestStatus::ArtifactsReady => (estimated_end, None),
193+
BenchmarkRequestStatus::InProgress => (estimated_end, None),
128194
BenchmarkRequestStatus::Completed {
129195
completed_at,
130196
duration,
@@ -150,6 +216,7 @@ fn request_to_ui(
150216
completed_at,
151217
duration_s,
152218
errors,
219+
end_estimated: estimated_end.is_some(),
153220
}
154221
}
155222

0 commit comments

Comments
 (0)