Skip to content

Commit 2011ab5

Browse files
authored
Rollup merge of #142123 - Kobzol:timings, r=nnethercote
Implement initial support for timing sections (`--json=timings`) This PR implements initial support for emitting high-level compilation section timings. The idea is to provide a very lightweight way of emitting durations of various compilation sections (frontend, backend, linker, or on a more granular level macro expansion, typeck, borrowck, etc.). The ultimate goal is to stabilize this output (in some form), make Cargo pass `--json=timings` and then display this information in the HTML output of `cargo build --timings`, to make it easier to quickly profile "what takes so long" during the compilation of a Cargo project. I would personally also like if Cargo printed some of this information in the interactive `cargo build` output, but the `build --timings` use-case is the main one. Now, this information is already available with several other sources, but I don't think that we can just use them as they are, which is why I proposed a new way of outputting this data (`--json=timings`): - This data is available under `-Zself-profile`, but that is very expensive and forever unstable. It's just a too big of a hammer to tell us the duration it took to run the linker. - It could also be extracted with `-Ztime-passes`. That is pretty much "for free" in terms of performance, and it can be emitted in a structured form to JSON via `-Ztime-passes-format=json`. I guess that one alternative might be to stabilize this flag in some form, but that form might just be `--json=timings`? I guess what we could do in theory is take the already emitted time passes and reuse them for `--json=timings`. Happy to hear suggestions! I'm sending this PR mostly for a vibeck, to see if the way I implemented it is passable. There are some things to figure out: - How do we represent the sections? Originally I wanted to output `{ section, duration }`, but then I realized that it might be more useful to actually emit `start` and `end` events. Both because it enables to see the output incrementally (in case compilation takes a long time and you read the outputs directly, or Cargo decides to show this data in `cargo build` some day in the future), and because it makes it simpler to represent hierarchy (see below). The timestamps currently emit microseconds elapsed from a predetermined point in time (~start of rustc), but otherwise they are fully opaque, and should be only ever used to calculate the duration using `end - start`. We could also precompute the duration for the user in the `end` event, but that would require doing more work in rustc, which I would ideally like to avoid :P - Do we want to have some form of hierarchy? I think that it would be nice to show some more granular sections rather than just frontend/backend/linker (e.g. macro expansion, typeck and borrowck as a part of the frontend). But for that we would need some way of representing hierarchy. A simple way would be something like `{ parent: "frontend" }`, but I realized that with start/end timestamps we get the hierarchy "for free", only the client will need to reconstruct it from the order of start/end events (e.g. `start A`, `start B` means that `B` is a child of `A`). - What exactly do we want to stabilize? This is probably a question for later. I think that we should definitely stabilize the format of the emitted JSON objects, and *maybe* some specific section names (but we should also make it clear that they can be missing, e.g. you don't link everytime you invoke `rustc`). The PR be tested e.g. with `rustc +stage1 src/main.rs --json=timings --error-format=json -Zunstable-options` on a crate without dependencies (it is not easy to use `--json` with stock Cargo, because it also passes this flag to `rustc`, so this will later need Cargo integration to be usable with it). Zulip discussions: [#t-compiler > Outputting time spent in various compiler sections](https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler/topic/Outputting.20time.20spent.20in.20various.20compiler.20sections/with/518850162) MCP: rust-lang/compiler-team#873 r? ``@nnethercote``
2 parents 7c46544 + 7e42320 commit 2011ab5

File tree

10 files changed

+188
-2
lines changed

10 files changed

+188
-2
lines changed

compiler/rustc_errors/src/emitter.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::snippet::{
3434
Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
3535
};
3636
use crate::styled_buffer::StyledBuffer;
37+
use crate::timings::TimingRecord;
3738
use crate::translation::{Translate, to_fluent_args};
3839
use crate::{
3940
CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
@@ -164,11 +165,16 @@ impl Margin {
164165
}
165166
}
166167

168+
pub enum TimingEvent {
169+
Start,
170+
End,
171+
}
172+
167173
const ANONYMIZED_LINE_NUM: &str = "LL";
168174

169175
pub type DynEmitter = dyn Emitter + DynSend;
170176

171-
/// Emitter trait for emitting errors.
177+
/// Emitter trait for emitting errors and other structured information.
172178
pub trait Emitter: Translate {
173179
/// Emit a structured diagnostic.
174180
fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
@@ -177,6 +183,10 @@ pub trait Emitter: Translate {
177183
/// Currently only supported for the JSON format.
178184
fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
179185

186+
/// Emit a timestamp with start/end of a timing section.
187+
/// Currently only supported for the JSON format.
188+
fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
189+
180190
/// Emit a report about future breakage.
181191
/// Currently only supported for the JSON format.
182192
fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}

compiler/rustc_errors/src/json.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ use termcolor::{ColorSpec, WriteColor};
2828
use crate::diagnostic::IsLint;
2929
use crate::emitter::{
3030
ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme,
31-
should_show_source_code,
31+
TimingEvent, should_show_source_code,
3232
};
3333
use crate::registry::Registry;
34+
use crate::timings::{TimingRecord, TimingSection};
3435
use crate::translation::{Translate, to_fluent_args};
3536
use crate::{
3637
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
@@ -104,6 +105,7 @@ impl JsonEmitter {
104105
enum EmitTyped<'a> {
105106
Diagnostic(Diagnostic),
106107
Artifact(ArtifactNotification<'a>),
108+
SectionTiming(SectionTimestamp<'a>),
107109
FutureIncompat(FutureIncompatReport<'a>),
108110
UnusedExtern(UnusedExterns<'a>),
109111
}
@@ -135,6 +137,21 @@ impl Emitter for JsonEmitter {
135137
}
136138
}
137139

140+
fn emit_timing_section(&mut self, record: TimingRecord, event: TimingEvent) {
141+
let event = match event {
142+
TimingEvent::Start => "start",
143+
TimingEvent::End => "end",
144+
};
145+
let name = match record.section {
146+
TimingSection::Linking => "link",
147+
};
148+
let data = SectionTimestamp { name, event, timestamp: record.timestamp };
149+
let result = self.emit(EmitTyped::SectionTiming(data));
150+
if let Err(e) = result {
151+
panic!("failed to print timing section: {e:?}");
152+
}
153+
}
154+
138155
fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>, registry: &Registry) {
139156
let data: Vec<FutureBreakageItem<'_>> = diags
140157
.into_iter()
@@ -263,6 +280,16 @@ struct ArtifactNotification<'a> {
263280
emit: &'a str,
264281
}
265282

283+
#[derive(Serialize)]
284+
struct SectionTimestamp<'a> {
285+
/// Name of the section
286+
name: &'a str,
287+
/// Start/end of the section
288+
event: &'a str,
289+
/// Opaque timestamp.
290+
timestamp: u128,
291+
}
292+
266293
#[derive(Serialize)]
267294
struct FutureBreakageItem<'a> {
268295
// Always EmitTyped::Diagnostic, but we want to make sure it gets serialized

compiler/rustc_errors/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ pub use snippet::Style;
7575
pub use termcolor::{Color, ColorSpec, WriteColor};
7676
use tracing::debug;
7777

78+
use crate::emitter::TimingEvent;
7879
use crate::registry::Registry;
80+
use crate::timings::TimingRecord;
7981

8082
pub mod annotate_snippet_emitter_writer;
8183
pub mod codes;
@@ -91,6 +93,7 @@ mod snippet;
9193
mod styled_buffer;
9294
#[cfg(test)]
9395
mod tests;
96+
pub mod timings;
9497
pub mod translation;
9598

9699
pub type PResult<'a, T> = Result<T, Diag<'a>>;
@@ -1157,6 +1160,14 @@ impl<'a> DiagCtxtHandle<'a> {
11571160
self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
11581161
}
11591162

1163+
pub fn emit_timing_section_start(&self, record: TimingRecord) {
1164+
self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::Start);
1165+
}
1166+
1167+
pub fn emit_timing_section_end(&self, record: TimingRecord) {
1168+
self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::End);
1169+
}
1170+
11601171
pub fn emit_future_breakage_report(&self) {
11611172
let inner = &mut *self.inner.borrow_mut();
11621173
let diags = std::mem::take(&mut inner.future_breakage_diagnostics);

compiler/rustc_errors/src/timings.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use std::time::Instant;
2+
3+
use crate::DiagCtxtHandle;
4+
5+
/// A high-level section of the compilation process.
6+
#[derive(Copy, Clone, Debug)]
7+
pub enum TimingSection {
8+
/// Time spent linking.
9+
Linking,
10+
}
11+
12+
/// Section with attached timestamp
13+
#[derive(Copy, Clone, Debug)]
14+
pub struct TimingRecord {
15+
pub section: TimingSection,
16+
/// Microseconds elapsed since some predetermined point in time (~start of the rustc process).
17+
pub timestamp: u128,
18+
}
19+
20+
impl TimingRecord {
21+
fn from_origin(origin: Instant, section: TimingSection) -> Self {
22+
Self { section, timestamp: Instant::now().duration_since(origin).as_micros() }
23+
}
24+
25+
pub fn section(&self) -> TimingSection {
26+
self.section
27+
}
28+
29+
pub fn timestamp(&self) -> u128 {
30+
self.timestamp
31+
}
32+
}
33+
34+
/// Manages emission of start/end section timings, enabled through `--json=timings`.
35+
pub struct TimingSectionHandler {
36+
/// Time when the compilation session started.
37+
/// If `None`, timing is disabled.
38+
origin: Option<Instant>,
39+
}
40+
41+
impl TimingSectionHandler {
42+
pub fn new(enabled: bool) -> Self {
43+
let origin = if enabled { Some(Instant::now()) } else { None };
44+
Self { origin }
45+
}
46+
47+
/// Returns a RAII guard that will immediately emit a start the provided section, and then emit
48+
/// its end when it is dropped.
49+
pub fn start_section<'a>(
50+
&self,
51+
diag_ctxt: DiagCtxtHandle<'a>,
52+
section: TimingSection,
53+
) -> TimingSectionGuard<'a> {
54+
TimingSectionGuard::create(diag_ctxt, section, self.origin)
55+
}
56+
}
57+
58+
/// RAII wrapper for starting and ending section timings.
59+
pub struct TimingSectionGuard<'a> {
60+
dcx: DiagCtxtHandle<'a>,
61+
section: TimingSection,
62+
origin: Option<Instant>,
63+
}
64+
65+
impl<'a> TimingSectionGuard<'a> {
66+
fn create(dcx: DiagCtxtHandle<'a>, section: TimingSection, origin: Option<Instant>) -> Self {
67+
if let Some(origin) = origin {
68+
dcx.emit_timing_section_start(TimingRecord::from_origin(origin, section));
69+
}
70+
Self { dcx, section, origin }
71+
}
72+
}
73+
74+
impl<'a> Drop for TimingSectionGuard<'a> {
75+
fn drop(&mut self) {
76+
if let Some(origin) = self.origin {
77+
self.dcx.emit_timing_section_end(TimingRecord::from_origin(origin, self.section));
78+
}
79+
}
80+
}

compiler/rustc_interface/src/queries.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::Arc;
44
use rustc_codegen_ssa::CodegenResults;
55
use rustc_codegen_ssa::traits::CodegenBackend;
66
use rustc_data_structures::svh::Svh;
7+
use rustc_errors::timings::TimingSection;
78
use rustc_hir::def_id::LOCAL_CRATE;
89
use rustc_metadata::EncodedMetadata;
910
use rustc_middle::dep_graph::DepGraph;
@@ -88,6 +89,7 @@ impl Linker {
8889
}
8990

9091
let _timer = sess.prof.verbose_generic_activity("link_crate");
92+
let _timing = sess.timings.start_section(sess.dcx(), TimingSection::Linking);
9193
codegen_backend.link(sess, codegen_results, self.metadata, &self.output_filenames)
9294
}
9395
}

compiler/rustc_session/src/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,7 @@ impl Default for Options {
13661366
real_rust_source_base_dir: None,
13671367
edition: DEFAULT_EDITION,
13681368
json_artifact_notifications: false,
1369+
json_timings: false,
13691370
json_unused_externs: JsonUnusedExterns::No,
13701371
json_future_incompat: false,
13711372
pretty: None,
@@ -1880,6 +1881,9 @@ pub struct JsonConfig {
18801881
pub json_rendered: HumanReadableErrorType,
18811882
pub json_color: ColorConfig,
18821883
json_artifact_notifications: bool,
1884+
/// Output start and end timestamps of several high-level compilation sections
1885+
/// (frontend, backend, linker).
1886+
json_timings: bool,
18831887
pub json_unused_externs: JsonUnusedExterns,
18841888
json_future_incompat: bool,
18851889
}
@@ -1921,6 +1925,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
19211925
let mut json_artifact_notifications = false;
19221926
let mut json_unused_externs = JsonUnusedExterns::No;
19231927
let mut json_future_incompat = false;
1928+
let mut json_timings = false;
19241929
for option in matches.opt_strs("json") {
19251930
// For now conservatively forbid `--color` with `--json` since `--json`
19261931
// won't actually be emitting any colors and anything colorized is
@@ -1937,6 +1942,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
19371942
}
19381943
"diagnostic-rendered-ansi" => json_color = ColorConfig::Always,
19391944
"artifacts" => json_artifact_notifications = true,
1945+
"timings" => json_timings = true,
19401946
"unused-externs" => json_unused_externs = JsonUnusedExterns::Loud,
19411947
"unused-externs-silent" => json_unused_externs = JsonUnusedExterns::Silent,
19421948
"future-incompat" => json_future_incompat = true,
@@ -1949,6 +1955,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
19491955
json_rendered,
19501956
json_color,
19511957
json_artifact_notifications,
1958+
json_timings,
19521959
json_unused_externs,
19531960
json_future_incompat,
19541961
}
@@ -2476,6 +2483,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
24762483
json_rendered,
24772484
json_color,
24782485
json_artifact_notifications,
2486+
json_timings,
24792487
json_unused_externs,
24802488
json_future_incompat,
24812489
} = parse_json(early_dcx, matches);
@@ -2497,6 +2505,10 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
24972505
let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers);
24982506
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
24992507

2508+
if !unstable_opts.unstable_options && json_timings {
2509+
early_dcx.early_fatal("--json=timings is unstable and requires using `-Zunstable-options`");
2510+
}
2511+
25002512
check_error_format_stability(early_dcx, &unstable_opts, error_format);
25012513

25022514
let output_types = parse_output_types(early_dcx, &unstable_opts, matches);
@@ -2774,6 +2786,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
27742786
real_rust_source_base_dir,
27752787
edition,
27762788
json_artifact_notifications,
2789+
json_timings,
27772790
json_unused_externs,
27782791
json_future_incompat,
27792792
pretty,

compiler/rustc_session/src/options.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,10 @@ top_level_options!(
410410
/// by the compiler.
411411
json_artifact_notifications: bool [TRACKED],
412412

413+
/// `true` if we're emitting JSON timings with the start and end of
414+
/// high-level compilation sections
415+
json_timings: bool [UNTRACKED],
416+
413417
/// `true` if we're emitting a JSON blob containing the unused externs
414418
json_unused_externs: JsonUnusedExterns [UNTRACKED],
415419

compiler/rustc_session/src/session.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_errors::emitter::{
1818
DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination,
1919
};
2020
use rustc_errors::json::JsonEmitter;
21+
use rustc_errors::timings::TimingSectionHandler;
2122
use rustc_errors::{
2223
Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort,
2324
FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle,
@@ -156,6 +157,9 @@ pub struct Session {
156157
/// Used by `-Z self-profile`.
157158
pub prof: SelfProfilerRef,
158159

160+
/// Used to emit section timings events (enabled by `--json=timings`).
161+
pub timings: TimingSectionHandler,
162+
159163
/// Data about code being compiled, gathered during compilation.
160164
pub code_stats: CodeStats,
161165

@@ -1126,6 +1130,8 @@ pub fn build_session(
11261130
.as_ref()
11271131
.map(|_| rng().next_u32().to_base_fixed_len(CASE_INSENSITIVE).to_string());
11281132

1133+
let timings = TimingSectionHandler::new(sopts.json_timings);
1134+
11291135
let sess = Session {
11301136
target,
11311137
host,
@@ -1136,6 +1142,7 @@ pub fn build_session(
11361142
io,
11371143
incr_comp_session: RwLock::new(IncrCompSession::NotInitialized),
11381144
prof,
1145+
timings,
11391146
code_stats: Default::default(),
11401147
lint_store: None,
11411148
driver_lint_caps,

src/doc/rustc/src/command-line-arguments.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ to customize the output:
471471
- `future-incompat` - includes a JSON message that contains a report if the
472472
crate contains any code that may fail to compile in the future.
473473

474+
- `timings` - output a JSON message when a certain compilation "section"
475+
(such as frontend analysis, code generation, linking) begins or ends.
476+
474477
Note that it is invalid to combine the `--json` argument with the
475478
[`--color`](#option-color) argument, and it is required to combine `--json`
476479
with `--error-format=json`.

src/doc/rustc/src/json.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,35 @@ appropriately. (This is needed by Cargo which shares the same dependencies
298298
across multiple build targets, so it should only report an unused dependency if
299299
its not used by any of the targets.)
300300

301+
## Timings
302+
303+
**This setting is currently unstable and requires usage of `-Zunstable-options`.**
304+
305+
The `--timings` option will tell `rustc` to emit messages when a certain compilation
306+
section (such as code generation or linking) begins or ends. The messages currently have
307+
the following format:
308+
309+
```json
310+
{
311+
"$message_type": "section_timing", /* Type of this message */
312+
"event": "start", /* Marks the "start" or "end" of the compilation section */
313+
"name": "link", /* The name of the compilation section */
314+
// Opaque timestamp when the message was emitted, in microseconds
315+
// The timestamp is currently relative to the beginning of the compilation session
316+
"time": 12345
317+
}
318+
```
319+
320+
Note that the JSON format of the `timings` messages is unstable and subject to change.
321+
322+
Compilation sections can be nested; for example, if you encounter the start of "foo",
323+
then the start of "bar", then the end of "bar" and then the end of "bar", it means that the
324+
"bar" section happened as a part of the "foo" section.
325+
326+
The timestamp should only be used for computing the duration of each section.
327+
328+
We currently do not guarantee any specific section names to be emitted.
329+
301330
[option-emit]: command-line-arguments.md#option-emit
302331
[option-error-format]: command-line-arguments.md#option-error-format
303332
[option-json]: command-line-arguments.md#option-json

0 commit comments

Comments
 (0)