Skip to content

Commit f4d156a

Browse files
committed
Make headers of the HTML --timings unit table dynamic
1 parent 5a266b2 commit f4d156a

File tree

1 file changed

+112
-20
lines changed

1 file changed

+112
-20
lines changed

src/cargo/core/compiler/timings.rs

Lines changed: 112 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::util::{CargoResult, GlobalContext};
1313
use anyhow::Context as _;
1414
use cargo_util::paths;
1515
use indexmap::IndexMap;
16+
use itertools::Itertools;
1617
use std::collections::HashMap;
1718
use std::io::{BufWriter, Write};
1819
use std::thread::available_parallelism;
@@ -90,11 +91,35 @@ struct UnitTime {
9091
unlocked_units: Vec<Unit>,
9192
/// Same as `unlocked_units`, but unlocked by rmeta.
9293
unlocked_rmeta_units: Vec<Unit>,
94+
/// Individual compilation section durations, gathered from `--json=timings`.
9395
/// IndexMap is used to keep original insertion order, we want to be able to tell which
9496
/// sections were started in which order.
9597
sections: IndexMap<String, CompilationSection>,
9698
}
9799

100+
const FRONTEND_SECTION_NAME: &str = "Frontend";
101+
const CODEGEN_SECTION_NAME: &str = "Codegen";
102+
103+
impl UnitTime {
104+
fn aggregate_sections(&self) -> AggregatedSections {
105+
let end = self.duration;
106+
107+
if let Some(rmeta) = self.rmeta_time {
108+
// We only know when the rmeta time was generated
109+
AggregatedSections::OnlyMetadataTime {
110+
frontend: SectionData {
111+
start: 0.0,
112+
end: rmeta,
113+
},
114+
codegen: SectionData { start: rmeta, end },
115+
}
116+
} else {
117+
// We only know the total duration
118+
AggregatedSections::OnlyTotalDuration
119+
}
120+
}
121+
}
122+
98123
/// Periodic concurrency tracking information.
99124
#[derive(serde::Serialize)]
100125
struct Concurrency {
@@ -109,6 +134,33 @@ struct Concurrency {
109134
inactive: usize,
110135
}
111136

137+
/// Postprocessed section data that has both start and an end.
138+
#[derive(Copy, Clone, serde::Serialize)]
139+
struct SectionData {
140+
/// Start (relative to the start of the unit)
141+
start: f64,
142+
/// End (relative to the start of the unit)
143+
end: f64,
144+
}
145+
146+
impl SectionData {
147+
fn duration(&self) -> f64 {
148+
(self.end - self.start).max(0.0)
149+
}
150+
}
151+
152+
/// Contains post-processed data of individual compilation sections.
153+
#[derive(serde::Serialize)]
154+
enum AggregatedSections {
155+
/// We only know when .rmeta was generated, so we can distill frontend and codegen time.
156+
OnlyMetadataTime {
157+
frontend: SectionData,
158+
codegen: SectionData,
159+
},
160+
/// We know only the total duration
161+
OnlyTotalDuration,
162+
}
163+
112164
impl<'gctx> Timings<'gctx> {
113165
pub fn new(bcx: &BuildContext<'_, 'gctx>, root_units: &[Unit]) -> Timings<'gctx> {
114166
let has_report = |what| bcx.build_config.timing_outputs.contains(&what);
@@ -553,6 +605,29 @@ impl<'gctx> Timings<'gctx> {
553605

554606
/// Render the table of all units.
555607
fn write_unit_table(&self, f: &mut impl Write) -> CargoResult<()> {
608+
let mut units: Vec<&UnitTime> = self.unit_times.iter().collect();
609+
units.sort_unstable_by(|a, b| b.duration.partial_cmp(&a.duration).unwrap());
610+
611+
// We can have a bunch of situations here.
612+
// - -Zsection-timings is enabled, and we received some custom sections, in which
613+
// case we use them to determine the headers.
614+
// - We have at least one rmeta time, so we hard-code Frontend and Codegen headers.
615+
// - We only have total durations, so we don't add any additional headers.
616+
let aggregated: Vec<AggregatedSections> =
617+
units.iter().map(|u| u.aggregate_sections()).collect();
618+
619+
let headers: Vec<String> = if aggregated
620+
.iter()
621+
.any(|s| matches!(s, AggregatedSections::OnlyMetadataTime { .. }))
622+
{
623+
vec![
624+
FRONTEND_SECTION_NAME.to_string(),
625+
CODEGEN_SECTION_NAME.to_string(),
626+
]
627+
} else {
628+
vec![]
629+
};
630+
556631
write!(
557632
f,
558633
r#"
@@ -562,20 +637,48 @@ impl<'gctx> Timings<'gctx> {
562637
<th></th>
563638
<th>Unit</th>
564639
<th>Total</th>
565-
<th>Codegen</th>
640+
{headers}
566641
<th>Features</th>
567642
</tr>
568643
</thead>
569644
<tbody>
570-
"#
645+
"#,
646+
headers = headers.iter().map(|h| format!("<th>{h}</th>")).join("\n")
571647
)?;
572-
let mut units: Vec<&UnitTime> = self.unit_times.iter().collect();
573-
units.sort_unstable_by(|a, b| b.duration.partial_cmp(&a.duration).unwrap());
574-
for (i, unit) in units.iter().enumerate() {
575-
let codegen = match unit.codegen_time() {
648+
649+
for (i, (unit, aggregated_sections)) in units.iter().zip(aggregated).enumerate() {
650+
let format_duration = |section: Option<SectionData>| match section {
651+
Some(section) => {
652+
let duration = section.duration();
653+
let pct = (duration / unit.duration) * 100.0;
654+
format!("{duration:.1}s ({:.0}%)", pct)
655+
}
576656
None => "".to_string(),
577-
Some((_rt, ctime, cent)) => format!("{:.1}s ({:.0}%)", ctime, cent),
578657
};
658+
659+
// This is a bit complex, as we assume the most general option - we can have an
660+
// arbitrary set of headers, and an arbitrary set of sections per unit, so we always
661+
// initiate the cells to be empty, and then try to find a corresponding column for which
662+
// we might have data.
663+
let mut cells: HashMap<&str, SectionData> = Default::default();
664+
665+
match &aggregated_sections {
666+
AggregatedSections::OnlyMetadataTime { frontend, codegen } => {
667+
cells.insert(FRONTEND_SECTION_NAME, *frontend);
668+
cells.insert(CODEGEN_SECTION_NAME, *codegen);
669+
}
670+
AggregatedSections::OnlyTotalDuration => {}
671+
};
672+
let cells = headers
673+
.iter()
674+
.map(|header| {
675+
format!(
676+
"<td>{}</td>",
677+
format_duration(cells.remove(header.as_str()))
678+
)
679+
})
680+
.join("\n");
681+
579682
let features = unit.unit.features.join(", ");
580683
write!(
581684
f,
@@ -584,16 +687,14 @@ impl<'gctx> Timings<'gctx> {
584687
<td>{}.</td>
585688
<td>{}{}</td>
586689
<td>{:.1}s</td>
587-
<td>{}</td>
588-
<td>{}</td>
690+
{cells}
691+
<td>{features}</td>
589692
</tr>
590693
"#,
591694
i + 1,
592695
unit.name_ver(),
593696
unit.target,
594697
unit.duration,
595-
codegen,
596-
features,
597698
)?;
598699
}
599700
write!(f, "</tbody>\n</table>\n")?;
@@ -602,15 +703,6 @@ impl<'gctx> Timings<'gctx> {
602703
}
603704

604705
impl UnitTime {
605-
/// Returns the codegen time as (`rmeta_time`, `codegen_time`, percent of total)
606-
fn codegen_time(&self) -> Option<(f64, f64, f64)> {
607-
self.rmeta_time.map(|rmeta_time| {
608-
let ctime = self.duration - rmeta_time;
609-
let cent = (ctime / self.duration) * 100.0;
610-
(rmeta_time, ctime, cent)
611-
})
612-
}
613-
614706
fn name_ver(&self) -> String {
615707
format!("{} v{}", self.unit.pkg.name(), self.unit.pkg.version())
616708
}

0 commit comments

Comments
 (0)