关于 perf-workflow
perf-workflow 是一套循序渐进的性能问题分析技能,帮助开发者从证据定位瓶颈、验证根因,并完成优化与验证闭环。它强调可复现数据、全链路分析和单变量验证。
主要功能
- 01按证据、定位、假设、监控、优化、验证推进
- 02从用户操作追踪完整执行链路
- 03结合耗时与资源数据筛选瓶颈
- 04要求根因假设可证伪且可复现
- 05支持开关控制的长期性能监控
适用场景
- 01分析页面卡顿或主线程阻塞
- 02定位慢 API 或后端请求瓶颈
- 03调查内存增长与疑似泄漏
- 04为间歇性性能问题补充监控
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fudesign2008/ open-skills --skill perf-workflow安装后在 Claude Code 中输入/perf-workflow确认已加载。
详细介绍
性能问题分析工作流
强依赖:
clarifying-question-discipline(严格的主动提问规范)和known-issue-research(已知性能模式快速检索,§2.1)。如果缺少其中任一项,则中止并打印安装命令(npx skills add FuDesign2008/open-skills -g --skill '*' --yes)。
范围
此工作流负责找出性能瓶颈的根本原因:是谁触发了什么高开销操作,以及在什么条件下触发。确认根本原因后,可据此设计修复方案、实施代码/配置优化并验证结果。 如何发布、推出或交付修复不在此工作流的范围内——由项目自行决定。
触发识别
所有触发词都以「性能」开头,因此很容易识别和记忆。单独说出触发词,或使用**「触发词 + 冒号 + 空格 + 描述」**的形式,即可进入此工作流或相应阶段。使用冒号时,冒号和空格不受语言限制(中文或英文标点均可)。
- 「性能分析」或「性能分析: xxx」 → 进入此工作流,从阶段 1(收集证据)开始
- 「性能证据」或「性能证据: xxx」 → 阶段 1:收集证据
- 「性能定位」或「性能定位: xxx」 → 阶段 2:定位瓶颈
- 「性能假设」或「性能假设: xxx」 → 阶段 3:推测根本原因
- 「性能监控」或「性能监控: xxx」 → 阶段 4:构建监控
- 「性能优化」或「性能优化: xxx」 → 阶段 5:实施优化
- 「性能验证」或「性能验证: xxx」 → 阶段 6:验证
- 「性能深入」或「性能深入: xxx」 → 在阶段 2(定位瓶颈)中继续或深入分析
当用户说「性能问题」("performance issue")、「卡顿」("stutter/jank")、「很慢」("it's slow")等时,也可视为进入此工作流,从阶段 1(收集证据)开始。 当用户提供与性能相关的日志或性能分析结果并希望进行分析时,视为进入此工作流,从阶段 1(收集证据)或阶段 2(定位瓶颈)开始。
总体原则
「先定问题拆链路,再采数据筛瓶颈,下钻推导提假设,控制变量验根因,优化固化成闭环。」(先明确问题并拆解链路;再收集数据、筛选瓶颈;深入分析并推导假设;控制变量验证根因;最后将优化固化为闭环。)
- 数据驱动:先获取数据,再得出结论——绝不要先假设问题,再搜寻支持证据。如果没有复现路径或可分析的数据,先帮助用户收集这些信息(复现步骤、日志、性能分析数据、屏幕录制等)。
- 自顶向下:从用户感知到的完整链路入手,先宏观后微观。先确定慢在哪里(耗时/阻塞/渲染量等),再追溯是谁触发的,以及触发的条件是什么。
- 单一变量,可复现:根因假设必须能够通过现有数据或少量有针对性的监测手段得出是/否结论。验证时一次只改变一个变量;结果必须可复现、可证伪——绝不把相关性当作因果关系。
- 覆盖完整链路:分析从问题开始到结束的完整执行链路,而不只是局部数据——避免盲人摸象。
- 可开关、适用于生产环境的监测:性能可观测性应设计为代码库中完善且适用于生产环境的监测能力,并通过开关(环境变量、配置项、功能标志等)控制。关闭时,不输出任何内容,也不采集任何样本,且不影响线上产品;需要分析时,打开开关即可收集数据——无需临时添加监测代码,之后再将其移除。
- 不要过早优化:只优化那些有数据支撑、影响用户体验且超过阈值的性能问题。对于没有测量数据支撑的「可能有一天会出现的性能问题」,不要进行预防性优化——这只会增加不必要的代码复杂度和维护成本。
- ⚠️ 主动询问:遵循
clarifying-question-discipline(每轮只问一个问题;多轮沟通,直到问题明确;先澄清,不要急于回答;强依赖——如果缺少必要信息就中止)。 - 🚩 红旗:一次性向用户抛出多个问题/待确认事项,让用户逐一回答(违反硬性规范)——每次只问一个最关键的问题,收到答复后再问下一个。
阶段流程
前进流程:证据 → 定位 → 假设 → 监测 → 优化 → 验证。
常见跳转:
- 阶段 1 发现缺少数据,需要建立监测 → 直接进入阶段 4(建立监测)添加监测,然后返回阶段 2。
- 阶段 2 已有足够数据且瓶颈明确 → 跳过阶段 3,直接进入阶段 4 或 5。
- 阶段 6 否定了假设 → 返回阶段 2 或 3 重新分析。
- 阶段 6 表明优化效果未达预期 → 返回阶段 3 重新提出假设,或返回阶段 5 调整优化方案。
- 阶段 1 难以采集间歇性问题的数据 → 优先进入阶段 4 建立长期监测;问题复现后,再返回阶段 2。
阶段 1:收集证据(性能证据)
目标
获取可复现、可分析的性能数据,而不是仅凭描述猜测;将模糊的「卡顿/缓慢」转化为量化指标和性能基线,并明确优化目标和硬性红线。
需要澄清的信息
- 症状:是什么操作触发了变慢(点击、滚动、输入、拖动、API 请求等)?“慢”具体表现是什么(卡顿、空白屏幕、加载指示器、无响应)?尽可能量化(例如,某个端点的 P99 耗时,单位为毫秒;某项操作的帧率或延迟),并明确分析边界(问题的起点和终点,避免范围无限扩大)。
- 现有数据:是否已有日志、Performance/CPU 性能分析记录、网络抓包或自定义埋点?如果没有,先确定可以“在什么环境中、用什么方法”采集数据。
- 复现条件:问题总是可以复现,还是偶尔发生?是否对数据量、并发量或设备有要求?
常见数据来源(按场景选择)
| 场景 | 候选数据来源 |
|---|---|
| 前端卡顿 / 主线程阻塞 | 控制台日志、Chrome Performance 录制、Long Task / 自定义性能监控 |
| 渲染过多 / 重绘开销大 | 框架渲染统计、React DevTools Profiler、自定义提交/渲染埋点 |
| API 响应慢 / 后端问题 | 网络面板、服务端日志、APM、追踪记录 |
| 内存问题 / 泄漏 | 堆快照、内存趋势图 |
不绑定任何特定技术栈,应根据项目实际具备的监控能力和工具进行选择。
输出
- 清楚说明已有哪些数据,以及仍缺少哪些数据。
- 如果缺少数据:明确用户需要执行的操作,以及需要采集的内容(例如,提供复现步骤、切换某个开关、复现问题,并导出控制台或性能分析结果)。
- 建议包含的输出:症状(含量化指标)、复现条件、分析边界、当前性能基线,以及目标阈值或红线;可整理为结构化的“性能问题定义表”,便于后续对比。
阶段 2:定位瓶颈(性能定位)
目标
从原始数据中找出异常(谁消耗了多少时间/资源,以及发生在何时),并追踪触发链路(事件 / 调用栈 / 数据流);构建从起点到终点的完整链路拓扑,并从时间和资源两个维度进行初步瓶颈筛查,将分析范围缩小到 1~2 个核心环节。
分析方法(通用)
- 全链路拓扑:按执行顺序,将从用户操作到问题结束的每个环节逐一拆解(代码执行、系统调用、网络/存储等),形成无缺漏、无重叠的拓扑;每个环节都应可独立计时,并明确输入/输出。
- 二维采集:时间维度(各环节耗时、占整个链路的比例)+资源维度(CPU/内存/IO/网络等)——两者缺一不可。
- 初步瓶颈筛查:按耗时占比对各环节排序,锁定高耗时环节(例如占比 >20%);根据资源异常(饱和、持续上升、错误率)锁定异常环节;排除耗时低且资源正常的环节。资源利用率 >70% 是常见的告警线——超过此阈值后,响应时间往往会非线性上升,因此即使绝对值看起来不高,也值得优先排查。
-
发现异常 在时间线或汇总统计中,找出显著异常的指标,例如:
- 某个任务/请求的耗时超过阈值(例如主线程任务 >50ms、>200ms);
- 单次操作的处理量异常大(例如一次更新触及大量节点、一次请求返回超大数据负载);
- 触发频率异常高(例如某个事件的触发次数远超预期)。
-
定位异常 对于每个异常,确定:
- 异常发生在哪一层(前端/后端/网络/存储);
- 对应的代码或模块(文件、函数、组件、端点);
- 调用栈或事件链(从用户操作或入口到耗时/资源消耗点)。
-
构建因果链 将其串联为“用户操作 → 事件/请求 → 处理函数 → 高耗时操作 → 观测到的指标”,避免只看到症状,却找不到触发条件。
工作方法
- 对于文本日志:按关键词、耗时、错误码等使用搜索工具(grep/ripgrep 等)筛选,然后阅读相关片段和调用栈。
- 对于性能剖析数据:先查看时间线或火焰图上的“宽条”或“高占比”区域,再深入到具体函数/组件。
- 对于代码:阅读代码并搜索调用关系,同时交叉参考日志中的栈信息,弄清楚“谁在什么条件下调用了谁”。
输出
- 异常摘要:说明哪里耗时高/处理量大/频率高,以及大致位置。
- 因果链:用一两句话或简单图示描述“操作 → … → 瓶颈”。
- 待验证假设(初步方向):列出 1-3 个“可能的根因”方向,并说明每个方向有哪些证据可以证实或证伪。细化和分类将在第 3 阶段(假设根因)进行。
- 核心瓶颈环节列表:列出 1-2 个环节,并附上耗时占比或资源异常摘要。
已知性能模式快速检索(可选,定位卡顿时使用)
当满足 known-issue-research §2.1 的触发条件时,加载该技能并遵循 §2.1(性能模式变体)。本工作流自身的模式表和阶段编排仍保留在此文件中。
第 3 阶段:假设根因(性能假设)
目标
将异常和触发链提炼为可验证的根因假设,并根据常见性能问题模式对其分类,以指导后续的埋点和修复。假设必须可验证、可证伪——不能含糊猜测;可借助行业方法进行分类(见下文)。
根因分析参考
- USE 方法(资源类型瓶颈):从三个维度判断资源是否饱和(CPU、内存、IO、网络)——利用率、饱和度、错误。
- RED 方法(执行/请求类型瓶颈):从三个维度判断执行逻辑或调用链问题——速率、错误、耗时。
将这些方法与下方的“常见模式”表结合使用;此处不再展开具体步骤。
常见模式(与技术无关的描述)
| 模式 | 特征 | 典型诱因 |
|---|---|---|
| 响应范围过广 | 原本只应局部更新,却触发全局或大范围的重新计算/重新渲染 | 粗粒度事件(例如“任何变化”)触发了大范围更新;缺少细粒度订阅或差异比较 |
| 更新未批处理/合并 | 一次操作导致多次独立的重新计算或重新渲染 | 多次连续的状态更新未批处理;异步回调各自触发更新 |
| 高频触发 | 高频事件每次发生时都会执行开销较大的逻辑 | scroll/resize/mousemove 等事件未进行节流/防抖;每一帧或每次事件都进行大量重新计算 |
| 积压任务同时触发 | 主线程繁忙或队列积压时,许多延迟任务同时触发 | 多个节流/防抖计时器在同一时刻触发;计时器/微任务不断堆积 |
| 资源泄漏 | 内存/连接/句柄持续增长且从未释放 | 连接未关闭、缓存/计时器未清理、监听器未移除 |
| 同步阻塞 | 同步操作长时间占用主线程或关键路径 | 同步 I/O、锁竞争、长事务、开销较大的同步计算 |
| 重复/冗余计算 | 未使用缓存而反复计算同一结果 | 缺少记忆化/缓存、N+1 查询、重复序列化/反序列化 |
在具体项目中,这可能表现为“某个框架的 setState”“某个事件总线的 emit”“某个 RPC 调用”等,但在抽象层面上,它们都属于上述类别。
输出
- 当前最可能的 1-2 个根因假设;每个假设都必须可验证且可证伪(例如,通过判断“是否进行了全表扫描?”或“这个变量的值是多少?”得出是/否结论),避免使用“查询很慢是因为 SQL 写得不好”这类无法验证的表述。
- 如果有多个假设,按“用户感知影响最大 + 验证成本最低”排序,并优先验证排名最高的假设。
- 每个假设的验证方法:现有日志能否给出答案?如果不能,需要在路径的哪些位置添加可切换的监控(参见阶段 4:构建监控)?确认根因后,进入阶段 5(实施优化)进行修改,然后在阶段 6(验证)中验证假设和优化效果。
阶段 4:构建监控(性能监控)
目标
当现有数据无法验证假设时,在关键路径上添加可切换、达到生产级别的性能可观测性。监控逻辑作为真实代码发布,并由开关控制;关闭时不会影响线上产品,开启后则可用于分析性能监控数据。
何时需要额外监控
- 现有日志/性能分析数据无法显示“是谁触发的”“在什么条件下触发”或“当时某个变量的值是多少”。
- 需要比较“假设成立”和“假设不成立”时的行为(例如某个标志为 true 与 false 时的行为)。
- 项目尚未为这条路径配置可切换的性能监控,需要设计并发布此监控。
监控设计原则(通用)
-
由开关控制 使用运行时开关(环境变量、配置中心、功能标志、本地调试开关等)控制是否启用。生产环境中默认关闭,或仅对特定用户/会话启用;排查问题时再打开,避免给普通用户带来开销或噪声。
-
长期保留,而非临时使用 监控代码是长期保留的功能,而不是“添加后验证,再删除”。逻辑长期保留,行为完全由开关控制:关闭时不产生输出、不采样,也没有额外开销(或开销可忽略);开启时则按约定格式输出或上报,并与现有日志/性能分析记录保持一致。
-
便于对齐 输出稳定的时间戳(例如
performance.now()或服务端纳秒级时间)和位置标识符,以便与同一时间线上的现有日志/性能分析记录对齐,进行因果关系和时序分析。 -
信息恰到好处 每条记录至少应包含:位置标识符、时间戳,以及用于确认或证伪假设的少量关键变量(例如命中了哪个分支、ID、计数或错误代码)。尽可能复用项目现有的性能日志格式,保持分析方式一致。
选择监控点
- 优先:疑似的直接触发点(例如更新状态的调用、触发请求的调用、执行重新计算的函数入口)。
- 其次:触发链上的中间节点(例如事件处理程序入口、回调入口),用于确认调用顺序和频率。
- 然后:耗时计算的入口/出口,用于确认每次调用的耗时和调用次数。
输出
- 监控点列表(文件:行号或函数/端点名称),每个监控点都要注明用途和建议的开关名称。
- 用户需要采取的操作:如何启用开关、如何复现,以及如何捕获并提供新的日志/性能分析记录。
- 如果根因已经确认,或将在 Stage 6 中验证,请进入 Stage 5(实施优化)进行更改。
Stage 5:实施优化(性能优化)
目标
根据 Stage 3 的根因结论(以及 Stage 4 的监控结果;如果已在 Stage 6 中验证假设,也可据此实施),进行代码或配置层面的优化,以消除或缓解瓶颈。本阶段仅产出“针对根因的更改”及更改列表;多方案评估或详细任务拆分不在本阶段范围内。
原则
- 修复根因,而非表象:更改应对应 Stage 3 中识别的根因模式(例如缩小响应范围、批量更新、节流/防抖),而非泛泛地进行优化。
- 利用监控进行对比:优先使用 Stage 4 中可由开关控制的监控,在 Stage 6 的相同复现场景下对比更改前后的情况,确认更改确实有效。
- 控制变量验证:更改前先通过单变量实验验证根因;每次优化只针对一个根因点,以便 Stage 6 将收益归因于单项更改。
- 分层并考虑成本效益:按从高到低的层级考虑——业务逻辑 → 应用代码 → 框架/依赖项 → 系统/硬件,优先选择较高层;优先选择“更改成本低、收益高”的方案(例如简化逻辑、复用缓存)。
- 按时间占比排序:优先优化时间占比最高的部分;时间占比低于 10% 的部分即使性能提升 100 倍,对整体指标的影响也微乎其微,因此应跳过,聚焦真正的主要瓶颈。
- 稳定性:不要改变业务语义、引入功能缺陷或产生新的性能副作用。
输出
- 变更清单:文件、位置(函数/模块)、变更摘要。
- 建议的验证方法:说明应使用哪个复现场景,以及要监控哪些指标(与第 4 阶段的监控保持一致),以便第 6 阶段验证优化效果。
第 6 阶段:验证(性能验证)
目标
- 使用新数据,对第 3 阶段(提出根因假设)中的假设作出是/否判断;必要时修正假设,或返回第 2 阶段(定位瓶颈)。
- 如果已完成第 5 阶段(实施优化),则使用相同的复现场景和第 4 阶段的监控数据进行优化前后对比,确认优化是否有效,以及指标是否达到目标。
验证方法
- 对每个假设,明确说明“如果假设成立,日志/性能分析结果应显示什么”和“如果假设不成立,应显示什么”。
- 在新日志中搜索相应模式,并检查其是否符合假设成立时的预期。
- 如果有时间戳,将不同模块/层的埋点对齐到同一时间线上,确认事件顺序,以及它们是否发生在同一任务/请求中。
- 优化后,在相同的复现路径下对比优化前后的关键指标(耗时、调用次数、渲染量等),并说明通过标准。
- 效果验证(已完成第 5 阶段时):
- 基线复测:在与优化前相同的复现条件和环境下,对比全维度指标。
- 功能回归:确认常规、边缘情况和峰值负载场景仍能正常工作。
- 副作用检查:确认没有引入其他性能问题(例如通过缓存优化响应时间,却导致内存占用上升)。
- 如果可行:通过生产环境/金丝雀发布验证真实流量下的效果。
输出
- 每个假设的结论:已确认 / 已推翻 / 仍不确定。
- 如果仍不确定:说明还缺少哪些信息,以及下一步是添加可切换的监控以收集更多数据,还是从不同角度重新分析。
- 如果已确认:用一两句话概述根因(谁在什么条件下触发了什么),作为设计和实施修复方案的依据。
- 如果已完成第 5 阶段(实施优化):报告优化前后对比,以及是否达到目标。
- 闭环(可选后续步骤):验证通过后,可考虑将关键指标纳入常态化监控和告警;如果有 CI/CD,可考虑在流水线中添加性能基线或门禁以防止回归;并将此次优化的指标和最佳实践纳入团队规范,以持续改进。
- 停止条件:一旦所有假设都已验证且关键指标达到目标,或继续优化剩余瓶颈的性价比过低(变更成本远高于收益),本轮优化即可结束。
输出详细程度(自适应)
- 问题简单、数据充足:可精简为“异常 + 因果链 + 根因结论”。
- 问题复杂、涉及多个模块:简要输出各阶段的结果(收集了什么、主要异常、假设、监控点列表、优化要点、验证结论和效果结论),并在必要时附上关键日志片段或调用栈摘录。
- 涉及特定技术栈时,在该次交流中自然地结合相关内容,不要在此 SKILL 文件中硬编码特定标签或命令。