Skip to content

Commit eb6587c

Browse files
Rollup merge of rust-lang#136924 - Kobzol:bootstrap-tracing, r=jieyouxu
Add profiling of bootstrap commands using Chrome events Since we now have support for tracing in bootstrap, and the execution of most commands is centralized within a few functions, it's quite trivial to also trace command execution, and visualize it using the Chrome profiler. This can be helpful both to profile what takes time in bootstrap and also to get a visual idea of what happens in a given bootstrap invocation (since the execution of external commands is usually the most interesting thing). This is how it looks: ![image](https://github.com/user-attachments/assets/3351489e-3a0f-4729-9082-5bf40c586d4b) I first tried to use [tracing-flame](https://github.com/tokio-rs/tracing/tree/master/tracing-flame), but the output wasn't very useful, because the event/stackframe names were bootstrap code locations, instead of the command contents. r? `@jieyouxu`
2 parents c7b4e2e + 521cbd3 commit eb6587c

File tree

9 files changed

+84
-4
lines changed

9 files changed

+84
-4
lines changed

src/bootstrap/Cargo.lock

+12
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ dependencies = [
5959
"termcolor",
6060
"toml",
6161
"tracing",
62+
"tracing-chrome",
6263
"tracing-subscriber",
6364
"tracing-tree",
6465
"walkdir",
@@ -727,6 +728,17 @@ dependencies = [
727728
"syn",
728729
]
729730

731+
[[package]]
732+
name = "tracing-chrome"
733+
version = "0.7.2"
734+
source = "registry+https://github.com/rust-lang/crates.io-index"
735+
checksum = "bf0a738ed5d6450a9fb96e86a23ad808de2b727fd1394585da5cdd6788ffe724"
736+
dependencies = [
737+
"serde_json",
738+
"tracing-core",
739+
"tracing-subscriber",
740+
]
741+
730742
[[package]]
731743
name = "tracing-core"
732744
version = "0.1.33"

src/bootstrap/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default-run = "bootstrap"
77

88
[features]
99
build-metrics = ["sysinfo"]
10-
tracing = ["dep:tracing", "dep:tracing-subscriber", "dep:tracing-tree"]
10+
tracing = ["dep:tracing", "dep:tracing-chrome", "dep:tracing-subscriber", "dep:tracing-tree"]
1111

1212
[lib]
1313
path = "src/lib.rs"
@@ -67,6 +67,7 @@ sysinfo = { version = "0.33.0", default-features = false, optional = true, featu
6767

6868
# Dependencies needed by the `tracing` feature
6969
tracing = { version = "0.1", optional = true, features = ["attributes"] }
70+
tracing-chrome = { version = "0.7", optional = true }
7071
tracing-subscriber = { version = "0.3", optional = true, features = ["env-filter", "fmt", "registry", "std"] }
7172
tracing-tree = { version = "0.4.0", optional = true }
7273

src/bootstrap/src/bin/main.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tracing::instrument;
2121
#[cfg_attr(feature = "tracing", instrument(level = "trace", name = "main"))]
2222
fn main() {
2323
#[cfg(feature = "tracing")]
24-
setup_tracing();
24+
let _guard = setup_tracing();
2525

2626
let args = env::args().skip(1).collect::<Vec<_>>();
2727

@@ -210,15 +210,25 @@ fn check_version(config: &Config) -> Option<String> {
210210
// - `tracing`'s `#[instrument(..)]` macro will need to be gated like `#![cfg_attr(feature =
211211
// "tracing", instrument(..))]`.
212212
#[cfg(feature = "tracing")]
213-
fn setup_tracing() {
213+
fn setup_tracing() -> impl Drop {
214214
use tracing_subscriber::EnvFilter;
215215
use tracing_subscriber::layer::SubscriberExt;
216216

217217
let filter = EnvFilter::from_env("BOOTSTRAP_TRACING");
218218
// cf. <https://docs.rs/tracing-tree/latest/tracing_tree/struct.HierarchicalLayer.html>.
219219
let layer = tracing_tree::HierarchicalLayer::default().with_targets(true).with_indent_amount(2);
220220

221-
let registry = tracing_subscriber::registry().with(filter).with(layer);
221+
let mut chrome_layer = tracing_chrome::ChromeLayerBuilder::new().include_args(true);
222+
223+
// Writes the Chrome profile to trace-<unix-timestamp>.json if enabled
224+
if !env::var("BOOTSTRAP_PROFILE").is_ok_and(|v| v == "1") {
225+
chrome_layer = chrome_layer.writer(io::sink());
226+
}
227+
228+
let (chrome_layer, _guard) = chrome_layer.build();
229+
230+
let registry = tracing_subscriber::registry().with(filter).with(layer).with(chrome_layer);
222231

223232
tracing::subscriber::set_global_default(registry).unwrap();
233+
_guard
224234
}

src/bootstrap/src/core/build_steps/compile.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2234,6 +2234,10 @@ pub fn stream_cargo(
22342234
cb: &mut dyn FnMut(CargoMessage<'_>),
22352235
) -> bool {
22362236
let mut cmd = cargo.into_cmd();
2237+
2238+
#[cfg(feature = "tracing")]
2239+
let _run_span = crate::trace_cmd!(cmd);
2240+
22372241
let cargo = cmd.as_command_mut();
22382242
// Instruct Cargo to give us json messages on stdout, critically leaving
22392243
// stderr as piped so we can get those pretty colors.

src/bootstrap/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ impl Build {
905905
return CommandOutput::default();
906906
}
907907

908+
#[cfg(feature = "tracing")]
909+
let _run_span = trace_cmd!(command);
910+
908911
let created_at = command.get_created_location();
909912
let executed_at = std::panic::Location::caller();
910913

src/bootstrap/src/utils/exec.rs

+22
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,25 @@ impl Default for CommandOutput {
329329
}
330330
}
331331
}
332+
333+
/// Helper trait to format both Command and BootstrapCommand as a short execution line,
334+
/// without all the other details (e.g. environment variables).
335+
#[allow(unused)]
336+
pub trait FormatShortCmd {
337+
fn format_short_cmd(&self) -> String;
338+
}
339+
340+
impl FormatShortCmd for BootstrapCommand {
341+
fn format_short_cmd(&self) -> String {
342+
self.command.format_short_cmd()
343+
}
344+
}
345+
346+
impl FormatShortCmd for Command {
347+
fn format_short_cmd(&self) -> String {
348+
let program = Path::new(self.get_program());
349+
let mut line = vec![program.file_name().unwrap().to_str().unwrap()];
350+
line.extend(self.get_args().map(|arg| arg.to_str().unwrap()));
351+
line.join(" ")
352+
}
353+
}

src/bootstrap/src/utils/helpers.rs

+3
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ pub fn make(host: &str) -> PathBuf {
265265

266266
#[track_caller]
267267
pub fn output(cmd: &mut Command) -> String {
268+
#[cfg(feature = "tracing")]
269+
let _run_span = crate::trace_cmd!(cmd);
270+
268271
let output = match cmd.stderr(Stdio::inherit()).output() {
269272
Ok(status) => status,
270273
Err(e) => fail(&format!("failed to execute command: {cmd:?}\nERROR: {e}")),

src/bootstrap/src/utils/tracing.rs

+17
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,20 @@ macro_rules! error {
4747
::tracing::error!($($tokens)*)
4848
}
4949
}
50+
51+
#[macro_export]
52+
macro_rules! trace_cmd {
53+
($cmd:expr) => {
54+
{
55+
use $crate::utils::exec::FormatShortCmd;
56+
57+
::tracing::span!(
58+
target: "COMMAND",
59+
::tracing::Level::TRACE,
60+
"executing command",
61+
cmd = $cmd.format_short_cmd(),
62+
full_cmd = ?$cmd
63+
).entered()
64+
}
65+
};
66+
}

src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md

+8
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ For `#[instrument]`, it's recommended to:
121121
- Explicitly pick an instrumentation name via `name = ".."` to distinguish between e.g. `run` of different steps.
122122
- Take care to not cause diverging behavior via tracing, e.g. building extra things only when tracing infra is enabled.
123123

124+
### Profiling bootstrap
125+
126+
You can use the `COMMAND` tracing target to trace execution of most commands spawned by bootstrap. If you also use the `BOOTSTRAP_PROFILE=1` environment variable, bootstrap will generate a Chrome JSON trace file, which can be visualized in Chrome's `chrome://tracing` page or on https://ui.perfetto.dev.
127+
128+
```bash
129+
$ BOOTSTRAP_TRACING=COMMAND=trace BOOTSTRAP_PROFILE=1 ./x build library
130+
```
131+
124132
### rust-analyzer integration?
125133

126134
Unfortunately, because bootstrap is a `rust-analyzer.linkedProjects`, you can't ask r-a to check/build bootstrap itself with `tracing` feature enabled to get relevant completions, due to lack of support as described in <https://github.com/rust-lang/rust-analyzer/issues/8521>.

0 commit comments

Comments
 (0)