Skip to content

Commit 33acc34

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

File tree

1 file changed

+113
-20
lines changed

1 file changed

+113
-20
lines changed

src/cargo/core/compiler/timings.rs

Lines changed: 113 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);
@@ -480,6 +532,7 @@ impl<'gctx> Timings<'gctx> {
480532
.enumerate()
481533
.map(|(i, ut)| (ut.unit.clone(), i))
482534
.collect();
535+
483536
#[derive(serde::Serialize)]
484537
struct UnitData {
485538
i: usize,
@@ -553,6 +606,29 @@ impl<'gctx> Timings<'gctx> {
553606

554607
/// Render the table of all units.
555608
fn write_unit_table(&self, f: &mut impl Write) -> CargoResult<()> {
609+
let mut units: Vec<&UnitTime> = self.unit_times.iter().collect();
610+
units.sort_unstable_by(|a, b| b.duration.partial_cmp(&a.duration).unwrap());
611+
612+
// We can have a bunch of situations here.
613+
// - -Zsection-timings is enabled, and we received some custom sections, in which
614+
// case we use them to determine the headers.
615+
// - We have at least one rmeta time, so we hard-code Frontend and Codegen headers.
616+
// - We only have total durations, so we don't add any additional headers.
617+
let aggregated: Vec<AggregatedSections> =
618+
units.iter().map(|u| u.aggregate_sections()).collect();
619+
620+
let headers: Vec<String> = if aggregated
621+
.iter()
622+
.any(|s| matches!(s, AggregatedSections::OnlyMetadataTime { .. }))
623+
{
624+
vec![
625+
FRONTEND_SECTION_NAME.to_string(),
626+
CODEGEN_SECTION_NAME.to_string(),
627+
]
628+
} else {
629+
vec![]
630+
};
631+
556632
write!(
557633
f,
558634
r#"
@@ -562,20 +638,48 @@ impl<'gctx> Timings<'gctx> {
562638
<th></th>
563639
<th>Unit</th>
564640
<th>Total</th>
565-
<th>Codegen</th>
641+
{headers}
566642
<th>Features</th>
567643
</tr>
568644
</thead>
569645
<tbody>
570-
"#
646+
"#,
647+
headers = headers.iter().map(|h| format!("<th>{h}</th>")).join("\n")
571648
)?;
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() {
649+
650+
for (i, (unit, aggregated_sections)) in units.iter().zip(aggregated).enumerate() {
651+
let format_duration = |section: Option<SectionData>| match section {
652+
Some(section) => {
653+
let duration = section.duration();
654+
let pct = (duration / unit.duration) * 100.0;
655+
format!("{duration:.1}s ({:.0}%)", pct)
656+
}
576657
None => "".to_string(),
577-
Some((_rt, ctime, cent)) => format!("{:.1}s ({:.0}%)", ctime, cent),
578658
};
659+
660+
// This is a bit complex, as we assume the most general option - we can have an
661+
// arbitrary set of headers, and an arbitrary set of sections per unit, so we always
662+
// initiate the cells to be empty, and then try to find a corresponding column for which
663+
// we might have data.
664+
let mut cells: HashMap<&str, SectionData> = Default::default();
665+
666+
match &aggregated_sections {
667+
AggregatedSections::OnlyMetadataTime { frontend, codegen } => {
668+
cells.insert(FRONTEND_SECTION_NAME, *frontend);
669+
cells.insert(CODEGEN_SECTION_NAME, *codegen);
670+
}
671+
AggregatedSections::OnlyTotalDuration => {}
672+
};
673+
let cells = headers
674+
.iter()
675+
.map(|header| {
676+
format!(
677+
"<td>{}</td>",
678+
format_duration(cells.remove(header.as_str()))
679+
)
680+
})
681+
.join("\n");
682+
579683
let features = unit.unit.features.join(", ");
580684
write!(
581685
f,
@@ -584,16 +688,14 @@ impl<'gctx> Timings<'gctx> {
584688
<td>{}.</td>
585689
<td>{}{}</td>
586690
<td>{:.1}s</td>
587-
<td>{}</td>
588-
<td>{}</td>
691+
{cells}
692+
<td>{features}</td>
589693
</tr>
590694
"#,
591695
i + 1,
592696
unit.name_ver(),
593697
unit.target,
594698
unit.duration,
595-
codegen,
596-
features,
597699
)?;
598700
}
599701
write!(f, "</tbody>\n</table>\n")?;
@@ -602,15 +704,6 @@ impl<'gctx> Timings<'gctx> {
602704
}
603705

604706
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-
614707
fn name_ver(&self) -> String {
615708
format!("{} v{}", self.unit.pkg.name(), self.unit.pkg.version())
616709
}

0 commit comments

Comments
 (0)