Skip to content

Commit 31520c1

Browse files
committed
Create a new endpoint for compilation sections
A separate endpoint was created, because loading the sections requires loading self-profile data, which can be quite slow. Making it a separate endpoint won't block loading of the graphs, which are loaded quite fast.
1 parent 806403e commit 31520c1

File tree

7 files changed

+217
-51
lines changed

7 files changed

+217
-51
lines changed

site/frontend/src/urls.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export const STATUS_DATA_URL = `${BASE_URL}/status_page`;
77
export const BOOTSTRAP_DATA_URL = `${BASE_URL}/bootstrap`;
88
export const GRAPH_DATA_URL = `${BASE_URL}/graphs`;
99
export const COMPARE_DATA_URL = `${BASE_URL}/get`;
10-
export const COMPARE_COMPILE_DETAIL_DATA_URL = `${BASE_URL}/compare-compile-detail`;
10+
export const COMPARE_COMPILE_DETAIL_DATA_URL = `${BASE_URL}/compare-compile-detail-graphs`;
1111
export const SELF_PROFILE_DATA_URL = `${BASE_URL}/self-profile`;

site/src/api.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub mod graphs {
110110
}
111111
}
112112

113-
pub mod detail {
113+
pub mod detail_graphs {
114114
use crate::api::graphs::{GraphKind, Series};
115115
use collector::Bound;
116116
use serde::de::{DeserializeOwned, Error};
@@ -159,13 +159,48 @@ pub mod detail {
159159
deserializer.deserialize_str(CommaSeparatedVisitor(Default::default()))
160160
}
161161

162-
#[derive(Debug, PartialEq, Clone, Serialize)]
162+
#[derive(Debug, Serialize)]
163163
pub struct Response {
164164
pub commits: Vec<(i64, String)>,
165165
pub graphs: Vec<Series>,
166166
}
167167
}
168168

169+
pub mod detail_sections {
170+
use collector::Bound;
171+
use serde::{Deserialize, Serialize};
172+
173+
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
174+
pub struct Request {
175+
pub start: Bound,
176+
pub end: Bound,
177+
pub benchmark: String,
178+
pub scenario: String,
179+
pub profile: String,
180+
}
181+
182+
#[derive(Default, Debug, Clone, Serialize)]
183+
pub struct CompilationSection {
184+
pub name: String,
185+
// It is unspecified if this is duration, fraction or something else. It should only be
186+
// evaluated against the total sum of values.
187+
pub value: u64,
188+
}
189+
190+
/// Counts how much <resource> (time/instructions) was spent in individual compilation sections
191+
/// (e.g. frontend, backend, linking) during the compilation of a single test case.
192+
#[derive(Default, Debug, Serialize)]
193+
pub struct CompilationSections {
194+
pub sections: Vec<CompilationSection>,
195+
}
196+
197+
#[derive(Debug, Serialize)]
198+
pub struct Response {
199+
pub before: Option<CompilationSections>,
200+
pub after: Option<CompilationSections>,
201+
}
202+
}
203+
169204
pub mod bootstrap {
170205
use collector::Bound;
171206
use hashbrown::HashMap;

site/src/request_handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod status_page;
99
pub use bootstrap::handle_bootstrap;
1010
pub use dashboard::handle_dashboard;
1111
pub use github::handle_github;
12-
pub use graph::{handle_compile_detail, handle_graphs};
12+
pub use graph::{handle_compile_detail_graphs, handle_compile_detail_sections, handle_graphs};
1313
pub use next_artifact::handle_next_artifact;
1414
pub use self_profile::{
1515
handle_self_profile, handle_self_profile_processed_download, handle_self_profile_raw,

site/src/request_handlers/graph.rs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
1-
use collector::Bound;
21
use std::collections::HashMap;
32
use std::sync::Arc;
43

4+
use collector::Bound;
5+
6+
use crate::api::detail_sections::CompilationSections;
57
use crate::api::graphs::GraphKind;
6-
use crate::api::{detail, graphs, ServerResult};
8+
use crate::api::{detail_graphs, detail_sections, graphs, ServerResult};
79
use crate::db::{self, ArtifactId, Profile, Scenario};
810
use crate::interpolate::IsInterpolated;
911
use crate::load::SiteCtxt;
1012
use crate::selector::{CompileBenchmarkQuery, CompileTestCase, Selector, SeriesResponse};
13+
use crate::self_profile::get_or_download_self_profile;
1114

12-
/// Returns data for a detailed information when comparing a single test result comparison
15+
/// Returns data for before/after graphs when comparing a single test result comparison
1316
/// for a compile-time benchmark.
14-
pub async fn handle_compile_detail(
15-
request: detail::Request,
17+
pub async fn handle_compile_detail_graphs(
18+
request: detail_graphs::Request,
1619
ctxt: Arc<SiteCtxt>,
17-
) -> ServerResult<detail::Response> {
18-
log::info!("handle_compile_detail({:?})", request);
20+
) -> ServerResult<detail_graphs::Response> {
21+
log::info!("handle_compile_detail_graphs({:?})", request);
1922

2023
let artifact_ids = Arc::new(master_artifact_ids_for_range(
2124
&ctxt,
2225
request.start,
2326
request.end,
2427
));
2528

29+
let scenario = request.scenario.parse()?;
2630
let interpolated_responses: Vec<_> = ctxt
2731
.statistic_series(
2832
CompileBenchmarkQuery::default()
29-
.benchmark(Selector::One(request.benchmark))
33+
.benchmark(Selector::One(request.benchmark.clone()))
3034
.profile(Selector::One(request.profile.parse()?))
31-
.scenario(Selector::One(request.scenario.parse()?))
35+
.scenario(Selector::One(scenario))
3236
.metric(Selector::One(request.stat.parse()?)),
3337
artifact_ids.clone(),
3438
)
@@ -48,12 +52,73 @@ pub async fn handle_compile_detail(
4852
}
4953
assert!(interpolated_responses.next().is_none());
5054

51-
Ok(detail::Response {
55+
Ok(detail_graphs::Response {
5256
commits: artifact_ids_to_commits(artifact_ids),
5357
graphs,
5458
})
5559
}
5660

61+
/// Returns data for compilation sections (frontend/backend/linker) when comparing a single test
62+
/// result comparison for a compile-time benchmark.
63+
pub async fn handle_compile_detail_sections(
64+
request: detail_sections::Request,
65+
ctxt: Arc<SiteCtxt>,
66+
) -> ServerResult<detail_sections::Response> {
67+
log::info!("handle_compile_detail_sections({:?})", request);
68+
69+
let artifact_ids = Arc::new(master_artifact_ids_for_range(
70+
&ctxt,
71+
request.start,
72+
request.end,
73+
));
74+
75+
let scenario = request.scenario.parse()?;
76+
77+
async fn calculate_sections(
78+
ctxt: &SiteCtxt,
79+
aid: Option<&ArtifactId>,
80+
benchmark: &str,
81+
profile: &str,
82+
scenario: Scenario,
83+
) -> Option<CompilationSections> {
84+
match aid {
85+
Some(aid) => {
86+
get_or_download_self_profile(ctxt, aid.clone(), benchmark, profile, scenario, None)
87+
.await
88+
.ok()
89+
.map(|profile| CompilationSections {
90+
sections: profile.compilation_sections,
91+
})
92+
}
93+
None => None,
94+
}
95+
}
96+
97+
// Doc queries are not split into the classic frontend/backend/linker parts.
98+
let (before, after) = if request.profile != "doc" {
99+
tokio::join!(
100+
calculate_sections(
101+
&ctxt,
102+
artifact_ids.get(0),
103+
&request.benchmark,
104+
&request.profile,
105+
scenario,
106+
),
107+
calculate_sections(
108+
&ctxt,
109+
artifact_ids.get(1),
110+
&request.benchmark,
111+
&request.profile,
112+
scenario,
113+
)
114+
)
115+
} else {
116+
(None, None)
117+
};
118+
119+
Ok(detail_sections::Response { before, after })
120+
}
121+
57122
pub async fn handle_graphs(
58123
request: graphs::Request,
59124
ctxt: Arc<SiteCtxt>,

site/src/request_handlers/self_profile.rs

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ use crate::comparison::Metric;
1414
use crate::db::ArtifactId;
1515
use crate::load::SiteCtxt;
1616
use crate::selector::{self};
17-
use crate::self_profile::{
18-
download_and_analyze_self_profile, get_self_profile_raw_data, SelfProfileKey,
19-
SelfProfileWithAnalysis,
20-
};
17+
use crate::self_profile::{get_or_download_self_profile, get_self_profile_raw_data};
2118
use crate::server::{Response, ResponseHeaders};
2219

2320
pub async fn handle_self_profile_processed_download(
@@ -535,35 +532,7 @@ pub async fn handle_self_profile(
535532
assert_eq!(cpu_responses.len(), 1, "all selectors are exact");
536533
let mut cpu_response = cpu_responses.remove(0).series;
537534

538-
async fn get_from_cache(
539-
ctxt: &SiteCtxt,
540-
aid: ArtifactId,
541-
benchmark: &str,
542-
profile: &str,
543-
scenario: database::Scenario,
544-
metric: Option<f64>,
545-
) -> ServerResult<SelfProfileWithAnalysis> {
546-
let key = SelfProfileKey {
547-
aid: aid.clone(),
548-
benchmark: benchmark.to_string(),
549-
profile: profile.to_string(),
550-
scenario,
551-
};
552-
let cache_result = ctxt.self_profile_cache.lock().get(&key);
553-
match cache_result {
554-
Some(res) => Ok(res),
555-
None => {
556-
let profile = download_and_analyze_self_profile(
557-
ctxt, aid, benchmark, profile, scenario, metric,
558-
)
559-
.await?;
560-
ctxt.self_profile_cache.lock().insert(key, profile.clone());
561-
Ok(profile)
562-
}
563-
}
564-
}
565-
566-
let mut self_profile = get_from_cache(
535+
let mut self_profile = get_or_download_self_profile(
567536
ctxt,
568537
commits.get(0).unwrap().clone(),
569538
bench_name,
@@ -574,7 +543,7 @@ pub async fn handle_self_profile(
574543
.await?;
575544
let base_self_profile = match commits.get(1) {
576545
Some(aid) => Some(
577-
get_from_cache(
546+
get_or_download_self_profile(
578547
ctxt,
579548
aid.clone(),
580549
bench_name,

site/src/self_profile.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! This module handles self-profile "rich" APIs (e.g., chrome profiler JSON)
22
//! generation from the raw artifacts on demand.
33
4+
use crate::api::detail_sections::CompilationSection;
45
use crate::api::self_profile::ArtifactSize;
56
use crate::api::{self_profile, ServerResult};
67
use crate::load::SiteCtxt;
8+
use analyzeme::ProfilingData;
79
use anyhow::Context;
810
use bytes::Buf;
911
use database::ArtifactId;
@@ -197,9 +199,10 @@ impl SelfProfileCache {
197199
pub struct SelfProfileWithAnalysis {
198200
pub profile: self_profile::SelfProfile,
199201
pub profiling_data: analyzeme::AnalysisResults,
202+
pub compilation_sections: Vec<CompilationSection>,
200203
}
201204

202-
pub(crate) async fn download_and_analyze_self_profile(
205+
async fn download_and_analyze_self_profile(
203206
ctxt: &SiteCtxt,
204207
aid: ArtifactId,
205208
benchmark: &str,
@@ -221,15 +224,101 @@ pub(crate) async fn download_and_analyze_self_profile(
221224
.map_err(|e| format!("error extracting self profiling data: {}", e))?,
222225
Err(e) => return Err(format!("could not fetch raw profile data: {e:?}")),
223226
};
227+
228+
let compilation_sections = compute_compilation_sections(&profiling_data);
224229
let profiling_data = profiling_data.perform_analysis();
225230
let profile =
226231
get_self_profile_data(metric, &profiling_data).map_err(|e| format!("{}: {}", aid, e))?;
227232
Ok(SelfProfileWithAnalysis {
228233
profile,
229234
profiling_data,
235+
compilation_sections,
230236
})
231237
}
232238

239+
/// Tries to categorize the duration of three high-level sections of compilation (frontend,
240+
/// backend, linker) from the self-profile queries.
241+
fn compute_compilation_sections(profile: &ProfilingData) -> Vec<CompilationSection> {
242+
let mut first_event_start = None;
243+
let mut frontend_end = None;
244+
let mut backend_start = None;
245+
let mut backend_end = None;
246+
let mut linker_duration = None;
247+
248+
for event in profile.iter_full() {
249+
if first_event_start.is_none() {
250+
first_event_start = event.payload.timestamp().map(|t| t.start());
251+
}
252+
253+
if event.label == "analysis" {
254+
// End of "analysis" => end of frontend
255+
frontend_end = event.payload.timestamp().map(|t| t.end());
256+
} else if event.label == "codegen_crate" {
257+
// Start of "codegen_crate" => start of backend
258+
backend_start = event.payload.timestamp().map(|t| t.start());
259+
} else if event.label == "finish_ongoing_codegen" {
260+
// End of "finish_ongoing_codegen" => end of backend
261+
backend_end = event.payload.timestamp().map(|t| t.end());
262+
} else if event.label == "link_crate" {
263+
// The "link" query overlaps codegen, so we want to look at the "link_crate" query
264+
// instead.
265+
linker_duration = event.duration();
266+
}
267+
}
268+
let mut sections = vec![];
269+
if let (Some(start), Some(end)) = (first_event_start, frontend_end) {
270+
if let Ok(duration) = end.duration_since(start) {
271+
sections.push(CompilationSection {
272+
name: "Frontend".to_string(),
273+
value: duration.as_nanos() as u64,
274+
});
275+
}
276+
}
277+
if let (Some(start), Some(end)) = (backend_start, backend_end) {
278+
if let Ok(duration) = end.duration_since(start) {
279+
sections.push(CompilationSection {
280+
name: "Backend".to_string(),
281+
value: duration.as_nanos() as u64,
282+
});
283+
}
284+
}
285+
if let Some(duration) = linker_duration {
286+
sections.push(CompilationSection {
287+
name: "Linker".to_string(),
288+
value: duration.as_nanos() as u64,
289+
});
290+
}
291+
292+
sections
293+
}
294+
295+
pub(crate) async fn get_or_download_self_profile(
296+
ctxt: &SiteCtxt,
297+
aid: ArtifactId,
298+
benchmark: &str,
299+
profile: &str,
300+
scenario: database::Scenario,
301+
metric: Option<f64>,
302+
) -> ServerResult<SelfProfileWithAnalysis> {
303+
let key = SelfProfileKey {
304+
aid: aid.clone(),
305+
benchmark: benchmark.to_string(),
306+
profile: profile.to_string(),
307+
scenario,
308+
};
309+
let cache_result = ctxt.self_profile_cache.lock().get(&key);
310+
match cache_result {
311+
Some(res) => Ok(res),
312+
None => {
313+
let profile =
314+
download_and_analyze_self_profile(ctxt, aid, benchmark, profile, scenario, metric)
315+
.await?;
316+
ctxt.self_profile_cache.lock().insert(key, profile.clone());
317+
Ok(profile)
318+
}
319+
}
320+
}
321+
233322
fn get_self_profile_data(
234323
cpu_clock: Option<f64>,
235324
profile: &analyzeme::AnalysisResults,

0 commit comments

Comments
 (0)