Skip to content

Commit 5224b47

Browse files
committed
Make headers of the HTML --timings unit table dynamic
1 parent 037cf46 commit 5224b47

File tree

1 file changed

+111
-20
lines changed

1 file changed

+111
-20
lines changed

src/cargo/core/compiler/timings.rs

Lines changed: 111 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;
@@ -97,6 +98,29 @@ struct UnitTime {
9798
sections: IndexMap<String, CompilationSection>,
9899
}
99100

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

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

556607
/// Render the table of all units.
557608
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+
558632
write!(
559633
f,
560634
r#"
@@ -564,20 +638,48 @@ impl<'gctx> Timings<'gctx> {
564638
<th></th>
565639
<th>Unit</th>
566640
<th>Total</th>
567-
<th>Codegen</th>
641+
{headers}
568642
<th>Features</th>
569643
</tr>
570644
</thead>
571645
<tbody>
572-
"#
646+
"#,
647+
headers = headers.iter().map(|h| format!("<th>{h}</th>")).join("\n")
573648
)?;
574-
let mut units: Vec<&UnitTime> = self.unit_times.iter().collect();
575-
units.sort_unstable_by(|a, b| b.duration.partial_cmp(&a.duration).unwrap());
576-
for (i, unit) in units.iter().enumerate() {
577-
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+
}
578657
None => "".to_string(),
579-
Some((_rt, ctime, cent)) => format!("{:.1}s ({:.0}%)", ctime, cent),
580658
};
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+
581683
let features = unit.unit.features.join(", ");
582684
write!(
583685
f,
@@ -586,16 +688,14 @@ impl<'gctx> Timings<'gctx> {
586688
<td>{}.</td>
587689
<td>{}{}</td>
588690
<td>{:.1}s</td>
589-
<td>{}</td>
590-
<td>{}</td>
691+
{cells}
692+
<td>{features}</td>
591693
</tr>
592694
"#,
593695
i + 1,
594696
unit.name_ver(),
595697
unit.target,
596698
unit.duration,
597-
codegen,
598-
features,
599699
)?;
600700
}
601701
write!(f, "</tbody>\n</table>\n")?;
@@ -604,15 +704,6 @@ impl<'gctx> Timings<'gctx> {
604704
}
605705

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

0 commit comments

Comments
 (0)