Skip to content

feat: run results report CPU time and per-call bridge records from the runtime - #40

Merged
schplitt merged 1 commit into
mainfrom
feat/bridge-call-report
Jul 25, 2026
Merged

feat: run results report CPU time and per-call bridge records from the runtime#40
schplitt merged 1 commit into
mainfrom
feat/bridge-call-report

Conversation

@schplitt

Copy link
Copy Markdown
Owner

What

Every RunResult (completed / failed / aborted) now reports what the run actually did, measured inside the Rust runtime:

result.durationMs   // wall-clock time of the run (unchanged, now documented as such)
result.cpuTimeMs    // NEW — active V8 execution time, bridge waits excluded (µs resolution)
result.bridgeCalls  // NEW — one record per bridge call attempt, in attempt order:
// [{ name: 'fetch', startMs: 0.4, durationMs: 2.3, argBytes: 180, responseBytes: 41208, ok: true, blocked: false }, …]
  • Names are resolved: plain globals as-is, shim stubs under their public name, host-module import leaves as <specifier>.<path> (e.g. tools:search.query).
  • Blocked attempts are visible: calls rejected by maxBridgeCalls/maxBridgeCallBytes/function-argument checks get a record with blocked: true — they never reached the host, but the report shows exactly which attempt crossed the limit.
  • No payloads, ever — names, timing, and sizes only, so the report is cheap enough to stay always-on.
  • One clock: record offsets/durations share the run's clock with durationMs, so entries line up on a timeline; durationMs − cpuTimeMs ≈ bridge waits + scheduling.

Where the data comes from

Recording lives in the Rust runtime (bridge callback records the attempt; the poll loop settles it when the response frame returns). The TS client only decodes the Result frame and maps wire-level stub names to public names — the client measures nothing. cpuTimeMs reads the existing CPU budget through a new µs-resolution accessor (the integer-ms one only fed the 10ms guard and would report 0 for sub-ms runs).

Wire: result frames gain f64 cpuTimeMs + List<BridgeCallRecord> (~40 bytes/record); docs/protocol.md §5.6 documents the layout.

Known gap

Aborted runs report cpuTimeMs: 0 and bridgeCalls: [] — the abort tears the connection down before the runtime can send its result frame. Tracked in #36 (graceful terminate), which matters most for durable-isolates suspensions.

Testing

  • Rust (207): records on success incl. metadata/order, blocked record at the limit, CPU ≤ wall, wire encoder layout.
  • TS (453): wire decode incl. name resolution + blocked records; e2e: entry order/metadata, cpuTimeMs excludes a 150ms handler wait, shim + import name resolution, handler errors (ok: false), failed runs keep the report, blocked entries at the limit, aborted-empty, prefix.run().
  • typecheck, cargo fmt, eslint clean on changed files.

…e runtime

Every RunResult now carries cpuTimeMs (active V8 execution, bridge waits
excluded — durationMs stays wall-clock) and bridgeCalls: one metadata
record per bridge call attempt, recorded inside the Rust runtime on the
run's own clock. Records carry the resolved name, start offset,
round-trip duration, argument/response byte sizes, and outcome; attempts
blocked by maxBridgeCalls/maxBridgeCallBytes are included with
blocked: true. Payloads are never captured.

The TS client only decodes and maps wire-level stub names to public
names (__iso4_call + handle → specifier.path, shim stubs → public name)
— measurement lives runtime-side so any client gets the same data.

Aborted runs report zeros and an empty list for now: the abort teardown
precedes the runtime's result frame. Graceful termination (#36) will
close that gap.
@schplitt
schplitt merged commit 4126420 into main Jul 25, 2026
1 check passed
@schplitt
schplitt deleted the feat/bridge-call-report branch July 25, 2026 17:16
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.

1 participant