Skip to content

test(wsl): capture a CPU profile of orcad's startup after a distro restart - #35043

Merged
nwparker merged 1 commit into
mainfrom
nwparker/wsl-orcad-startup-profile
Oct 28, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/wsl-orcad-startup-profile

Conversation

@nwparker

@nwparker nwparker commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​39 $\color{#cf222e}{\Huge{\mathbf{−}}}$​3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​36
Prod 0 0 0 0

ELI5

The WSL host's server (orcad) takes about 1.5–1.9 s after a distro restart before its first timed startup step even begins. This PR has the real-WSL test record a CPU profile of that startup, so we can see where the time goes. It changes no shipped code.

What Changed

After the three measured restarts, the idle-release lane (wsl-orcad-real-distro-idle-release-lane.test.ts) runs one more restart, which is not recorded. orcad is launched with NODE_OPTIONS=--cpu-prof --cpu-prof-dir=/tmp/orca-lane-orcad-profiles, set through the lane's own transport orcadLaunchEnv, so the Node children orcad starts write profiles too. The lane then waits up to 2 min for orcad's idle exit, since profiles are only written on exit. It copies every .cpuprofile into the lane receipts artifact and lists their names under orcadStartupProfiles. The test timeout grows from 20 to 24 minutes. The job cap (40 min) already covers the ~13-min job.

Why

#35005's timing puts most of orcad's startup ahead of startOrcadRuntime's first step. A local profile of the same template on macOS finds two candidates:

  • a synchronous child-Node node-pty load probe (runOrcadNativePreflight), which blocks the event loop;
  • module compile and evaluation of the 11.8 MB server bundle.

The profile needs to come from the real cold-cache WSL restart before W3 changes startup order. The on-disk compile cache, idea (a) of W3, already exists: orcad-compile-cache.ts from #27923 keys it per version slot, fails open, and checks that enableCompileCache exists. So these restarts already run with it, and this profile will show what remains.

Flag-off: no change. This is test-only.
User-risk: none. Test only.

Linked Issue

Internal WSL-via-orcad tracker, D3 bar (restart → first usable prompt).

Visual Proof

N/A: test only.

Testing

pnpm tc, typecheck:ci, code-quality, anti-slop and ast-grep pass locally. The profiles come from this PR's WSL2 lane run.

  • Automated tests added/updated

Status

Pushed. Waiting for the lane artifacts.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • N/A for visuals with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote impact considered (lane only)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (CI runs the rest)

…start

After the three measured restarts, the idle-release lane runs one more, unrecorded restart with
orcad launched under --cpu-prof (through the lane's own launch env), waits for its idle exit and
copies the profiles of orcad and the Node children it started into the receipts.
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

The baseline test adds an Orcad restart with CPU profiling enabled. It waits up to 120 seconds for the launcher to exit, copies generated profile files into the receipt directory, and records their filenames. The test timeout increases from 20 to 24 minutes.

Priority: Low

Merge Risk: Moderate · up to 86112

The profiling lane can fail when run alone or report success without a usable startup profile. Fix the timeout and profile collection before relying on its receipts.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description explains the change, motivation, testing, scope, and visual-proof exception. However, the required Linked Issue section does not contain an actual issue reference or link; it only name… Add the actual linked issue reference in the Linked Issue section, such as Fixes #35005 or the repository’s supported issue URL format. Keep the existing explanation if the issue is internal, but provide a resolvable issue link or identif…
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: adding CPU profiling to the Orcad startup test after a WSL distro restart.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 1 files.

Full details: Description check

Explanation

The description explains the change, motivation, testing, scope, and visual-proof exception. However, the required Linked Issue section does not contain an actual issue reference or link; it only names an internal tracker item.

Resolution

Add the actual linked issue reference in the Linked Issue section, such as Fixes #35005 or the repository’s supported issue URL format. Keep the existing explanation if the issue is internal, but provide a resolvable issue link or identifier.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a8984ed4-c0c5-4f0f-b1b7-514d2fdba247
Commits

Reviewing files that changed from the base of the PR and between 4a9c126 and 86112d3.

Files selected for processing: 1
  • src/main/wsl/wsl-orcad-real-distro-idle-release-lane.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Comment on lines +428 to +432
await waitFor(
'the profiled orcad to idle-exit',
async () => ((await guest(serving)).trim() === '0' ? true : null),
120_000,
1000

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.

Minor · Functional Correctness · Quick win

Set the idle timeout for this test.

If this test runs alone, idleReleasePhase does not set ORCAD_E2E_IDLE_TIMEOUT_ENV. Orcad then uses its 15-minute default, so this 120-second wait times out. Set the test timeout independently of the preceding test. (raw.githubusercontent.com)

120_000,
1000
)
const names = (await guest(`ls ${PROFILE_DIR} 2>/dev/null || true`)).split('\n').filter(Boolean)

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.

Minor · Functional Correctness · Quick win

Fail when the Orcad profile is missing.

If Orcad exits without writing its profile, such as after a signal or a profile-write failure, this command returns an empty list and the test passes. Require a profile for the launched Orcad process before recording success. (raw.githubusercontent.com)

for (const name of names) {
writeFileSync(
join(RECEIPT_DIR, name),
await guest(`cat ${quotePosixShell(`${PROFILE_DIR}/${name}`)}`)

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.

Minor · Data Integrity & Integration · Quick win

Supported by static analysis

Script executed:

rg -n 'cpuprofile|writeFileSync.*guest|readFile.*ssh|execCommand.*offset|base64.*head -c' src/main/wsl config/scripts tests | head -100
sed -n '130,165p' src/main/wsl/wsl-orcad-real-distro-idle-release-lane.test.ts
sed -n '430,446p' src/main/wsl/wsl-orcad-real-distro-idle-release-lane.test.ts

Repository: stablyai/orca

Length of output: 3595


Script executed:

# Output limit path and execCommand options
rg -n -F -e 'MAX_EXEC_OUTPUT_CHARS' -e 'appendExecOutputTail' -e 'export async function execCommand' -e 'maxOutputChars' -e 'outputLimit' src/main/ssh/remote-exec/remote-exec-command.ts || test "$?" -eq 1
sed -n '1,60p' src/main/ssh/remote-exec/remote-exec-command.ts
sed -n '240,320p' src/main/ssh/remote-exec/remote-exec-command.ts

# WSL transport surface: any read, copy, or file-transfer primitive
rg -n -F -e 'createWslOrcadHostTransport' -e 'readFile' -e 'copyFile' -e 'exec(' -e 'execWithClosedStdin' src/main/wsl | head -80 || test "$?" -eq 1

# Test lane: profile directory, flags, and the copy loop
rg -n -F -e 'PROFILE_DIR' -e 'cpu-prof' -e 'orcadStartupProfiles' src/main/wsl/wsl-orcad-real-distro-idle-release-lane.test.ts || test "$?" -eq 1
sed -n '380,446p' src/main/wsl/wsl-orcad-real-distro-idle-release-lane.test.ts

# Existing .cpuprofile fixtures and sizes
rg --files -g '*.cpuprofile' . || test "$?" -eq 1
find . -name '*.cpuprofile' -not -path '*/node_modules/*' -exec wc -c {} + 2>/dev/null | tail -20 || true

Repository: stablyai/orca

Length of output: 17635


Copy profiles in chunks so the 1 MiB output cap does not truncate them.

guest() runs cat through execCommand, which keeps only the last 1,048,576 characters of stdout with no truncation marker. A profile larger than that cap is written without its beginning, so the receipt holds invalid JSON. Copy each profile in byte-range chunks, base64-encoded so each guest call stays under the cap, and decode the chunks before writing the file.

🐛 Suggested fix
+const PROFILE_CHUNK_BYTES = 512 * 1024
...
   for (const name of names) {
-    writeFileSync(
-      join(RECEIPT_DIR, name),
-      await guest(`cat ${quotePosixShell(`${PROFILE_DIR}/${name}`)}`)
-    )
+    const file = quotePosixShell(`${PROFILE_DIR}/${name}`)
+    const size = Number(await guest(`wc -c < ${file}`))
+    const chunks: Buffer[] = []
+    for (let offset = 0; offset < size; offset += PROFILE_CHUNK_BYTES) {
+      const block = offset / PROFILE_CHUNK_BYTES
+      const encoded = await guest(
+        `dd if=${file} bs=${PROFILE_CHUNK_BYTES} skip=${block} count=1 status=none | base64`
+      )
+      chunks.push(Buffer.from(encoded, 'base64'))
+    }
+    writeFileSync(join(RECEIPT_DIR, name), Buffer.concat(chunks))
   }

@nwparker
nwparker merged commit e808442 into main Oct 28, 2026
39 checks passed
@nwparker

Copy link
Copy Markdown
Contributor Author

STATUS: profile captured. Head 86112d3, lane run 40679553390 green on all 3 families.

Finding: every orcad launch first runs a whole second orcad-server.js process (the startup preflight) and waits for it. On a restarted distro that child took 701 / 1438 / 1101 ms (ubuntu / debian / fedora). The node-pty probe child adds only about 20-30 ms of CPU.

Known gap: the main process's own profile exceeded the 1 MiB command-output cap and came back truncated. The child profiles are complete.

Checks run: pnpm tc, typecheck:ci, code-quality:changed, anti-slop, ast-grep.

Next: a proposal to qualify each slot once per host, sent to the lead.

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