diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index fe01e289334cc..6ab6f96079ebd 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -34,6 +34,7 @@ use crate::snippet::{ Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString, }; use crate::styled_buffer::StyledBuffer; +use crate::timings::TimingRecord; use crate::translation::{Translate, to_fluent_args}; use crate::{ CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level, @@ -164,11 +165,16 @@ impl Margin { } } +pub enum TimingEvent { + 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); @@ -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, _record: TimingRecord, _event: TimingEvent) {} + /// Emit a report about future breakage. /// Currently only supported for the JSON format. fn emit_future_breakage_report(&mut self, _diags: Vec, _registry: &Registry) {} diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index a6583407b7e7e..d67e2ba2d60ec 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -28,9 +28,10 @@ use termcolor::{ColorSpec, WriteColor}; use crate::diagnostic::IsLint; use crate::emitter::{ ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme, - should_show_source_code, + TimingEvent, should_show_source_code, }; use crate::registry::Registry; +use crate::timings::{TimingRecord, TimingSection}; use crate::translation::{Translate, to_fluent_args}; use crate::{ CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions, @@ -104,6 +105,7 @@ impl JsonEmitter { enum EmitTyped<'a> { Diagnostic(Diagnostic), Artifact(ArtifactNotification<'a>), + SectionTiming(SectionTimestamp<'a>), FutureIncompat(FutureIncompatReport<'a>), UnusedExtern(UnusedExterns<'a>), } @@ -135,6 +137,21 @@ impl Emitter for JsonEmitter { } } + fn emit_timing_section(&mut self, record: TimingRecord, event: TimingEvent) { + let event = match event { + TimingEvent::Start => "start", + TimingEvent::End => "end", + }; + let name = match record.section { + TimingSection::Linking => "link", + }; + let data = SectionTimestamp { name, event, timestamp: record.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, registry: &Registry) { let data: Vec> = diags .into_iter() @@ -263,6 +280,16 @@ struct ArtifactNotification<'a> { emit: &'a str, } +#[derive(Serialize)] +struct SectionTimestamp<'a> { + /// Name of the section + name: &'a str, + /// Start/end of the section + event: &'a str, + /// Opaque timestamp. + timestamp: u128, +} + #[derive(Serialize)] struct FutureBreakageItem<'a> { // Always EmitTyped::Diagnostic, but we want to make sure it gets serialized diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 9f72fc4705aea..b520eb353d650 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -74,7 +74,9 @@ pub use snippet::Style; pub use termcolor::{Color, ColorSpec, WriteColor}; use tracing::debug; +use crate::emitter::TimingEvent; use crate::registry::Registry; +use crate::timings::TimingRecord; pub mod annotate_snippet_emitter_writer; pub mod codes; @@ -90,6 +92,7 @@ mod snippet; mod styled_buffer; #[cfg(test)] mod tests; +pub mod timings; pub mod translation; pub type PResult<'a, T> = Result>; @@ -1156,6 +1159,14 @@ impl<'a> DiagCtxtHandle<'a> { self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type); } + pub fn emit_timing_section_start(&self, record: TimingRecord) { + self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::Start); + } + + pub fn emit_timing_section_end(&self, record: TimingRecord) { + self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::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); diff --git a/compiler/rustc_errors/src/timings.rs b/compiler/rustc_errors/src/timings.rs new file mode 100644 index 0000000000000..27fc9df8d796a --- /dev/null +++ b/compiler/rustc_errors/src/timings.rs @@ -0,0 +1,80 @@ +use std::time::Instant; + +use crate::DiagCtxtHandle; + +/// A high-level section of the compilation process. +#[derive(Copy, Clone, Debug)] +pub enum TimingSection { + /// Time spent linking. + Linking, +} + +/// Section with attached timestamp +#[derive(Copy, Clone, Debug)] +pub struct TimingRecord { + pub section: TimingSection, + /// Microseconds elapsed since some predetermined point in time (~start of the rustc process). + pub timestamp: u128, +} + +impl TimingRecord { + fn from_origin(origin: Instant, section: TimingSection) -> Self { + Self { section, timestamp: Instant::now().duration_since(origin).as_micros() } + } + + pub fn section(&self) -> TimingSection { + self.section + } + + pub fn timestamp(&self) -> u128 { + self.timestamp + } +} + +/// Manages emission of start/end section timings, enabled through `--json=timings`. +pub struct TimingSectionHandler { + /// Time when the compilation session started. + /// If `None`, timing is disabled. + origin: Option, +} + +impl TimingSectionHandler { + pub fn new(enabled: bool) -> Self { + let origin = if enabled { Some(Instant::now()) } else { None }; + Self { origin } + } + + /// 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.origin) + } +} + +/// RAII wrapper for starting and ending section timings. +pub struct TimingSectionGuard<'a> { + dcx: DiagCtxtHandle<'a>, + section: TimingSection, + origin: Option, +} + +impl<'a> TimingSectionGuard<'a> { + fn create(dcx: DiagCtxtHandle<'a>, section: TimingSection, origin: Option) -> Self { + if let Some(origin) = origin { + dcx.emit_timing_section_start(TimingRecord::from_origin(origin, section)); + } + Self { dcx, section, origin } + } +} + +impl<'a> Drop for TimingSectionGuard<'a> { + fn drop(&mut self) { + if let Some(origin) = self.origin { + self.dcx.emit_timing_section_end(TimingRecord::from_origin(origin, self.section)); + } + } +} diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 9a474b910f686..877440ec7d2cb 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -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_metadata::EncodedMetadata; use rustc_middle::dep_graph::DepGraph; @@ -88,6 +89,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.metadata, &self.output_filenames) } } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 406a6bd335a74..1a5571f8a224d 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -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, @@ -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, } @@ -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 @@ -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, @@ -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, } @@ -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); @@ -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); @@ -2774,6 +2786,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, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index f76f258d00de9..78f79f6484ad9 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -410,6 +410,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], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b8b4518b14e29..ca42c5a425660 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -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, @@ -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, @@ -1126,6 +1130,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, @@ -1136,6 +1142,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, diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index b704cee705b05..d45ad1be27b8c 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -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`. diff --git a/src/doc/rustc/src/json.md b/src/doc/rustc/src/json.md index c853f34ee036c..8839a1a78c31d 100644 --- a/src/doc/rustc/src/json.md +++ b/src/doc/rustc/src/json.md @@ -298,6 +298,31 @@ 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 */ + "event": "start", /* Marks the "start" or "end" of the compilation section */ + "name": "link", /* The name of the compilation section */ + "time": 12345 /* Opaque 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. + +The timestamp should only be used for computing the duration of each 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