Skip to content

Make "frame" spans go on a separate trace line than others #4451

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

Closed
wants to merge 1 commit into from

Conversation

Stypox
Copy link
Contributor

@Stypox Stypox commented Jul 7, 2025

tracing_chrome supports two styles of data that are shown differently in https://ui.perfetto.dev:

  • "Threaded", i.e. each thread has its own trace line in the UI, each line with a tree structure of nested spans. Since Miri is single-threaded, this is equivalent to having a single trace line with all of the spans.
  • "Async", i.e. each span has its own trace line in the UI (this is also the representation the Firefox profiler uses).

I believe the "Threaded" style generally allows for more insights as argued here, however it's a bit cumbersome to use because the "frame" spans (i.e. those that indicate stack frames in the interpreted program) are very big with respect to other spans and are therefore in the way. So this PR puts them (and only them) on a separate trace line:

Ideally this kind of filtering should be doable from the trace viewer UI, but I don't think https://ui.perfetto.dev supports anything like that.

Let me know if I should make the changes more general, e.g. by adding a new TraceStyle like ThreadedWithExceptions(Vec<&'static str>) instead of a hardcoded if. I don't think we're going to need this for other spans though. <- this was done

.id()
.into_u64()
),
_ => if span.metadata().name() == "frame" {
Copy link
Member

Choose a reason for hiding this comment

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

I'm definitely not a fan of hard-coding a particular name here, that way lies total spaghetti code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, I've made it more general

"frame" spans indicate stack frames in the interpreted program
@Stypox Stypox force-pushed the separate-frame-in-traces branch from 7b27a2f to d7ec893 Compare July 7, 2025 08:01
@Stypox
Copy link
Contributor Author

Stypox commented Jul 7, 2025

@rustbot ready

@rustbot rustbot added the S-waiting-on-review Status: Waiting for a review to complete label Jul 7, 2025
@RalfJung
Copy link
Member

RalfJung commented Jul 7, 2025

You just moved the "frame" name checking to a different file, I don't see how this is better.

The "frame" span itself should have appropriate metadata that controls this behavior, rather than special-casing the name after it has already entered the logging system. IOW, the code here is in charge of saying what happens with frame.

@RalfJung
Copy link
Member

RalfJung commented Jul 7, 2025

I don't fully understand neither tracing nor the visualizations you show, but it seems like you might want the perf-related spans in one "line" and the debugging-related ones in another? frame is just an example here for the one debugging span that is actually enabled in regular rustc build, but there is nothing very special about it.

@RalfJung RalfJung added S-waiting-on-author Status: Waiting for the PR author to address review comments and removed S-waiting-on-review Status: Waiting for a review to complete labels Jul 7, 2025
@Stypox
Copy link
Contributor Author

Stypox commented Jul 13, 2025

The "frame" span itself should have appropriate metadata that controls this behavior, rather than special-casing the name after it has already entered the logging system. IOW, the code here is in charge of saying what happens with frame.

Yeah I agree, and the right way to do this would be to use the target parameter when creating spans, which allows setting a category to the span and defaults to the module name. However, the data still appears all on the same "line" when visualizing it in Perfetto, and while there are ways to separate it out like explained in this comment, it's quite cumbersome to setup the UI manually every time you open a trace. Speaking of which, I looked for ways to save a Perfetto workspace to file (in order to be able to restore the queries and the configuration later), but it seems like there is no way to do so.

frame is just an example here for the one debugging span that is actually enabled in regular rustc build, but there is nothing very special about it.

The problem with frame is that, unlike other spans, it's long-lived and can grow big nested trees, which makes interpreting the visualization more cumbersome. But yeah other than this there is nothing special about it.


One idea me and Max had during our last meeting is to allow enabling/disabling spans from specific modules at runtime, in the same way that text logs from specific modules be enabled/disabled using the MIRI_LOG= env var. Do you think this would be useful? It would be quite easy to add (e.g. using another environment variable and then tracing::EnvFilter).

@RalfJung
Copy link
Member

Yeah I agree, and the right way to do this would be to use the target parameter when creating spans, which allows setting a category to the span and defaults to the module name. However, the data still appears all on the same "line" when visualizing it in Perfetto, and while there are ways to separate it out like explained in #4452 (comment), it's quite cumbersome to setup the UI manually every time you open a trace. Speaking of which, I looked for ways to save a Perfetto workspace to file (in order to be able to restore the queries and the configuration later), but it seems like there is no way to do so.

I have no idea what you are talking about or how that relates to my comment that you are replying to.^^

What I mean is that this should become something like this pseudo-code

let span = info_span!("frame", tracing_separate_root=true, "{}", instance);

I have no idea how flexible the tracing system is, but this is clearly information that the span should provide ("treat me as a separate root", or whatever the right terminology is here), not something that should be hard-coded in Miri by matching on the span's name.

One idea me and Max had during our last meeting is to allow enabling/disabling spans from specific modules at runtime, in the same way that text logs from specific modules be enabled/disabled using the MIRI_LOG= env var. Do you think this would be useful? It would be quite easy to add (e.g. using another environment variable and then tracing::EnvFilter).

I'm fine with adding that. No idea if it is useful.^^

Or maybe frame should just be excluded from tracing entirely, if there's a way to do that (controlled by the code that creates the span ofc, not controlled by Miri)? Originally I thought maybe it could be useful to explore how much time Miri spends in which function, but maybe that's not currently very useful.

@Stypox
Copy link
Contributor Author

Stypox commented Jul 14, 2025

I have no idea what you are talking about or how that relates to my comment that you are replying to.^^

I meant that the intended way to tell some spans apart from some others, as far as I understand from the tracing library documentation, is by putting them in separate categories. Then the unwanted span categories can be filtered out when analyzing the trace file, e.g. with Perfetto's SQL queries.

What I mean is that this should become something like this pseudo-code let span = info_span!("frame", tracing_separate_root=true, "{}", instance);

Yeah I agree that would be the best solution, however the "tracing_separate_root=true" would act as a parameter being logged, and would be printed together with the logs when MIRI_LOG is enabled. And then I would need to change tracing-chrome to treat that parameter as a hint to put the span on a separate "line". But yeah this would work.

Or maybe frame should just be excluded from tracing entirely, if there's a way to do that (controlled by the code that creates the span ofc, not controlled by Miri)? Originally I thought maybe it could be useful to explore how much time Miri spends in which function, but maybe that's not currently very useful.

I think frame can be useful, e.g. in #4297 to understand which parts of the random number generator are slow for Miri. But if we wanted to exclude it from tracing, we could do so by reintroducing the Machine::TRACING_ENABLED constant.

@RalfJung
Copy link
Member

RalfJung commented Jul 14, 2025

Yeah I agree that would be the best solution, however the "tracing_separate_root=true" would act as a parameter being logged, and would be printed together with the logs when MIRI_LOG is enabled. And then I would need to change tracing-chrome to treat that parameter as a hint to put the span on a separate "line". But yeah this would work.

We have have to have a call since I feel like we are still talking completely past each other and I don't know how to make myself more clear. :/

I didn't suggest it to be a parameter to be logged. I suggested this to be some distinguisher on the span, some metadata, that later layers can check. I don't know what the syntax of that could/should look like -- remember that I know nothing about the concrete details of the tracing library, I am talking purely in terms of general high-level concepts here. I am relying on you to map that to tracing concepts; do not expect whatever I say to already be phrased in terms of the tracing API.

I think frame can be useful, e.g. in #4297 to understand which parts of the random number generator are slow for Miri.

Okay, makes sense.

But if we wanted to exclude it from tracing, we could do so by reintroducing the Machine::TRACING_ENABLED constant.

I don't see how that could possibly help.

@Stypox
Copy link
Contributor Author

Stypox commented Jul 15, 2025

Closing in favor of rust-lang/rust#143955

@Stypox Stypox closed this Jul 15, 2025
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jul 17, 2025
…fJung

Make frame spans appear on a separate trace line

This PR changes tracing_chrome's `tracing::Layer` so that if a span has the "tracing_separate_line" field as one of the span arguments, that span is put on a separate trace line. See rust-lang/miri#4451 for an earlier attempt and for screenshots explaining better what I mean by "separate trace line".

This PR also makes the "frame" span use this feature (so it appears on a separate trace line, see rust-lang/miri#4451 for motivation), but passes `tracing::field::Empty` as the span parameter value so it is ignored by other tracing layers (e.g. the logger):

```rust
info_span!("frame", tracing_separate_line = Empty, "{}", instance);
```

<details><summary>Also see the following discussion I had with ``@RalfJung</summary>``

> Is there no way to attach metadata we could use instead?

[These](https://docs.rs/tracing-core/0.1.34/src/tracing_core/metadata.rs.html#57) are the **static** metadata items we can control about a span. We can't add more metadata outside of them. The most relevant are:
- `name` (for the frame span it's currently "frame")
- `target` which acts as the category (for the frame span it's currently "rustc_const_eval::interpret::stack" by default)
- `fields` which contains a list of the *names* of each of the arguments passed to the `span!` macro (for the frame span it's currently ["message"], where "message" is the default identifier for data passed in the `format!` syntax)

When the tracing code is called at runtime, the **dynamic** values of the arguments are collected into a [`ValueSet`](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#166). Each argument value stored there corresponds with one of the static names stored in `fields` (see above).

---

We have already determined that filtering out spans by `name` is not a good idea, and I would say the same goes for `target`. Both the `name` and the `target` fields are printed to stderr when `MIRI_LOG=` is enabled, so changing them to contain an identifier (e.g. "frame:tracing_separate_root" instead of "frame" as the name) would uselessly clutter the text logs (unless we add one more filter [there](https://github.com/rust-lang/rust/blob/master/compiler/rustc_log/src/lib.rs#L137), but then it gets even more complicated).

```rust
// examples of how the above (problematic) solutions would look like
info_span!("frame:tracing_separate_root", "{}", instance);
info_span!(target: "tracing_separate_root", "frame", "{}", instance);
```

---

So that leaves us with `fields` and their runtime values. Now, my initial thought (inspired by [this comment](rust-lang/miri#4451 (comment))) was to use a field with the static name "tracing_separate_root" and with a dynamic boolean value of "true". In `tracing_chrome.rs` we can easily check if this field is true and act accordingly. This would work but then again this field would also be picked up by the logger when `MIRI_LOG=` is enabled, and would uselessly clutter the text logs.

```rust
// example of how the above (problematic) solution would look like
info_span!("frame", tracing_separate_root = true, "{}", instance);
```

---

To avoid cluttering the text logs, we can instead set "tracing_separate_root" to the dynamic value of `tracing::field::Empty`. Citing from [here](https://docs.rs/tracing/0.1.41/tracing/field/struct.Empty.html), "when a field’s value is `Empty`, it will not be recorded".  "not being recorded" means that the field and its value won't be printed to stderr text logs, nor will it be printed by any other tracing layers that might be attached in the future. In `tracing_chrome.rs` we would still be able to check if "tracing_separate_root" is in the list of static `fields`, and act accordingly. So I believe this solution would effectively allow us to attach metadata to a span in a way that does not clutter logs and still allows being read in `tracing_chrome.rs`.

If we ever wanted to pass arbitrary metadata (i.e. not just a present/not present flag), it would be possible with a custom `Empty` that also holds data and implement `Value` without doing anything ([like `Empty` does](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#775)).

```rust
// example of how the above solution would look like
info_span!("frame", tracing_separate_root = tracing::field::Empty, "{}", instance);
```
</details>
rust-timer added a commit to rust-lang/rust that referenced this pull request Jul 17, 2025
Rollup merge of #143955 - Stypox:tracing-frame-filter, r=RalfJung

Make frame spans appear on a separate trace line

This PR changes tracing_chrome's `tracing::Layer` so that if a span has the "tracing_separate_line" field as one of the span arguments, that span is put on a separate trace line. See rust-lang/miri#4451 for an earlier attempt and for screenshots explaining better what I mean by "separate trace line".

This PR also makes the "frame" span use this feature (so it appears on a separate trace line, see rust-lang/miri#4451 for motivation), but passes `tracing::field::Empty` as the span parameter value so it is ignored by other tracing layers (e.g. the logger):

```rust
info_span!("frame", tracing_separate_line = Empty, "{}", instance);
```

<details><summary>Also see the following discussion I had with ``@RalfJung</summary>``

> Is there no way to attach metadata we could use instead?

[These](https://docs.rs/tracing-core/0.1.34/src/tracing_core/metadata.rs.html#57) are the **static** metadata items we can control about a span. We can't add more metadata outside of them. The most relevant are:
- `name` (for the frame span it's currently "frame")
- `target` which acts as the category (for the frame span it's currently "rustc_const_eval::interpret::stack" by default)
- `fields` which contains a list of the *names* of each of the arguments passed to the `span!` macro (for the frame span it's currently ["message"], where "message" is the default identifier for data passed in the `format!` syntax)

When the tracing code is called at runtime, the **dynamic** values of the arguments are collected into a [`ValueSet`](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#166). Each argument value stored there corresponds with one of the static names stored in `fields` (see above).

---

We have already determined that filtering out spans by `name` is not a good idea, and I would say the same goes for `target`. Both the `name` and the `target` fields are printed to stderr when `MIRI_LOG=` is enabled, so changing them to contain an identifier (e.g. "frame:tracing_separate_root" instead of "frame" as the name) would uselessly clutter the text logs (unless we add one more filter [there](https://github.com/rust-lang/rust/blob/master/compiler/rustc_log/src/lib.rs#L137), but then it gets even more complicated).

```rust
// examples of how the above (problematic) solutions would look like
info_span!("frame:tracing_separate_root", "{}", instance);
info_span!(target: "tracing_separate_root", "frame", "{}", instance);
```

---

So that leaves us with `fields` and their runtime values. Now, my initial thought (inspired by [this comment](rust-lang/miri#4451 (comment))) was to use a field with the static name "tracing_separate_root" and with a dynamic boolean value of "true". In `tracing_chrome.rs` we can easily check if this field is true and act accordingly. This would work but then again this field would also be picked up by the logger when `MIRI_LOG=` is enabled, and would uselessly clutter the text logs.

```rust
// example of how the above (problematic) solution would look like
info_span!("frame", tracing_separate_root = true, "{}", instance);
```

---

To avoid cluttering the text logs, we can instead set "tracing_separate_root" to the dynamic value of `tracing::field::Empty`. Citing from [here](https://docs.rs/tracing/0.1.41/tracing/field/struct.Empty.html), "when a field’s value is `Empty`, it will not be recorded".  "not being recorded" means that the field and its value won't be printed to stderr text logs, nor will it be printed by any other tracing layers that might be attached in the future. In `tracing_chrome.rs` we would still be able to check if "tracing_separate_root" is in the list of static `fields`, and act accordingly. So I believe this solution would effectively allow us to attach metadata to a span in a way that does not clutter logs and still allows being read in `tracing_chrome.rs`.

If we ever wanted to pass arbitrary metadata (i.e. not just a present/not present flag), it would be possible with a custom `Empty` that also holds data and implement `Value` without doing anything ([like `Empty` does](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#775)).

```rust
// example of how the above solution would look like
info_span!("frame", tracing_separate_root = tracing::field::Empty, "{}", instance);
```
</details>
github-actions bot pushed a commit that referenced this pull request Jul 18, 2025
Make frame spans appear on a separate trace line

This PR changes tracing_chrome's `tracing::Layer` so that if a span has the "tracing_separate_line" field as one of the span arguments, that span is put on a separate trace line. See #4451 for an earlier attempt and for screenshots explaining better what I mean by "separate trace line".

This PR also makes the "frame" span use this feature (so it appears on a separate trace line, see #4451 for motivation), but passes `tracing::field::Empty` as the span parameter value so it is ignored by other tracing layers (e.g. the logger):

```rust
info_span!("frame", tracing_separate_line = Empty, "{}", instance);
```

<details><summary>Also see the following discussion I had with ``@RalfJung</summary>``

> Is there no way to attach metadata we could use instead?

[These](https://docs.rs/tracing-core/0.1.34/src/tracing_core/metadata.rs.html#57) are the **static** metadata items we can control about a span. We can't add more metadata outside of them. The most relevant are:
- `name` (for the frame span it's currently "frame")
- `target` which acts as the category (for the frame span it's currently "rustc_const_eval::interpret::stack" by default)
- `fields` which contains a list of the *names* of each of the arguments passed to the `span!` macro (for the frame span it's currently ["message"], where "message" is the default identifier for data passed in the `format!` syntax)

When the tracing code is called at runtime, the **dynamic** values of the arguments are collected into a [`ValueSet`](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#166). Each argument value stored there corresponds with one of the static names stored in `fields` (see above).

---

We have already determined that filtering out spans by `name` is not a good idea, and I would say the same goes for `target`. Both the `name` and the `target` fields are printed to stderr when `MIRI_LOG=` is enabled, so changing them to contain an identifier (e.g. "frame:tracing_separate_root" instead of "frame" as the name) would uselessly clutter the text logs (unless we add one more filter [there](https://github.com/rust-lang/rust/blob/master/compiler/rustc_log/src/lib.rs#L137), but then it gets even more complicated).

```rust
// examples of how the above (problematic) solutions would look like
info_span!("frame:tracing_separate_root", "{}", instance);
info_span!(target: "tracing_separate_root", "frame", "{}", instance);
```

---

So that leaves us with `fields` and their runtime values. Now, my initial thought (inspired by [this comment](#4451 (comment))) was to use a field with the static name "tracing_separate_root" and with a dynamic boolean value of "true". In `tracing_chrome.rs` we can easily check if this field is true and act accordingly. This would work but then again this field would also be picked up by the logger when `MIRI_LOG=` is enabled, and would uselessly clutter the text logs.

```rust
// example of how the above (problematic) solution would look like
info_span!("frame", tracing_separate_root = true, "{}", instance);
```

---

To avoid cluttering the text logs, we can instead set "tracing_separate_root" to the dynamic value of `tracing::field::Empty`. Citing from [here](https://docs.rs/tracing/0.1.41/tracing/field/struct.Empty.html), "when a field’s value is `Empty`, it will not be recorded".  "not being recorded" means that the field and its value won't be printed to stderr text logs, nor will it be printed by any other tracing layers that might be attached in the future. In `tracing_chrome.rs` we would still be able to check if "tracing_separate_root" is in the list of static `fields`, and act accordingly. So I believe this solution would effectively allow us to attach metadata to a span in a way that does not clutter logs and still allows being read in `tracing_chrome.rs`.

If we ever wanted to pass arbitrary metadata (i.e. not just a present/not present flag), it would be possible with a custom `Empty` that also holds data and implement `Value` without doing anything ([like `Empty` does](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#775)).

```rust
// example of how the above solution would look like
info_span!("frame", tracing_separate_root = tracing::field::Empty, "{}", instance);
```
</details>
Muscraft pushed a commit to Muscraft/rust that referenced this pull request Jul 21, 2025
…fJung

Make frame spans appear on a separate trace line

This PR changes tracing_chrome's `tracing::Layer` so that if a span has the "tracing_separate_line" field as one of the span arguments, that span is put on a separate trace line. See rust-lang/miri#4451 for an earlier attempt and for screenshots explaining better what I mean by "separate trace line".

This PR also makes the "frame" span use this feature (so it appears on a separate trace line, see rust-lang/miri#4451 for motivation), but passes `tracing::field::Empty` as the span parameter value so it is ignored by other tracing layers (e.g. the logger):

```rust
info_span!("frame", tracing_separate_line = Empty, "{}", instance);
```

<details><summary>Also see the following discussion I had with ``@RalfJung</summary>``

> Is there no way to attach metadata we could use instead?

[These](https://docs.rs/tracing-core/0.1.34/src/tracing_core/metadata.rs.html#57) are the **static** metadata items we can control about a span. We can't add more metadata outside of them. The most relevant are:
- `name` (for the frame span it's currently "frame")
- `target` which acts as the category (for the frame span it's currently "rustc_const_eval::interpret::stack" by default)
- `fields` which contains a list of the *names* of each of the arguments passed to the `span!` macro (for the frame span it's currently ["message"], where "message" is the default identifier for data passed in the `format!` syntax)

When the tracing code is called at runtime, the **dynamic** values of the arguments are collected into a [`ValueSet`](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#166). Each argument value stored there corresponds with one of the static names stored in `fields` (see above).

---

We have already determined that filtering out spans by `name` is not a good idea, and I would say the same goes for `target`. Both the `name` and the `target` fields are printed to stderr when `MIRI_LOG=` is enabled, so changing them to contain an identifier (e.g. "frame:tracing_separate_root" instead of "frame" as the name) would uselessly clutter the text logs (unless we add one more filter [there](https://github.com/rust-lang/rust/blob/master/compiler/rustc_log/src/lib.rs#L137), but then it gets even more complicated).

```rust
// examples of how the above (problematic) solutions would look like
info_span!("frame:tracing_separate_root", "{}", instance);
info_span!(target: "tracing_separate_root", "frame", "{}", instance);
```

---

So that leaves us with `fields` and their runtime values. Now, my initial thought (inspired by [this comment](rust-lang/miri#4451 (comment))) was to use a field with the static name "tracing_separate_root" and with a dynamic boolean value of "true". In `tracing_chrome.rs` we can easily check if this field is true and act accordingly. This would work but then again this field would also be picked up by the logger when `MIRI_LOG=` is enabled, and would uselessly clutter the text logs.

```rust
// example of how the above (problematic) solution would look like
info_span!("frame", tracing_separate_root = true, "{}", instance);
```

---

To avoid cluttering the text logs, we can instead set "tracing_separate_root" to the dynamic value of `tracing::field::Empty`. Citing from [here](https://docs.rs/tracing/0.1.41/tracing/field/struct.Empty.html), "when a field’s value is `Empty`, it will not be recorded".  "not being recorded" means that the field and its value won't be printed to stderr text logs, nor will it be printed by any other tracing layers that might be attached in the future. In `tracing_chrome.rs` we would still be able to check if "tracing_separate_root" is in the list of static `fields`, and act accordingly. So I believe this solution would effectively allow us to attach metadata to a span in a way that does not clutter logs and still allows being read in `tracing_chrome.rs`.

If we ever wanted to pass arbitrary metadata (i.e. not just a present/not present flag), it would be possible with a custom `Empty` that also holds data and implement `Value` without doing anything ([like `Empty` does](https://docs.rs/tracing-core/0.1.34/src/tracing_core/field.rs.html#775)).

```rust
// example of how the above solution would look like
info_span!("frame", tracing_separate_root = tracing::field::Empty, "{}", instance);
```
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: Waiting for the PR author to address review comments
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants