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

Conversation

Kobzol
Copy link
Contributor

@Kobzol Kobzol commented Jun 6, 2025

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

MCP: rust-lang/compiler-team#873

r? @nnethercote

@rustbot
Copy link
Collaborator

rustbot commented Jun 6, 2025

nnethercote is not on the review rotation at the moment.
They may take a while to respond.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jun 6, 2025
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.

@nnethercote
Copy link
Contributor

Seems reasonable as a starting point. I feel like hierarchy is inevitable -- every profiler I've ever written has ended up with it.

@nnethercote nnethercote added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 12, 2025
@Kobzol
Copy link
Contributor Author

Kobzol commented Jun 12, 2025

I agree. Are you fine with the "implicit hierarchy", which then has to be reconstructed by the user reading the JSON events?

@nnethercote
Copy link
Contributor

Sure. Keeping it simple to start with sounds good, given that it's somewhat unclear exactly how this data will be used. r=me when you're ready.

@Kobzol
Copy link
Contributor Author

Kobzol commented Jun 13, 2025

Added documentation and fixed a few review remarks.

let name = match section {
TimingSection::Linking => "link",
};
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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants