关于 k6-trend-analysis
用于分析多个 Grafana Cloud k6 测试运行结果、发现性能变化的技能。它检查指标趋势、脚本变更和阈值余量,在阈值或告警触发前指出退化;只报告发现,不应用变更。
主要功能
- 01比较多次测试运行的指标
- 02按请求标签分组趋势
- 03检测趋势拐点处的脚本变更
- 04计算阈值余量
- 05估算可能触发阈值的时间
适用场景
- 01在阈值触发前发现延迟退化
- 02定位导致性能回归的端点
- 03区分脚本变更与外部变化
- 04分析指定日期范围内的趋势
MCP 相关
它遵循 k6-manage 中的 gcx 用法,查询 Grafana Cloud k6 API 和指标。它不编辑脚本,也不应用阈值变更。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ grafana/ skills --skill k6-trend-analysis安装后在 Claude Code 中输入/k6-trend-analysis确认已加载。
详细介绍
k6 趋势分析
分析 Grafana Cloud k6 测试的多次运行中的指标趋势,以便及早发现性能退化——在阈值超限、警报触发之前。当前 P95 为 380ms,低于 500ms 的阈值,看起来“正常”;但如果一个月前还是 250ms,就说明性能正在悄然退化。本技能会发现这种变化并建议采取措施。
本技能不做什么
- 深入调查单次运行失败的原因:加载
k6-cloud-investigate-test - 编辑脚本或应用阈值变更:加载
k6-test-maintenance - 创建新的测试脚本:使用适当的测试创建工作流
- 直接查询服务端指标:需要关联可观测性数据时,本技能会转交给
debug-with-grafana
依赖项
本技能将所有 GCk6 API 操作交由 k6-manage 处理。执行任何 API 调用前都要先阅读它——其中涵盖身份验证、路径构建(重复的
cloud/cloud/ 前缀)、使用 @nextLink 分页、spill envelope,以及
指标查询语法。不要在此处重复这些内容。
使用的工具:gcx(遵循 k6-manage 中的模式)。
工作流
按顺序完成以下步骤。最后呈现分析结果,不要应用任何变更。
步骤 1:确定测试
用户会提供以下内容之一:
- GCk6 URL(从路径中提取负载测试 ID)
- 测试 ID
- 测试名称(通过
gcx k6 tests list或 v6 API 搜索)
获取测试元数据,确认测试存在。记录 id、name、project_id 和
created 时间戳。对于多次运行的指标端点,需要使用负载测试 ID,而不是运行 ID。
步骤 2:确定分析时间范围
默认分析最近 30 天。在以下情况下调整:
- 用户要求指定时间范围
- 测试运行次数很少(30 天内少于 5 次)——扩大时间范围并说明原因
- 测试运行频率很高(30 天内数百次)——考虑抽样或缩短时间范围。如果数量特别多(超过 200 次),请询问用户
明确说明时间范围:“正在分析 {start_date} 至 {end_date} 的运行记录。”
步骤 3:获取时间范围内的运行记录
使用 v6 API 获取该测试的所有运行记录,通过 $orderby=created desc
分页(@nextLink 循环模式参见 k6-manage 第 3 节)。筛选出分析时间范围内的运行记录。
记录每次运行的:
id、created、endedresult(passed/failed/timed_out)status(created/queued/initializing/running/finished/aborted)note(如果存在——用户有时会为运行添加备注)
除非用户明确要求分析,否则丢弃未达到 finished 状态的运行记录(如 aborted、timed_out 等),因为未完成的运行会导致指标聚合结果不可靠。
统计运行次数。如果可用的运行记录少于 3 条,请告知用户当前样本量不足以进行有意义的趋势分析,并建议扩大时间范围或等待更多运行记录。
步骤 4:获取所有指标及其类型
查询指标值之前,先确认测试会生成哪些指标。使用多次运行指标列表端点(k6-manage references/metrics.md 第 2 节):
GET /cloud/v5/load_tests/{loadTestId}/metrics(test_run_ids=[{id1},{id2},...])
传入一组具有代表性的运行 ID(最早和最近的几次),以便发现分析时间范围内可能新增或移除的指标。记录每个指标的 name 和 type(counter、gauge、trend、rate)。
按类型对指标分组——查询方法必须与指标类型匹配(参见 metrics.md 中的“Query methods”)。使用错误的方法会返回空结果。
步骤 5:获取每次运行的聚合值
对于每个指标,使用多次运行聚合端点(metrics.md 第 8 节)查询时间窗口内所有运行的聚合值:
GET /cloud/v5/load_tests/{loadTestId}/query_aggregate_k6(
query='<method>',
metric='<metric_name>',
test_run_ids=[{id1},{id2},...]
)
具体的 gcx 形式(代理前缀见 k6-manage §2;-o json 可避免输出溢出封装):
LT=<load_test_id>; IDS="123,124,125"
gcx --context <ctx> api "/api/plugins/k6-app/resources/cloud/cloud/v5/load_tests/$LT/query_aggregate_k6(query='histogram_quantile(0.95)',metric='http_req_duration',test_run_ids=[$IDS])" -o json
根据指标类型选择聚合方法:
| 指标类型 | 首选方法 | 捕获的内容 |
|---|---|---|
| trend | histogram_quantile(0.95) | P95 延迟——最常见的 SLO 目标 |
| trend | histogram_quantile(0.5) | 中位数——体现典型表现 |
| trend | histogram_avg | 均值——容易受离群值影响 |
| counter | increase | 每次运行的总计数 |
| rate | ratio | 成功/失败比率 |
| gauge | max | 每次运行的峰值 |
对于 trend 类型的指标(延迟),查询多个分位数(P50、P90、P95、P99),以判断性能退化是普遍存在还是集中在长尾。
对于多目标测试,始终按请求分组进行细分。 对整个测试计算单个聚合 P95,会掩盖仅限于某个端点或页面的回归——如果测试访问了许多 URL,某个 URL 的速度变慢 3 倍,可能不会反映在测试级 P95 中。默认使用分组查询:
| 指标模式 | 默认分组方式 | 原因 |
|---|---|---|
browser_web_vital_*(LCP、FCP、CLS、TTFB、INP、FID) | by (url) | 每次导航都会产生自己的 Web Vital;按 URL 查看趋势可将回归定位到特定页面。 |
http_req_duration、http_req_failed、http_reqs | 如果请求带有 name 标签,则使用 by (name, status);否则使用 by (url, status) | 不同端点的基线各不相同;将它们混在一起会掩盖每个端点的变化。 |
iteration_duration(多场景测试) | by (scenario) | 浏览器场景和协议场景的持续时间差异很大;将它们混在一起没有意义。 |
| 带标签的自定义趋势 | by (<the tag>) | 用户添加标签所依据的内容,大概就是他们关心的内容。 |
构造分组查询前,先通过标签端点(metrics.md §4)和标签值端点(metrics.md §5)发现可用标签。不要只根据脚本内容推断标签——标签重命名或重构可能会在无声无息间改变标签范围。示例:
# List labels for a specific metric on a representative run
GET /cloud/v5/test_runs/{id}/labels?match[]=browser_web_vital_lcp
# Then enumerate values for a useful label
GET /cloud/v5/test_runs/{id}/label/url/values
仅当分组不会增加信息时,才退回到不带 by 的基本聚合,例如 vus、load_generator_cpu_percent,以及所有请求都访问同一 URL 的单目标测试。
响应会将 test_run_id 作为标签返回——使用它将每个值映射回步骤 3 中的运行时间戳。分组查询会为每个(分组、运行)组合返回一个时间序列;将结果整理成“rid x group”表,以便在步骤 7 中计算趋势。
如果运行 ID 太多,无法放入单个 URL(数百个),则将查询分成每组 50 个运行 ID 的批次,并合并结果。
步骤 6:提取阈值
阈值定义了“通过”的含义。获取测试当前的脚本(通过 k6-manage 第 5 节),并解析 export const options = { thresholds: {...} } 块。记录每个阈值的:
- 指标名称和选择器(例如
http_req_duration{name:homepage}) - 条件(例如
p(95)<500) - 是否设置了
abortOnFail
还要查看运行数据中最近一次运行的阈值结果,确认当前哪些阈值通过、哪些失败。
并非所有指标都会设置阈值——这没有问题。没有阈值的指标仍会进行趋势分析;只是不会计算余量。
步骤 7:计算趋势
检测拐点,并以确定性方式排除脚本变更。 在对回归得出结论之前,先查找指标值中的不连续变化——即同一日期多个指标同时出现的突然上升或下降。发现此类变化时,不要猜测脚本是否变更。运行级脚本端点可以在几秒内给出确定答案:
-
对边界两侧的打包脚本计算 sha256 差异。 通过
GET /cloud/v6/test_runs/{id}/script获取最后一次“之前”运行和第一次“之后”运行的快照(k6-manage §5,“两个不同的脚本端点”——请使用运行范围的端点,而非负载测试端点,因为后者只会显示当前版本)。使用shasum -a 256进行比较。gcx --context <ctx> api /api/plugins/k6-app/resources/cloud/cloud/v6/test_runs/<before_id>/script > /tmp/before.bin gcx --context <ctx> api /api/plugins/k6-app/resources/cloud/cloud/v6/test_runs/<after_id>/script > /tmp/after.bin shasum -a 256 /tmp/before.bin /tmp/after.bin对于跨多次运行的拐点,要进行彻底检查,请为过渡期间的每次运行都计算哈希——如果连续 N 次运行共享一个哈希,接下来的 N 次运行共享另一个哈希,那么边界就很清晰。如果哈希在中途发生变化,则说明测试经过了多次编辑。
-
如果 sha256 不同,则测试脚本发生了变化——拐点可能是测试端的伪象,而非服务回归。请在边界处将分析拆分为不同阶段,并分别计算每个阶段内的趋势。跨脚本变更比较指标会产生误导性趋势——如果脚本只是停止访问某个响应缓慢的端点,那么 P95 从 3,000ms 降至 150ms 并不代表“有所改善”。在报告中明确说明各阶段,并将建议重点放在最近的阶段。
-
如果 sha256 相同,则脚本逐字节完全相同,回归原因在测试之外(服务端、基础设施或负载区域)。这是高置信度的信息——请将其带入步骤 9(服务端关联分析),而不是将“测试是否发生变化?”留作未解问题。
这项 sha256 差异检查只需 5 秒,且能确定性地排除大量可能原因。每次检测到拐点时都要执行,而不仅仅是在用户提出要求时执行。
对于每个指标,按时间顺序构建 (run_timestamp, value) 数对序列。 然后计算:
基本统计信息:
- 所有运行的平均值、标准差、最小值和最大值
- 当前值(最近一次运行)
- 基线值(时间窗口内最早一次运行的值,或前 3 次运行的平均值,以提高稳定性)
趋势方向: 将运行分成两半(时间窗口的前半段和后半段)。 比较两半的平均值:
- 恶化:后半段平均值恶化 >10%(延迟/错误率更高,成功率更低)
- 改善:后半段平均值改善 >10%
- 稳定:变化在 10% 以内
- 波动:无论方向如何,标准差都超过平均值的 25%
10% 和 25% 是起始阈值。如果用户的测试容差很严格,或指标噪声很大,请调整阈值并说明理由。
变化速率:
以每周百分比变化表示:((recent_mean - baseline_mean) / baseline_mean) * 100 / weeks_in_window。这样可以对不同时间窗口长度进行归一化。
异常检测: 标记指标值偏离总体平均值超过 2 个标准差的任何一次运行。这些可能是值得单独调查的拐点。
步骤 8:余量分析
对于已定义阈值的指标(来自步骤 6),计算余量:
headroom_pct = ((threshold_value - current_value) / threshold_value) * 100
余量分类:
- 充足(>50%):远未达到限制
- 适当(20-50%):状态良好,但值得监控
- 不足(<20%):如果趋势持续,可能会超出限制
- 已超限(<0%):已经不符合要求
对于余量不足且正在恶化的指标,估算如果当前变化速率持续下去,何时会超出阈值:
weeks_until_breach = headroom_absolute / rate_of_change_per_week
这只是粗略估算,并非预测——请表述为“按当前恶化速度,该指标可能在约 N 周后超出阈值”。
步骤 9:服务端关联分析(必要时)
如果步骤 7 发现性能退化,请提出(不要自动运行)与服务端数据进行关联分析,以区分服务退化(修复服务)、测试环境变化(负载区域延迟、LG 耗尽)和脚本变更(修改后变慢)。先展示发现并征求同意。
将任务交给 debug-with-grafana(通过 gcx),查询服务在同一时间窗口内的 Prometheus
指标和 Loki 日志;查看服务错误率变化、上游延迟、资源压力(CPU/内存/连接池),以及与拐点同时发生的部署。
当服务没有可观测性数据时(第三方服务或其他团队的服务——没有 Prometheus 任务、Loki 流或探测数据),改用客户端信号,仍可定位回归:
- **浏览器测试:**对比 Tempo 迭代跟踪(k6-manage §7、
/api/v1/tempo/api/search+/traces/{id})中的前后两次运行。跨度名称汇总、最慢的跨度以及各 URL 的导航时长,可以将回归定位到某个 URL、定位器操作或资源;Web 指标是web_vital.*跨度属性——直接读取即可。 - **协议测试:**对比
http_req_duration by (name, status)和http_req_failed by (name, status);某个具名请求出现回归,说明问题可能出在对应端点。拆分http_req_waiting(服务端耗时)和http_req_receiving(传输耗时),以区分处理缓慢和下载缓慢。 - 负载佐证(两者均适用):查询
data_received/browser_data_received。延迟上升 + 负载增大 → 服务端内容变化;延迟上升 + 负载稳定 → 服务端处理变化;负载间歇性变化 → 缓存不稳定或 A/B 测试。
有条件时优先进行服务端关联分析——它能直接回答“为什么”;备用方法能回答“在哪里”和“发生了哪类变化”,也能为负责团队提供一份不错的问题单。
步骤 10:提交报告
始终以结构化报告呈现发现。切勿直接应用变更。
报告模板
使用以下结构。省略不适用的部分(例如,未发现异常时跳过“异常”部分)。
# Trend Analysis: {test_name}
**Test ID**: {test_id}
**Analysis window**: {start_date} to {end_date} ({N} runs analyzed)
**Overall health**: {Healthy | Watch | Degrading | Critical}
## Run Summary
| Period | Runs | Passed | Failed | Pass Rate |
|--------|------|--------|--------|-----------|
| First half | N | N | N | N% |
| Second half | N | N | N | N% |
## Metric Trends
| Metric | Type | Current | Baseline | Change | Trend | Threshold | Headroom |
|--------|------|---------|----------|--------|-------|-----------|----------|
| http_req_duration (P95) | trend | 380ms | 250ms | +52% | Degrading | 500ms | 24% |
| http_req_failed | rate | 0.8% | 0.3% | +167% | Degrading | 1% | 20% |
| http_reqs | counter | 15,230 | 15,100 | +0.9% | Stable | - | - |
## Flagged Issues
### 1. {metric_name}: {classification}
- **Current**: {value} | **Baseline**: {value} | **Change**: {pct}%
- **Threshold**: {threshold} | **Headroom**: {pct}%
- **Rate of change**: {pct}% per week
- **Projected breach**: ~{N} weeks at current rate
- **Anomalous runs**: {run_ids with dates, if any}
## Threshold Recommendations
| Metric | Current Threshold | Recommended | Rationale |
|--------|-------------------|-------------|-----------|
| http_req_duration | p(95)<500 | p(95)<420 | Current P95 is 380ms; tightening to 420ms gives 10% headroom from current performance while surfacing further degradation early |
## Suggested Next Steps
- [ ] **Investigate service side**: P95 latency has increased 52% -- consider
loading `debug-with-grafana` to check service health
- [ ] **Deep-dive run {run_id}**: anomalous P95 spike on {date} -- consider
loading `k6-cloud-investigate-test` for this run
- [ ] **Tighten thresholds**: 2 metrics have >30% headroom that could be
tightened -- consider loading `k6-test-maintenance` to apply changes
整体健康状况分类
根据最差指标判定整体健康状况:
- 健康:所有指标均稳定或有所改善,余量充足或足够
- 关注:至少一个指标正在恶化,但余量仍足够
- 退化:至少一个指标正在恶化,且余量不足
- 严重:至少一个指标已超出阈值,或多个指标正在恶化且余量不足
阈值建议
建议调整阈值时,请遵循以下原则:
- 只收紧,绝不放宽,除非用户提出要求。放宽阈值会掩盖问题。
- 将目标设为高于指标近期 P95 10-20% 的余量。为正常波动留出足够空间,同时也能及时发现真正的性能退化。
- 使用后半段均值作为基线,而非最近一次运行的结果(它可能是异常值)。
- 尊重用户意图:如果当前阈值非常宽松(余量 >100%),可能是有意设置的。可以指出收紧阈值的机会,但不要过度坚持——用户了解自己的情况。
- 考虑分组阈值:如果测试使用基于标签的阈值(例如,
http_req_duration{name:homepage}),并且不同端点的趋势有所差异,则建议为各端点分别设置阈值。
注意事项
| 问题 | 详情 |
|---|---|
| 零观测值阈值 | 在 k6 中,没有观测值的阈值默认通过。如果某项指标看似通过,但没有数据,请标记出来——该阈值实际上并未被评估。 |
| 不同运行中的指标类型变化 | 如果某项指标的类型在不同运行之间发生变化(例如脚本重构),多运行聚合端点会使用最新类型。使用错误的方法查询早期运行时会返回空结果。如果检测到这种情况,请标记出来。 |
| 未完成的运行会扭曲趋势 | 中止或超时的运行通常持续时间较短、迭代次数较少,因此产生的指标值缺乏代表性。默认将其排除。 |
| LG 资源指标 | load_generator_cpu_percent 和 load_generator_file_handles 呈上升趋势,可能意味着测试超出了负载生成器的资源分配,而非服务性能正在下降。请单独指出这一点。 |
| 速率指标的方向 | 对于 ratio 类型的速率指标(例如检查通过率),“性能下降”意味着数值降低(通过次数变少),这与延迟指标的方向相反。 |