feat(cli): add CPU profiling support for Chrome DevTools analysis#4620
Conversation
) Add a cpuProfiler module that generates .cpuprofile files loadable in Chrome DevTools Performance tab. Three trigger modes: - Environment variable: QWEN_CODE_CPU_PROFILE=1 records from start to exit - Signal toggle: SIGUSR1 starts/stops recording (Linux/macOS) - Command: /doctor cpu-profile [--duration N] records for N seconds Output written to ~/.qwen/cpu-profiles/ with safety guards (rate limit, max 5 files, disk space check). Complements existing heap snapshot and memory diagnostics infrastructure.
📋 Review SummaryThis PR adds a well-designed CPU profiling module that generates Chrome DevTools-compatible 🔍 General Feedback
🎯 Specific Feedback🟡 High Priority Issues
🟢 Medium Priority Improvements
🔵 Low Priority Suggestions
✅ Highlights
|
There was a problem hiding this comment.
Pull request overview
Adds first-class CPU profiling support to the CLI by wiring a new cpuProfiler utility into startup and exposing it via /doctor cpu-profile, producing .cpuprofile artifacts compatible with Chrome DevTools’ Performance tab.
Changes:
- Introduces
cpuProfilermodule to start/stop V8 CPU profiling and persist.cpuprofilefiles with retention/rate-limit/disk-space guards. - Extends
/doctorwith a newcpu-profilesubcommand (--duration) to record a bounded capture window. - Initializes CPU profiling support from the CLI entrypoint and adds unit tests for the new module.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/cli/src/utils/cpuProfiler.ts | New CPU profiling utility (inspector-based), file writing, retention/rate limit, SIGUSR1 toggle, exit cleanup. |
| packages/cli/src/utils/cpuProfiler.test.ts | Unit tests for start/stop lifecycle, rate limiting, cleanup behavior, and guardrails. |
| packages/cli/src/ui/commands/doctorCommand.ts | Adds /doctor cpu-profile command path and argument parsing. |
| packages/cli/index.ts | Wires initCpuProfiler() into CLI startup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
- Platform-aware SIGUSR1 hint (suppress on Windows) - Log warning when statfsSync is unavailable instead of silent swallow - Improve error messages for stopping state and lost session - Add user feedback on abort (report saved file path) - Fix non-deterministic sort in cleanupOldProfiles on stat failure - Add initCpuProfiler() ordering comment - Unify copyright year to 2025 - Add InspectorSession interface rationale comment - Fix doctorCommand.test.ts assertions for new cpu-profile subcommand - Add zh-CN/zh-TW translations for cpu-profile command description
Verification Report — CPU Profiling for Chrome DevTools (#4620)Test Environment
Test ResultsAll three trigger modes verified successfully:
Terminal Output (tmux session)SIGUSR1 signal toggle: File Management Verification
CPU Profile Analysis (parsed from .cpuprofile JSON)The profile correctly captures V8 sampling data — during idle waiting for API response, 99% samples land on Chrome DevTools Flame Chart
The generated Unit Tests
Complementary ToolingThis CPU profiling feature pairs well with the existing
The Notes
|
The i18n check failed because zh.js and zh-TW.js had the "Record a CPU profile for Chrome DevTools analysis" key but en.js did not. Add the identity mapping to en.js.
There was a problem hiding this comment.
Stage 1: Template Gate
@yiliang114 — The PR body was missing several required headings from the PR template.
Update (re-triage): The author has updated the PR body to include all required template sections:
- ✅
## What this PR does - ✅
## Why it's needed - ✅
## Reviewer Test Planwith### How to verify,### Evidence (Before & After),### Tested on - ✅
## Risk & Scope - ✅
## Linked Issues - ✅
<details><summary>中文说明</summary>
Template gate passed. Proceeding to Stage 2.
中文说明
PR body 缺少模板必需标题。
更新(重新审查): 作者已更新 PR body,包含所有必需模板 section:
- ✅
## What this PR does - ✅
## Why it's needed - ✅
## Reviewer Test Plan(含### How to verify、### Evidence (Before & After)、### Tested on) - ✅
## Risk & Scope - ✅
## Linked Issues - ✅
<details><summary>中文说明</summary>
模板检查通过,进入 Stage 2。
— Qwen Code
Template issues resolved by author. Re-triage in progress.
Stage 2: Product Direction GateResult: ALIGNED ✅ The Claude Code changelog does not show an explicit CPU profiling feature, but this PR is directionally sound:
No product direction concern. Continuing to Stage 3. 中文说明结果:方向一致 ✅ Claude Code changelog 中没有明确的 CPU profiling 功能,但此 PR 方向合理:
无产品方向问题,进入 Stage 3。 — Qwen Code |
Stage 3: KISS-Focused Code ReviewResult: PASS with 1 significant issue Code Quality Summary8 files changed (+984/-3). The code is well-structured and follows project conventions:
Significant Issue: Rate Limit Leaves Profiler StuckFile: When
Scenario: User sends SIGUSR1 to start, then SIGUSR1 again within 30s → gets rate limit error → profile is stuck recording with no way to stop cleanly. Suggested fix: When rate-limited, either:
This is not a blocker for the feature itself, but it's a real correctness issue that could cause user confusion. Recommend fixing before merge. Minor Notes
中文说明结果:通过,但有 1 个重要问题 代码质量总结8 个文件变更(+984/-3)。代码结构良好,遵循项目规范:模块边界清晰、状态机简洁、动态导入 重要问题:限速导致 Profiler 卡死文件: 当
场景:用户发 SIGUSR1 开始录制,30s 内再发 SIGUSR1 停止 → 收到限速错误 → profile 卡在录制状态无法干净停止。 建议修复:限速时要么重置 state 为 idle 并丢弃数据,要么提供强制写入选项,要么仅对 env-var 退出清理应用限速。这不是功能 blocker,但是真实的正确性问题,建议 merge 前修复。 — Qwen Code |
Stage 4: Real-Scenario TestingResult: PASS ✅ All three trigger modes verified via tmux real-scenario testing. Before (installed
|
Stage 5: Final DecisionResult: NEEDS MAINTAINER REVIEW — not approving, one correctness issue to resolve first. Assessment
What Needs To Happen
Once the rate limit fix lands, I would approve this PR. The feature is well-built, well-tested, and fills a real gap. 中文说明结果:需要维护者审查 — 暂不批准,有一个正确性问题需要先修复。
需要做的事
修复限速问题后我会批准此 PR。功能构建良好、测试充分、填补了真实空白。 — Qwen Code |
- When enforceRateLimit() throws in stopCpuProfile(), explicitly stop the V8 profiler, disconnect the session, and reset state to 'idle' so the user is not stuck and can start a fresh recording. - Tailor the "stopped externally" info message to omit SIGUSR1 on Windows where the signal does not exist. - Add test verifying state recovery after rate-limit rejection.
|
Thanks for the thorough review! The rate-limit-stuck-state issue you flagged has already been addressed in the current code — the |
tanzhenxin
left a comment
There was a problem hiding this comment.
Review
Clean, well-built CPU profiler — three sensible triggers, a race-safe state machine, correct retention/disk-space guards, and locked-down file permissions. Approving. Three non-blocking follow-ups worth a quick pass:
1. Some status messages print placeholders literally (severity: medium · confidence: very high)
A few new messages use single-brace placeholders ({max}, {path}, {duration}) where the translation layer only substitutes the double-brace form used everywhere else. The success message is fine, but the --duration range error, the in-progress line, and the abort-early "Profile saved: {path}" all render their placeholder verbatim. Switching to {{...}} fixes them.
2. The headless command can't be interrupted mid-recording (severity: low · confidence: high)
In non-interactive mode the wait never receives the runner's cancellation signal, so qwen -p '/doctor cpu-profile --duration 300' blocks for the full duration with no way to Ctrl-C out. Threading the abort signal into the command context restores it.
3. The rate limiter discards a profile it just recorded (severity: low · confidence: high)
The 30s limit is checked at stop time, after recording — so profiling twice within 30s loses the second capture with a "wait Ns" error (worst in env-var mode on exit). Gating at start time avoids spending the overhead only to throw the data away.
Verdict
APPROVE — solid, well-guarded feature; the above are polish, not blockers.

What this PR does
Adds a
cpuProfilermodule that generates.cpuprofilefiles loadable in Chrome DevTools Performance tab. Three trigger modes:QWEN_CODE_CPU_PROFILE=1records from process start to exitSIGUSR1starts/stops recording (Linux/macOS)/doctor cpu-profile [--duration N]records for N secondsOutput written to
~/.qwen/cpu-profiles/with safety guards (rate limit 30s, max 5 files, disk space check 256 MiB).Design decisions:
node:inspector/promisesfor V8 CPU profiling APIidle→recording→stopping) prevents concurrent recording conflictsregisterCleanup()ensures in-progress profiles are flushed on exit0600(owner-only)Why it's needed
Qwen-code already ships heap snapshots (
/doctor memory --snapshot) for diagnosing memory distribution — a pattern borrowed from Claude Code'sheapDumpService(auto-dump at 1.5 GB + manual/heapdump). But when users report high CPU or unexplained process hangs (#4617), there is no equivalent self-service tool to capture what the process is spending compute on.Heap snapshots answer "what's holding memory" — CPU profiles answer "what's burning cycles." Without this, the only recourse is asking users to install external tools (
node --prof,perf,dtrace) and manually attach to a running process, which most non-expert users won't do.This PR closes that gap: three low-friction trigger modes produce standard
.cpuprofilefiles that load directly in Chrome DevTools — the same workflow engineers already use for V8 heap snapshots. It completes the performance diagnostics matrix:/doctor memory)/doctor memory --snapshot)/doctor cpu-profile)Reviewer Test Plan
How to verify
/doctorcommand mode:SIGUSR1 signal toggle (Linux/macOS):
Environment variable (full lifecycle):
Load the generated
.cpuprofilein Chrome DevTools → Performance tab → Load profile button.Evidence (Before & After)
Terminal output —
/doctor cpu-profile:SIGUSR1 signal toggle:
File management — retention and permissions working:
CPU profile parsed content (confirms valid V8 sampling data):
Chrome DevTools flame chart:
Tested on
Environment (optional)
macOS arm64, Node.js v24.3.0, model: qwen3-coder-flash (model choice does not affect CPU profiling — the profiler captures local Node.js process activity).
Risk & Scope
SIGUSR1does not exist on Windows — the signal handler is a no-op there; Windows users must use/doctor cpu-profileor the env var instead.fs.statfsSync()(disk space check) is not available on all platforms; the guard is wrapped in try-catch and logs a warning on failure rather than blocking the write.Linked Issues
Closes #4617
Part of the memory/performance diagnostics enhancement series: #4181, #4182, #4183, #4184
中文说明
这个 PR 做了什么
新增
cpuProfiler模块,生成可在 Chrome DevTools Performance 标签页加载的.cpuprofile文件。三种触发模式:QWEN_CODE_CPU_PROFILE=1从进程启动录制到退出SIGUSR1开始/停止录制(Linux/macOS)/doctor cpu-profile [--duration N]录制 N 秒输出写入
~/.qwen/cpu-profiles/,带安全防护(30s 限速、最多 5 个文件、256 MiB 磁盘空间检查)。为什么需要
Qwen-code 已提供堆快照(
/doctor memory --snapshot)用于诊断内存分布——这是借鉴 Claude CodeheapDumpService的模式(1.5 GB 自动 dump + 手动/heapdump)。但当用户报告高 CPU 或进程无响应挂起(#4617)时,没有等价的自助工具来捕获进程在消耗什么算力。堆快照回答"什么占着内存"——CPU profile 回答"什么在烧 CPU"。没有这个工具,唯一的办法是让用户安装外部工具(
node --prof、perf、dtrace)并手动 attach 到运行中的进程,大多数非专家用户不会这样做。此 PR 填补了这个空白:三种低门槛触发模式生成标准
.cpuprofile文件,直接在 Chrome DevTools 中加载——工程师已经熟悉的 V8 堆快照工作流。它完善了性能诊断矩阵。评审测试计划
如何验证
/doctor cpu-profile --duration 5— 验证文件写入kill -USR1 <pid>切换 — 验证信号触发录制/停止QWEN_CODE_CPU_PROFILE=1— 验证全生命周期录制证据(修复前后对比)
0600,目录权限0700测试平台
单测结果
cpuProfiler.test.tsdoctorCommand.test.tsmustTranslateKeys.test.ts(i18n 一致性)风险与范围
SIGUSR1在 Windows 上不存在——信号处理程序在那里是 no-op;Windows 用户需使用/doctor cpu-profile或环境变量。fs.statfsSync()(磁盘空间检查)并非所有平台都可用;已用 try-catch 包裹,失败时记录警告而非阻断写入。关联 Issue
Closes #4617
属于内存/性能诊断增强系列:#4181、#4182、#4183、#4184