Skip to content

Commit 34e1e75

Browse files
committed
perf: Enable chrome tracing at shim startup for complete profiles
The profile was still missing ~500ms because chrome tracing was only enabled inside the Command::Run match arm, after clap parsing, config resolution, and repo inference had already completed. This extracts --profile/--anon-profile from the shim's argument parser and enables chrome tracing immediately after TurboSubscriber creation, before any repo inference or CLI parsing occurs. Also instruments Args::new, get_command, initialize_telemetry_client, and run_with_args for full startup visibility.
1 parent 3746764 commit 34e1e75

File tree

5 files changed

+111
-11
lines changed

5 files changed

+111
-11
lines changed

crates/turborepo-lib/src/cli/mod.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ fn format_error_message(mut err_str: String) -> String {
300300
}
301301

302302
impl Args {
303+
#[tracing::instrument(skip_all)]
303304
pub fn new(os_args: Vec<OsString>) -> Self {
304305
let clap_args = match Args::parse(os_args) {
305306
Ok(args) => args,
@@ -1137,6 +1138,7 @@ impl RunArgs {
11371138
}
11381139
}
11391140

1141+
#[tracing::instrument(skip_all)]
11401142
fn initialize_telemetry_client(
11411143
color_config: ColorConfig,
11421144
version: &str,
@@ -1262,6 +1264,7 @@ fn default_to_run_command(cli_args: &Args) -> Result<Command, Error> {
12621264
})
12631265
}
12641266

1267+
#[tracing::instrument(skip_all)]
12651268
fn get_command(cli_args: &mut Args) -> Result<Command, Error> {
12661269
if let Some(command) = mem::take(&mut cli_args.command) {
12671270
Ok(command)
@@ -1587,13 +1590,6 @@ pub async fn run(
15871590
let event = CommandEventBuilder::new("run").with_parent(&root_telemetry);
15881591
event.track_call();
15891592

1590-
// Enable chrome tracing before any real work so that config
1591-
// resolution and CommandBase construction are captured.
1592-
let profile_file = run_args.profile_file_and_include_args();
1593-
if let Some((ref file_path, include_args)) = profile_file {
1594-
let _ = logger.enable_chrome_tracing(file_path, include_args);
1595-
}
1596-
15971593
let base = CommandBase::new(cli_args.clone(), repo_root, version, color_config)?;
15981594
event.track_ui_mode(base.opts.run_opts.ui_mode);
15991595

@@ -1609,14 +1605,14 @@ pub async fn run(
16091605
}
16101606
})?;
16111607

1612-
if let Some((ref file_path, _)) = profile_file {
1613-
// Flush the chrome trace so the file is fully written
1614-
// before we read it for markdown generation.
1608+
// Chrome tracing is enabled early in shim::run(). Here we just
1609+
// flush and generate the markdown summary.
1610+
if let Some(file_path) = logger.chrome_tracing_file() {
16151611
let _ = logger.flush_chrome_tracing();
16161612

16171613
let md_path = format!("{file_path}.md");
16181614
if let Err(e) = turborepo_profile_md::trace_to_markdown(
1619-
std::path::Path::new(file_path),
1615+
std::path::Path::new(&file_path),
16201616
std::path::Path::new(&md_path),
16211617
) {
16221618
warn!("Failed to generate profile markdown: {e}");

crates/turborepo-lib/src/shim.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ pub fn run() -> Result<i32, Error> {
144144
let color_config = args.color_config();
145145
let subscriber = TurboSubscriber::new_with_verbosity(args.verbosity, &color_config);
146146

147+
// Enable chrome tracing as early as possible so that repo inference,
148+
// config resolution, and CLI parsing are all captured in profiles.
149+
if let Some((ref file_path, include_args)) = args.profile_file_and_include_args() {
150+
let _ = subscriber.enable_chrome_tracing(file_path, include_args);
151+
}
152+
147153
// Create the runtime with all implementations
148154
let runtime = ShimRuntime::new(
149155
TurboCliRunner::new(&subscriber),

crates/turborepo-lib/src/tracing.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ pub struct TurboSubscriber {
8080
chrome_update: Handle<Option<ChromeLog>, DaemonLogLayered>,
8181
chrome_guard: Mutex<Option<tracing_chrome::FlushGuard>>,
8282

83+
/// The resolved file path for chrome tracing output, if enabled.
84+
chrome_tracing_file: Mutex<Option<String>>,
85+
8386
#[cfg(feature = "pprof")]
8487
pprof_guard: pprof::ProfilerGuard<'static>,
8588
}
@@ -158,6 +161,7 @@ impl TurboSubscriber {
158161
daemon_guard: Mutex::new(None),
159162
chrome_update,
160163
chrome_guard: Mutex::new(None),
164+
chrome_tracing_file: Mutex::new(None),
161165
#[cfg(feature = "pprof")]
162166
pprof_guard,
163167
}
@@ -191,6 +195,8 @@ impl TurboSubscriber {
191195
to_file: P,
192196
include_args: bool,
193197
) -> Result<(), Error> {
198+
let file_path = to_file.as_ref().to_string_lossy().to_string();
199+
194200
let (layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
195201
.file(to_file)
196202
.include_args(include_args)
@@ -203,10 +209,23 @@ impl TurboSubscriber {
203209
.lock()
204210
.expect("not poisoned")
205211
.replace(guard);
212+
self.chrome_tracing_file
213+
.lock()
214+
.expect("not poisoned")
215+
.replace(file_path);
206216

207217
Ok(())
208218
}
209219

220+
/// Returns the chrome tracing output file path, if chrome tracing is
221+
/// enabled.
222+
pub fn chrome_tracing_file(&self) -> Option<String> {
223+
self.chrome_tracing_file
224+
.lock()
225+
.expect("not poisoned")
226+
.clone()
227+
}
228+
210229
/// Flushes and closes the chrome tracing layer so the trace file is
211230
/// fully written. This must be called before reading the trace file
212231
/// for post-processing (e.g., markdown generation).

crates/turborepo-shim/src/parser.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ pub struct ShimArgs {
8181
pub color: bool,
8282
pub no_color: bool,
8383
pub root_turbo_json: Option<AbsoluteSystemPathBuf>,
84+
/// Raw value from `--profile` (Some("") means flag present with no value).
85+
pub profile: Option<String>,
86+
/// Raw value from `--anon-profile` (Some("") means flag present with no
87+
/// value).
88+
pub anon_profile: Option<String>,
8489
}
8590

8691
impl ShimArgs {
@@ -106,6 +111,10 @@ impl ShimArgs {
106111
let mut no_color = false;
107112
let mut root_turbo_json_flag_idx = None;
108113
let mut root_turbo_json = None;
114+
let mut profile: Option<String> = None;
115+
let mut anon_profile: Option<String> = None;
116+
let mut found_profile_flag = false;
117+
let mut found_anon_profile_flag = false;
109118

110119
let args = args.skip(1);
111120
for (idx, arg) in args.enumerate() {
@@ -170,6 +179,40 @@ impl ShimArgs {
170179
// `--root-turbo-json=./path/to/turbo.json`, that entire chunk
171180
// is a single arg, so we need to split it up.
172181
root_turbo_json = Some(AbsoluteSystemPathBuf::from_unknown(&invocation_dir, path));
182+
} else if found_profile_flag {
183+
// Previous arg was `--profile`, this arg is the optional value.
184+
// If it looks like a flag, it's not our value — treat `--profile`
185+
// as having no value and re-process this arg next iteration.
186+
found_profile_flag = false;
187+
if arg.starts_with('-') {
188+
profile = Some(String::new());
189+
// Re-process this arg: push it to remaining for clap
190+
remaining_turbo_args.push(arg);
191+
} else {
192+
profile = Some(arg.clone());
193+
remaining_turbo_args.push(arg);
194+
}
195+
} else if found_anon_profile_flag {
196+
found_anon_profile_flag = false;
197+
if arg.starts_with('-') {
198+
anon_profile = Some(String::new());
199+
remaining_turbo_args.push(arg);
200+
} else {
201+
anon_profile = Some(arg.clone());
202+
remaining_turbo_args.push(arg);
203+
}
204+
} else if arg == "--profile" {
205+
remaining_turbo_args.push(arg);
206+
found_profile_flag = true;
207+
} else if let Some(value) = arg.strip_prefix("--profile=") {
208+
profile = Some(value.to_string());
209+
remaining_turbo_args.push(arg);
210+
} else if arg == "--anon-profile" {
211+
remaining_turbo_args.push(arg);
212+
found_anon_profile_flag = true;
213+
} else if let Some(value) = arg.strip_prefix("--anon-profile=") {
214+
anon_profile = Some(value.to_string());
215+
remaining_turbo_args.push(arg);
173216
} else if arg == "--debug" {
174217
return Err(Error::UnsupportedFlag {
175218
flag: "--debug".to_string(),
@@ -189,6 +232,14 @@ impl ShimArgs {
189232
}
190233
}
191234

235+
// If --profile or --anon-profile was the last arg, treat as no value
236+
if found_profile_flag {
237+
profile = Some(String::new());
238+
}
239+
if found_anon_profile_flag {
240+
anon_profile = Some(String::new());
241+
}
242+
192243
if let Some(idx) = cwd_flag_idx {
193244
let (spans, args_string) =
194245
Self::get_spans_in_args_string(vec![idx], env::args().skip(1));
@@ -241,9 +292,34 @@ impl ShimArgs {
241292
color,
242293
no_color,
243294
root_turbo_json,
295+
profile,
296+
anon_profile,
244297
})
245298
}
246299

300+
/// Returns the resolved profile file path and whether to include args
301+
/// (true for `--profile`, false for `--anon-profile`).
302+
pub fn profile_file_and_include_args(&self) -> Option<(String, bool)> {
303+
let resolve = |file: &str| -> String {
304+
if file.is_empty() {
305+
let now = std::time::SystemTime::now()
306+
.duration_since(std::time::UNIX_EPOCH)
307+
.expect("system clock is before unix epoch")
308+
.as_millis();
309+
format!("profile.{now}")
310+
} else {
311+
file.to_string()
312+
}
313+
};
314+
315+
match (self.profile.as_deref(), self.anon_profile.as_deref()) {
316+
(Some(file), None) => Some((resolve(file), true)),
317+
(None, Some(file)) => Some((resolve(file), false)),
318+
// Both set should be caught by clap later; just ignore here.
319+
_ => None,
320+
}
321+
}
322+
247323
/// Takes a list of indices into a Vec of arguments, i.e. ["--graph", "foo",
248324
/// "--cwd"] and converts them into `SourceSpan`'s into the string of those
249325
/// arguments, i.e. "-- graph foo --cwd". Returns the spans and the args
@@ -401,6 +477,8 @@ mod test {
401477
no_color,
402478
root_turbo_json: relative_root_turbo_json
403479
.map(|path| AbsoluteSystemPathBuf::from_unknown(invocation_dir, path)),
480+
profile: None,
481+
anon_profile: None,
404482
}
405483
}
406484
}

crates/turborepo-shim/src/run.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ where
183183
///
184184
/// A `ShimResult` containing either the exit code, a shim error, or a CLI
185185
/// error.
186+
#[tracing::instrument(skip_all)]
186187
pub fn run_with_args<R, C, S, V>(
187188
runtime: &ShimRuntime<R, C, S, V>,
188189
args: ShimArgs,

0 commit comments

Comments
 (0)