Skip to content

Add StartupTimings per-phase breakdown to Client::start#2066

Open
jmoseley wants to merge 1 commit into
mainfrom
jmoseley-ttft-sdk-seams
Open

Add StartupTimings per-phase breakdown to Client::start#2066
jmoseley wants to merge 1 commit into
mainfrom
jmoseley-ttft-sdk-seams

Conversation

@jmoseley

Copy link
Copy Markdown
Contributor

Summary

Adds a StartupTimings struct to the Rust SDK that decomposes the CLI spawn + handshake cost of Client::start into per-phase millisecond fields. This is Phase 1 of a cross-repo effort to reduce and instrument time to first token (TTFT): the github-app host currently only measures the total Client::start(config).await duration and can't attribute cold-start latency to a specific phase.

What's included

New module rust/src/startup_timings.rs with a #[non_exhaustive] public StartupTimings struct. Fields (all Option<u64>, whole milliseconds):

Field Phase
program_resolve_ms NEW timer around bundled-CLI resolution/extraction (resolve::copilot_binary_with_extract_dir) — previously completely untimed, and the prime suspect for the large Windows cold-start cost
process_spawn_ms subprocess command.spawn()
port_wait_ms TCP port-announcement wait (TCP transport only)
handshake_ms verify_protocol_version connect round-trip
session_fs_ms sessionFs.setProvider RPC (when configured)
llm_handler_ms llmInference.setProvider RPC (when configured)
total_ms full Client::start wall-clock

Each field is Some only when its phase actually runs (e.g. program_resolve_ms is None when the caller passes an explicit CLI path; port_wait_ms is Some only on the TCP transport).

Surfacing (non-breaking)

  • Timings are stored on ClientInner via a OnceLock and exposed through a new getter Client::startup_timings() -> Option<StartupTimings>.
  • A single structured debug!("Client::start timings", …) event carries the full breakdown, so hosts don't have to stitch together the individual per-phase debug lines.
  • Client::start's return type is unchanged — no breaking API change for any Rust consumer. Existing per-phase debug logs are retained.

spawn_stdio / spawn_tcp signatures now return their measured Duration(s) to the caller; these are private helpers, so no public surface changes.

Validation

  • cargo +nightly-2026-04-14 fmt --check — clean
  • cargo clippy --all-features --all-targets -- -D warnings — clean
  • cargo test --lib — 200/200 pass (incl. 2 new unit tests)
  • cargo test --all-features --test e2e389/389 pass

Scope

SDK crate only. The github-app host will consume these after a vendored SDK bump (separate follow-up) — this PR does not touch github-app.

Introduce a `StartupTimings` struct that decomposes the CLI spawn +
handshake cost into per-phase millisecond fields, so hosts can attribute
"time to first token" startup latency to a specific phase instead of
reconstructing it from scattered debug lines.

Phases captured:
- program_resolve_ms: NEW timer around bundled-CLI resolution/extraction
  (`resolve::copilot_binary_with_extract_dir`), previously untimed and the
  prime suspect for cold-start cost on Windows.
- process_spawn_ms: subprocess `command.spawn()`.
- port_wait_ms: TCP port-announcement wait (tcp transport only).
- handshake_ms: `verify_protocol_version` connect round-trip.
- session_fs_ms / llm_handler_ms: post-handshake provider-registration RPCs.
- total_ms: full Client::start wall-clock.

Surfacing is non-breaking: timings are stored on ClientInner via a
`OnceLock` and exposed through a new `Client::startup_timings()` getter,
plus a single structured `debug!` event. `Client::start`'s return type is
unchanged. Existing per-phase debug logs are retained.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95834bd3-b7ac-40bc-8bf4-60102600d41a
Copilot AI review requested due to automatic review settings July 23, 2026 19:42
@jmoseley
jmoseley requested a review from a team as a code owner July 23, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds per-phase Rust SDK startup timing instrumentation for TTFT analysis.

Changes:

  • Introduces the public StartupTimings type.
  • Measures startup phases and exposes them through Client::startup_timings().
  • Emits a consolidated tracing event.
Show a summary per file
File Description
rust/src/startup_timings.rs Defines timing fields and duration conversion tests.
rust/src/lib.rs Captures, stores, logs, and exposes startup timings.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread rust/src/lib.rs
Comment on lines +1372 to +1376
program_resolve_ms = ?timings.program_resolve_ms,
process_spawn_ms = ?timings.process_spawn_ms,
port_wait_ms = ?timings.port_wait_ms,
handshake_ms = ?timings.handshake_ms,
session_fs_ms = ?timings.session_fs_ms,
Comment thread rust/src/lib.rs
total_ms = ?timings.total_ms,
"Client::start timings"
);
let _ = client.inner.startup_timings.set(timings);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants