mcpField
K
k6-trend-analysis

k6-trend-analysis

github.com/grafana测试与质量2952.7k 次安装1 天前更新
代码仓库

关于 k6-trend-analysis

用于分析多个 Grafana Cloud k6 测试运行结果、发现性能变化的技能。它检查指标趋势、脚本变更和阈值余量,在阈值或告警触发前指出退化;只报告发现,不应用变更。

主要功能

  1. 01比较多次测试运行的指标
  2. 02按请求标签分组趋势
  3. 03检测趋势拐点处的脚本变更
  4. 04计算阈值余量
  5. 05估算可能触发阈值的时间

适用场景

  1. 01在阈值触发前发现延迟退化
  2. 02定位导致性能回归的端点
  3. 03区分脚本变更与外部变化
  4. 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、ended
  • result(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

根据指标类型选择聚合方法:

指标类型首选方法捕获的内容
trendhistogram_quantile(0.95)P95 延迟——最常见的 SLO 目标
trendhistogram_quantile(0.5)中位数——体现典型表现
trendhistogram_avg均值——容易受离群值影响
counterincrease每次运行的总计数
rateratio成功/失败比率
gaugemax每次运行的峰值

对于 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:计算趋势

检测拐点,并以确定性方式排除脚本变更。 在对回归得出结论之前,先查找指标值中的不连续变化——即同一日期多个指标同时出现的突然上升或下降。发现此类变化时,不要猜测脚本是否变更。运行级脚本端点可以在几秒内给出确定答案:

  1. 对边界两侧的打包脚本计算 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 次运行共享另一个哈希,那么边界就很清晰。如果哈希在中途发生变化,则说明测试经过了多次编辑。

  2. 如果 sha256 不同,则测试脚本发生了变化——拐点可能是测试端的伪象,而非服务回归。请在边界处将分析拆分为不同阶段,并分别计算每个阶段内的趋势。跨脚本变更比较指标会产生误导性趋势——如果脚本只是停止访问某个响应缓慢的端点,那么 P95 从 3,000ms 降至 150ms 并不代表“有所改善”。在报告中明确说明各阶段,并将建议重点放在最近的阶段。

  3. 如果 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 类型的速率指标(例如检查通过率),“性能下降”意味着数值降低(通过次数变少),这与延迟指标的方向相反。

评论

支持 Markdown