Skip to content

Implement initial support for timing sections (--json=timings) #142123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::snippet::{
Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
};
use crate::styled_buffer::StyledBuffer;
use crate::timings::TimingSection;
use crate::translation::{Translate, to_fluent_args};
use crate::{
CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
Expand Down Expand Up @@ -164,11 +165,16 @@ impl Margin {
}
}

pub enum TimingSectionKind {
Start,
End,
}

const ANONYMIZED_LINE_NUM: &str = "LL";

pub type DynEmitter = dyn Emitter + DynSend;

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

/// Emit a timestamp with start/end of a timing section.
/// Currently only supported for the JSON format.
fn emit_timing_section(&mut self, _section: TimingSection, _kind: TimingSectionKind) {}

/// Emit a report about future breakage.
/// Currently only supported for the JSON format.
fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
Expand Down
34 changes: 33 additions & 1 deletion compiler/rustc_errors/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::error::Report;
use std::io::{self, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use std::vec;

use derive_setters::Setters;
Expand All @@ -28,9 +29,10 @@ use termcolor::{ColorSpec, WriteColor};
use crate::diagnostic::IsLint;
use crate::emitter::{
ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme,
should_show_source_code,
TimingSectionKind, should_show_source_code,
};
use crate::registry::Registry;
use crate::timings::TimingSection;
use crate::translation::{Translate, to_fluent_args};
use crate::{
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
Expand Down Expand Up @@ -104,6 +106,7 @@ impl JsonEmitter {
enum EmitTyped<'a> {
Diagnostic(Diagnostic),
Artifact(ArtifactNotification<'a>),
SectionTiming(SectionTimestamp<'a>),
FutureIncompat(FutureIncompatReport<'a>),
UnusedExtern(UnusedExterns<'a>),
}
Expand Down Expand Up @@ -135,6 +138,25 @@ impl Emitter for JsonEmitter {
}
}

fn emit_timing_section(&mut self, section: TimingSection, kind: TimingSectionKind) {
let kind = match kind {
TimingSectionKind::Start => "start",
TimingSectionKind::End => "end",
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a method on TimingSectionKind.

Copy link
Contributor Author

@Kobzol Kobzol Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but the TimingSectionKind is a shared data structure for all emitters, and this form of output is specific for JSON. It should IMO live here, in theory we could emit the timings also in other formats. It also avoids having to go to a different file to see how the output format looks like.

I'm fine with moving it to a function in the json module, but I don't think it belongs as a method on the section kind.

let name = match section {
TimingSection::Linking => "link",
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, for TimingSection.

let timestamp = SystemTime::now()
Copy link
Member

@bjorn3 bjorn3 Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should use Instant rather than SystemTime. Using the latter causes issues when changing the system time during a build, for example because your ntp daemon finally got an internet connection. And IMHO it should preferrably use CLOCK_MONOTONIC rather than CLOCK_BOOTTIME as it doesn't make sense to count time where the system is suspended in performance profiles.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use system time for some incr dir names, I think, but you're right that for profiling Instant makes more sense. But then I have to count the duration from some origin time again. Before it was from the creation of the JSON emitter, but that had some issues w.r.t. multiple emitters existing. Should I just take a snapshot when rustc starts or something? 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right and Instant doesn't provide a way to serialize it. Maybe call clock_gettime(CLOCK_MONOTONIC) (unix) or QueryPerformanceCounter (windows) directly and return the raw result?

.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time should always be greater than the unix epoch")
.as_micros();
let data = SectionTimestamp { name, kind, timestamp };
let result = self.emit(EmitTyped::SectionTiming(data));
if let Err(e) = result {
panic!("failed to print timing section: {e:?}");
}
}

fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>, registry: &Registry) {
let data: Vec<FutureBreakageItem<'_>> = diags
.into_iter()
Expand Down Expand Up @@ -263,6 +285,16 @@ struct ArtifactNotification<'a> {
emit: &'a str,
}

#[derive(Serialize)]
struct SectionTimestamp<'a> {
/// Name of the section
name: &'a str,
/// Start/end of the section
kind: &'a str,
/// Microseconds elapsed since some predetermined point in time (~start of the rustc process).
timestamp: u128,
}

#[derive(Serialize)]
struct FutureBreakageItem<'a> {
// Always EmitTyped::Diagnostic, but we want to make sure it gets serialized
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ pub use snippet::Style;
pub use termcolor::{Color, ColorSpec, WriteColor};
use tracing::debug;

use crate::emitter::TimingSectionKind;
use crate::registry::Registry;
use crate::timings::TimingSection;

pub mod annotate_snippet_emitter_writer;
pub mod codes;
Expand All @@ -91,6 +93,7 @@ mod snippet;
mod styled_buffer;
#[cfg(test)]
mod tests;
pub mod timings;
pub mod translation;

pub type PResult<'a, T> = Result<T, Diag<'a>>;
Expand Down Expand Up @@ -1144,6 +1147,14 @@ impl<'a> DiagCtxtHandle<'a> {
self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
}

pub fn emit_timing_section_start(&self, section: TimingSection) {
self.inner.borrow_mut().emitter.emit_timing_section(section, TimingSectionKind::Start);
}

pub fn emit_timing_section_end(&self, section: TimingSection) {
self.inner.borrow_mut().emitter.emit_timing_section(section, TimingSectionKind::End);
}

pub fn emit_future_breakage_report(&self) {
let inner = &mut *self.inner.borrow_mut();
let diags = std::mem::take(&mut inner.future_breakage_diagnostics);
Expand Down
52 changes: 52 additions & 0 deletions compiler/rustc_errors/src/timings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::DiagCtxtHandle;

/// A high-level section of the compilation process.
#[derive(Copy, Clone, Debug)]
pub enum TimingSection {
/// Time spent linking.
Linking,
}

/// Manages emission of start/end section timings, enabled through `--json=timings`.
pub struct TimingSectionHandler {
enabled: bool,
}

impl TimingSectionHandler {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}

/// Returns a RAII guard that will immediately emit a start the provided section, and then emit
/// its end when it is dropped.
pub fn start_section<'a>(
&self,
diag_ctxt: DiagCtxtHandle<'a>,
section: TimingSection,
) -> TimingSectionGuard<'a> {
TimingSectionGuard::create(diag_ctxt, section, self.enabled)
}
}

pub struct TimingSectionGuard<'a> {
dcx: DiagCtxtHandle<'a>,
section: TimingSection,
enabled: bool,
}

impl<'a> TimingSectionGuard<'a> {
fn create(dcx: DiagCtxtHandle<'a>, section: TimingSection, enabled: bool) -> Self {
if enabled {
dcx.emit_timing_section_start(section);
}
Self { dcx, section, enabled }
}
}

impl<'a> Drop for TimingSectionGuard<'a> {
fn drop(&mut self) {
if self.enabled {
self.dcx.emit_timing_section_end(self.section);
}
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_interface/src/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use rustc_codegen_ssa::CodegenResults;
use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_data_structures::svh::Svh;
use rustc_errors::timings::TimingSection;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::dep_graph::DepGraph;
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -84,6 +85,7 @@ impl Linker {
}

let _timer = sess.prof.verbose_generic_activity("link_crate");
let _timing = sess.timings.start_section(sess.dcx(), TimingSection::Linking);
codegen_backend.link(sess, codegen_results, &self.output_filenames)
}
}
13 changes: 13 additions & 0 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,7 @@ impl Default for Options {
real_rust_source_base_dir: None,
edition: DEFAULT_EDITION,
json_artifact_notifications: false,
json_timings: false,
json_unused_externs: JsonUnusedExterns::No,
json_future_incompat: false,
pretty: None,
Expand Down Expand Up @@ -1880,6 +1881,9 @@ pub struct JsonConfig {
pub json_rendered: HumanReadableErrorType,
pub json_color: ColorConfig,
json_artifact_notifications: bool,
/// Output start and end timestamps of several high-level compilation sections
/// (frontend, backend, linker).
json_timings: bool,
pub json_unused_externs: JsonUnusedExterns,
json_future_incompat: bool,
}
Expand Down Expand Up @@ -1921,6 +1925,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
let mut json_artifact_notifications = false;
let mut json_unused_externs = JsonUnusedExterns::No;
let mut json_future_incompat = false;
let mut json_timings = false;
for option in matches.opt_strs("json") {
// For now conservatively forbid `--color` with `--json` since `--json`
// won't actually be emitting any colors and anything colorized is
Expand All @@ -1937,6 +1942,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
}
"diagnostic-rendered-ansi" => json_color = ColorConfig::Always,
"artifacts" => json_artifact_notifications = true,
"timings" => json_timings = true,
"unused-externs" => json_unused_externs = JsonUnusedExterns::Loud,
"unused-externs-silent" => json_unused_externs = JsonUnusedExterns::Silent,
"future-incompat" => json_future_incompat = true,
Expand All @@ -1949,6 +1955,7 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json
json_rendered,
json_color,
json_artifact_notifications,
json_timings,
json_unused_externs,
json_future_incompat,
}
Expand Down Expand Up @@ -2476,6 +2483,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
json_rendered,
json_color,
json_artifact_notifications,
json_timings,
json_unused_externs,
json_future_incompat,
} = parse_json(early_dcx, matches);
Expand All @@ -2497,6 +2505,10 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers);
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);

if !unstable_opts.unstable_options && json_timings {
early_dcx.early_fatal("--json=timings is unstable and requires using `-Zunstable-options`");
}

check_error_format_stability(early_dcx, &unstable_opts, error_format);

let output_types = parse_output_types(early_dcx, &unstable_opts, matches);
Expand Down Expand Up @@ -2765,6 +2777,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
real_rust_source_base_dir,
edition,
json_artifact_notifications,
json_timings,
json_unused_externs,
json_future_incompat,
pretty,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,10 @@ top_level_options!(
/// by the compiler.
json_artifact_notifications: bool [TRACKED],

/// `true` if we're emitting JSON timings with the start and end of
/// high-level compilation sections
json_timings: bool [UNTRACKED],

/// `true` if we're emitting a JSON blob containing the unused externs
json_unused_externs: JsonUnusedExterns [UNTRACKED],

Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rustc_errors::emitter::{
DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination,
};
use rustc_errors::json::JsonEmitter;
use rustc_errors::timings::TimingSectionHandler;
use rustc_errors::{
Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort,
FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle,
Expand Down Expand Up @@ -156,6 +157,9 @@ pub struct Session {
/// Used by `-Z self-profile`.
pub prof: SelfProfilerRef,

/// Used to emit section timings events (enabled by `--json=timings`).
pub timings: TimingSectionHandler,

/// Data about code being compiled, gathered during compilation.
pub code_stats: CodeStats,

Expand Down Expand Up @@ -1137,6 +1141,8 @@ pub fn build_session(
.as_ref()
.map(|_| rng().next_u32().to_base_fixed_len(CASE_INSENSITIVE).to_string());

let timings = TimingSectionHandler::new(sopts.json_timings);

let sess = Session {
target,
host,
Expand All @@ -1147,6 +1153,7 @@ pub fn build_session(
io,
incr_comp_session: RwLock::new(IncrCompSession::NotInitialized),
prof,
timings,
code_stats: Default::default(),
lint_store: None,
driver_lint_caps,
Expand Down
3 changes: 3 additions & 0 deletions src/doc/rustc/src/command-line-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,9 @@ to customize the output:
- `future-incompat` - includes a JSON message that contains a report if the
crate contains any code that may fail to compile in the future.

- `timings` - output a JSON message when a certain compilation "section"
(such as frontend analysis, code generation, linking) begins or ends.

Note that it is invalid to combine the `--json` argument with the
[`--color`](#option-color) argument, and it is required to combine `--json`
with `--error-format=json`.
Expand Down
23 changes: 23 additions & 0 deletions src/doc/rustc/src/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,29 @@ appropriately. (This is needed by Cargo which shares the same dependencies
across multiple build targets, so it should only report an unused dependency if
its not used by any of the targets.)

## Timings

**This setting is currently unstable and requires usage of `-Zunstable-options`.**

The `--timings` option will tell `rustc` to emit messages when a certain compilation
section (such as code generation or linking) begins or ends. The messages will have
the following format:

```json
{
"$message_type": "section_timing", /* Type of this message */
"kind": "start", /* Marks the "start" or "end" of the compilation section */
"name": "link", /* The name of the compilation section */
"time": 12345 /* UNIX timestamp when the message was emitted, in microseconds */
}
```

Compilation sections can be nested; for example, if you encounter the start of "foo",
then the start of "bar", then the end of "bar" and then the end of "bar", it means that the
"bar" section happened as a part of the "foo" section.

We currently do not guarantee any specific section names to be emitted.

[option-emit]: command-line-arguments.md#option-emit
[option-error-format]: command-line-arguments.md#option-error-format
[option-json]: command-line-arguments.md#option-json
Loading