Skip to content

Commit 0d43e2f

Browse files
authored
Merge pull request #4406 from Stypox/tracing
Add tracing_chrome under "tracing" feature
2 parents 0234984 + d2f2271 commit 0d43e2f

File tree

9 files changed

+828
-102
lines changed

9 files changed

+828
-102
lines changed

src/tools/miri/CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ compiler that has `debug=true` set in `bootstrap.toml`.
158158
You can set `MIRI_BACKTRACE=1` to get a backtrace of where an
159159
evaluation error was originally raised.
160160

161+
#### Tracing
162+
163+
You can generate a Chrome trace file from a Miri execution by passing `--features=tracing` during the
164+
build and then setting `MIRI_TRACING=1` when running Miri. This will generate a `.json` file that
165+
you can visualize in [Perfetto](https://ui.perfetto.dev/). For example:
166+
167+
```sh
168+
MIRI_TRACING=1 ./miri run --features=tracing tests/pass/hello.rs
169+
```
161170

162171
### UI testing
163172

src/tools/miri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ dependencies = [
627627
"regex",
628628
"rustc_version",
629629
"serde",
630+
"serde_json",
630631
"smallvec",
631632
"tempfile",
632633
"tikv-jemalloc-sys",

src/tools/miri/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ chrono = { version = "0.4.38", default-features = false }
2727
chrono-tz = "0.10"
2828
directories = "6"
2929
bitflags = "2.6"
30+
serde_json = { version = "1.0", optional = true }
3031

3132
# Copied from `compiler/rustc/Cargo.toml`.
3233
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't
@@ -67,6 +68,7 @@ default = ["stack-cache"]
6768
genmc = []
6869
stack-cache = []
6970
stack-cache-consistency-check = ["stack-cache"]
71+
tracing = ["serde_json"]
7072

7173
[lints.rust.unexpected_cfgs]
7274
level = "warn"

src/tools/miri/rustfmt.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ imports_granularity = "Module"
88
force_multiline_blocks = true
99
match_arm_blocks = false
1010
match_arm_leading_pipes = "Preserve"
11+
12+
ignore = [
13+
# This file is copy-pasted from the tracing_chrome crate and should remain like the original.
14+
"src/bin/log/tracing_chrome.rs"
15+
]

src/tools/miri/src/bin/log/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod setup;
2+
mod tracing_chrome;

src/tools/miri/src/bin/log/setup.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use std::env::{self, VarError};
2+
use std::str::FromStr;
3+
use std::sync::{Mutex, OnceLock};
4+
5+
use rustc_middle::ty::TyCtxt;
6+
use rustc_session::{CtfeBacktrace, EarlyDiagCtxt};
7+
8+
/// The tracing layer from `tracing-chrome` starts a thread in the background that saves data to
9+
/// file and closes the file when stopped. If the thread is not stopped properly, the file will be
10+
/// missing end terminators (`]` for JSON arrays) and other data may also not be flushed. Therefore
11+
/// we need to keep a guard that, when [Drop]ped, will send a signal to stop the thread. Make sure
12+
/// to manually drop this guard using [deinit_loggers], if you are exiting the program with
13+
/// [std::process::exit]!
14+
#[must_use]
15+
struct TracingGuard {
16+
#[cfg(feature = "tracing")]
17+
_chrome: super::tracing_chrome::FlushGuard,
18+
_no_construct: (),
19+
}
20+
21+
// This ensures TracingGuard is always a drop-type, even when the `_chrome` field is disabled.
22+
impl Drop for TracingGuard {
23+
fn drop(&mut self) {}
24+
}
25+
26+
fn rustc_logger_config() -> rustc_log::LoggerConfig {
27+
// Start with the usual env vars.
28+
let mut cfg = rustc_log::LoggerConfig::from_env("RUSTC_LOG");
29+
30+
// Overwrite if MIRI_LOG is set.
31+
if let Ok(var) = env::var("MIRI_LOG") {
32+
// MIRI_LOG serves as default for RUSTC_LOG, if that is not set.
33+
if matches!(cfg.filter, Err(VarError::NotPresent)) {
34+
// We try to be a bit clever here: if `MIRI_LOG` is just a single level
35+
// used for everything, we only apply it to the parts of rustc that are
36+
// CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
37+
// This way, if you set `MIRI_LOG=trace`, you get only the right parts of
38+
// rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
39+
if tracing::Level::from_str(&var).is_ok() {
40+
cfg.filter = Ok(format!(
41+
"rustc_middle::mir::interpret={var},rustc_const_eval::interpret={var},miri={var}"
42+
));
43+
} else {
44+
cfg.filter = Ok(var);
45+
}
46+
}
47+
}
48+
49+
cfg
50+
}
51+
52+
/// The global logger can only be set once per process, so track whether that already happened and
53+
/// keep a [TracingGuard] so it can be [Drop]ped later using [deinit_loggers].
54+
static LOGGER_INITED: OnceLock<Mutex<Option<TracingGuard>>> = OnceLock::new();
55+
56+
fn init_logger_once(early_dcx: &EarlyDiagCtxt) {
57+
// If the logger is not yet initialized, initialize it.
58+
LOGGER_INITED.get_or_init(|| {
59+
let guard = if env::var_os("MIRI_TRACING").is_some() {
60+
#[cfg(not(feature = "tracing"))]
61+
{
62+
crate::fatal_error!(
63+
"fatal error: cannot enable MIRI_TRACING since Miri was not built with the \"tracing\" feature"
64+
);
65+
}
66+
67+
#[cfg(feature = "tracing")]
68+
{
69+
let (chrome_layer, chrome_guard) =
70+
super::tracing_chrome::ChromeLayerBuilder::new().include_args(true).build();
71+
rustc_driver::init_logger_with_additional_layer(
72+
early_dcx,
73+
rustc_logger_config(),
74+
|| {
75+
tracing_subscriber::layer::SubscriberExt::with(
76+
tracing_subscriber::Registry::default(),
77+
chrome_layer,
78+
)
79+
},
80+
);
81+
82+
Some(TracingGuard { _chrome: chrome_guard, _no_construct: () })
83+
}
84+
} else {
85+
// initialize the logger without any tracing enabled
86+
rustc_driver::init_logger(early_dcx, rustc_logger_config());
87+
None
88+
};
89+
Mutex::new(guard)
90+
});
91+
}
92+
93+
pub fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
94+
// We only initialize `rustc` if the env var is set (so the user asked for it).
95+
// If it is not set, we avoid initializing now so that we can initialize later with our custom
96+
// settings, and *not* log anything for what happens before `miri` starts interpreting.
97+
if env::var_os("RUSTC_LOG").is_some() {
98+
init_logger_once(early_dcx);
99+
}
100+
}
101+
102+
pub fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
103+
// If the logger is not yet initialized, initialize it.
104+
init_logger_once(early_dcx);
105+
106+
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
107+
// Do this late, so we ideally only apply this to Miri's errors.
108+
if let Some(val) = env::var_os("MIRI_BACKTRACE") {
109+
let ctfe_backtrace = match &*val.to_string_lossy() {
110+
"immediate" => CtfeBacktrace::Immediate,
111+
"0" => CtfeBacktrace::Disabled,
112+
_ => CtfeBacktrace::Capture,
113+
};
114+
*tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
115+
}
116+
}
117+
118+
/// Must be called before the program terminates to ensure the trace file is closed correctly. Not
119+
/// doing so will result in invalid trace files. Also see [TracingGuard].
120+
pub fn deinit_loggers() {
121+
if let Some(guard) = LOGGER_INITED.get()
122+
&& let Ok(mut guard) = guard.lock()
123+
{
124+
std::mem::drop(guard.take());
125+
}
126+
}

0 commit comments

Comments
 (0)