Skip to content

Commit 11431f8

Browse files
committed
Return compilation sections from benchmark detail endpoint
1 parent c320757 commit 11431f8

File tree

3 files changed

+136
-4
lines changed

3 files changed

+136
-4
lines changed

site/src/api.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,27 @@ pub mod detail {
159159
deserializer.deserialize_str(CommaSeparatedVisitor(Default::default()))
160160
}
161161

162-
#[derive(Debug, PartialEq, Clone, Serialize)]
162+
#[derive(Default, Debug, Clone, Serialize)]
163+
pub struct CompilationSection {
164+
pub name: String,
165+
// It is unspecified if this is duration, fraction or something else. It should only be
166+
// evaluated against the total sum of values.
167+
pub value: u64,
168+
}
169+
170+
/// Counts how much <resource> (time/instructions) was spent in individual compilation sections
171+
/// (e.g. frontend, backend, linking) during the compilation of a single test case.
172+
#[derive(Default, Debug, Serialize)]
173+
pub struct CompilationSections {
174+
pub sections: Vec<CompilationSection>,
175+
}
176+
177+
#[derive(Debug, Serialize)]
163178
pub struct Response {
164179
pub commits: Vec<(i64, String)>,
165180
pub graphs: Vec<Series>,
181+
pub sections_before: Option<CompilationSections>,
182+
pub sections_after: Option<CompilationSections>,
166183
}
167184
}
168185

site/src/request_handlers/graph.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
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::CompilationSections;
57
use crate::api::graphs::GraphKind;
68
use crate::api::{detail, 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::download_and_analyze_self_profile;
1114

1215
/// Returns data for a detailed information when comparing a single test result comparison
1316
/// for a compile-time benchmark.
@@ -23,12 +26,13 @@ pub async fn handle_compile_detail(
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
)
@@ -37,6 +41,53 @@ pub async fn handle_compile_detail(
3741
.map(|sr| sr.interpolate().map(|series| series.collect::<Vec<_>>()))
3842
.collect();
3943

44+
async fn calculate_sections(
45+
ctxt: &SiteCtxt,
46+
aid: Option<&ArtifactId>,
47+
benchmark: &str,
48+
profile: &str,
49+
scenario: Scenario,
50+
) -> Option<CompilationSections> {
51+
match aid {
52+
Some(aid) => download_and_analyze_self_profile(
53+
ctxt,
54+
aid.clone(),
55+
benchmark,
56+
profile,
57+
scenario,
58+
None,
59+
)
60+
.await
61+
.ok()
62+
.map(|profile| CompilationSections {
63+
sections: profile.compilation_sections,
64+
}),
65+
None => None,
66+
}
67+
}
68+
69+
// Doc queries are not split into the classic frontend/backend/linker parts.
70+
let (sections_before, sections_after) = if request.profile != "doc" {
71+
tokio::join!(
72+
calculate_sections(
73+
&ctxt,
74+
artifact_ids.get(0),
75+
&request.benchmark,
76+
&request.profile,
77+
scenario,
78+
),
79+
calculate_sections(
80+
&ctxt,
81+
artifact_ids.get(1),
82+
&request.benchmark,
83+
&request.profile,
84+
scenario,
85+
)
86+
)
87+
} else {
88+
(None, None)
89+
};
90+
4091
let mut graphs = Vec::new();
4192

4293
let mut interpolated_responses = interpolated_responses.into_iter();
@@ -51,6 +102,8 @@ pub async fn handle_compile_detail(
51102
Ok(detail::Response {
52103
commits: artifact_ids_to_commits(artifact_ids),
53104
graphs,
105+
sections_before,
106+
sections_after,
54107
})
55108
}
56109

site/src/self_profile.rs

Lines changed: 62 additions & 0 deletions
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::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,6 +199,7 @@ 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

202205
pub(crate) async fn download_and_analyze_self_profile(
@@ -221,15 +224,74 @@ 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+
233295
fn get_self_profile_data(
234296
cpu_clock: Option<f64>,
235297
profile: &analyzeme::AnalysisResults,

0 commit comments

Comments
 (0)