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: https://github.com/rust-lang/compiler-team/issues/873
r? ``@nnethercote``
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user