首页
/ Kubernetes 调度器性能剖析:使用 traceparse 从 Go 执行轨迹中量化 GC 开销

Kubernetes 调度器性能剖析:使用 traceparse 从 Go 执行轨迹中量化 GC 开销

2026-09-06 19:06:19作者:田桥桑Industrious

traceparse 是 Kubernetes 仓库 hack/tools 下附带的一个轻量命令行工具,用于从 Go 执行轨迹文件(.trace.out)中汇总垃圾回收(GC)活动,帮助开发者快速定位调度器等热路径上的分配压力与 STW 停顿。本文以仓库内 hack/tools/traceparse/README.md 为主体,结合 main.go 实现scheduler_perf 基准测试文档 展开,读完后你将掌握如何构建并运行 traceparse、解读其输出表中每一行指标的业务含义,并能在 Kubernetes 调度器性能基准的前后对比中把它当作一把"分配回归检测尺"。

traceparse 是什么

traceparse 汇总来自 Go 执行轨迹文件(.trace.out)的 GC 活动,这类文件通常由两种方式产生:

  • go test -trace:Go 标准测试命令原生支持为单测/基准测试写入执行轨迹;
  • scheduler_perf 中的 -perf-trace 标志:Kubernetes 调度器性能基准套件在跑每个测试时单独输出一份 execution trace(含 GC 事件)。

main.go 的文件头注释中,工具定位被描述得很清楚:对每个文件,它输出——GC 周期数、GC 并发标记阶段总耗时、GC mark-assist 事件数量与总耗时(即 goroutine 因分配速度超过后台标记而被中断去协助 GC 的次数)、按类型区分的 stop-the-world 停顿数量与总耗时,以及运行期间的平均存活堆大小。

一句话概括其用途:它把一条庞大且难以直接阅读的 Go 执行轨迹,压成一张"按墙上时钟总耗时排序的命名区间事件表",让你一眼看出 GC 把时间花在了哪里。

构建与运行

在 Kubernetes 仓库根目录下执行两条命令即可完成构建并运行:

go -C hack/tools build -o /tmp/traceparse ./traceparse
/tmp/traceparse <trace-file> [<trace-file> ...]

说明:

  • go -C hack/tools 让 Go 先切换到独立模块 hack/tools/go.mod(模块路径为 k8s.io/kubernetes/hack/tools),再在该模块上下文中编译;-o /tmp/traceparse 将产物输出到 /tmp,避免污染仓库。
  • 工具支持一次传入多个轨迹文件,逐个处理、逐个打印结果。若某个文件解析失败,会打印 path: error 到 stderr 并以非零退出码结束,但其余文件仍会继续处理——这一点可以在 main.gomain() 循环中看到。
  • 不带任何参数时打印 usage: traceparse <trace-file> [<trace-file> ...] 并退出码 1(见 main.go)。

在 scheduler_perf 场景下,运行完基准后用两个轨迹文件做前后对比,是官方推荐的用法:

/tmp/traceparse \
  /tmp/perf-data/before-trace.out \
  /tmp/perf-data/after-trace.out

输出格式

对每个文件,traceparse 先打印一张按总墙上时钟耗时降序排列的命名区间事件表,随后打印平均存活堆大小:

=== path/to/foo-trace.out ===
range                                                    count  total duration
-----                                                    -----  --------------
GC concurrent mark phase                                    47  5.662s
GC mark assist                                           14294  743.936ms
GC incremental sweep                                    161077  321.769ms
stop-the-world (GC sweep termination)                       47  7.568ms
stop-the-world (read mem stats)                             42  5.446ms
stop-the-world (GC mark termination)                        47  2.221ms
stop-the-world (start trace)                                 1  1.792µs

avg live heap: 796 MB  (3827825 samples)

输出中的每列含义如下:

含义
range 命名区间事件的名称(即某个 GC 阶段或停顿的标签)
count 该区间被进入的次数(也是结束配对的次数)
total duration 该区间所有实例的墙上时钟耗时总和,按降序排列,最耗时的排在最前

最后一行 avg live heap 的 MB 数值来自运行时指标采样:heapLiveTotal / 采样次数 / 1024 / 1024,括号内为参与平均的采样点数。

scheduler_perf 分析中的关键指标

这是本工具最有价值的输出面。原 README 给出的对照表完整罗列如下:

Range What it tells you(它告诉你什么)
GC concurrent mark phase 总 GC 工作量;更少的周期/更少的时间 = 更小的分配压力
GC mark assist 最敏感的指标:goroutine 在调度中途被打断去协助 GC,因为分配速度超过了后台 GC 的标记速度;高计数意味着热路径上的分配正在限制吞吐量
stop-the-world (GC sweep termination) 会让所有 goroutine 停下的 STW 停顿;直接影响调度尾延迟
stop-the-world (GC mark termination) 每个 GC 周期的第二次 STW 停顿;通常很短
avg live heap 工作集大小;驱动 GC 触发频率

其中 GC mark assist 是官方反复强调的诊断抓手。在 test/integration/scheduler_perf/README.md 中有明确论述:它统计的是"因分配速度超过后台 GC 标记速度而被中断的 goroutine 次数",若调度热路径上的该计数(或总耗时)很大,会直接封顶吞吐量。因此做改动前后的对比时,mark assist 行的异常增长通常就意味着这次改动引入了热路径分配回归。

结合源码看它如何工作

三种作用域的多路区间跟踪

Go 执行轨迹中的"区间事件"(Range event)分为不同的资源作用域。理解这一点是读懂实现的关键。main.go 用三个 map 分别跟踪当前"未闭合"的区间:

goroutineRanges := map[trace.GoID]inFlight{} // 每个 goroutine 各自的未闭合区间
procRanges := map[trace.ProcID]inFlight{}    // proc 作用域的未闭合区间(如 GC sweep)
singletonRange := map[string]trace.Time{}    // 既非 goroutine 也非 proc 的区间(如 GC 并发标记阶段)
  • goroutine 作用域(如 GC mark assist):每条 goroutine 被分配一个 GoID,即便多个 goroutine 同时在协助 GC,各自独立的 inFlight 条目也能让耗时正确累加、互不干扰。注释里特别解释了"last-writer-wins"在这种非重叠区间上是安全的。
  • proc 作用域(如 GC incremental sweep):以 ProcID 为键跟踪。
  • 全局单例作用域(如 GC concurrent mark phase):整个进程同一时刻只有一个,用名字直接映射开始时间。

事件处理采用标准的"开区间/闭区间配对"逻辑:读到 trace.EventRangeBegin 时登记开始时间并把 rangeCounts[name]++;读到 trace.EventRangeEnd 时查找对应作用域的开始时间,累加 ev.Time() - startrangeDurs,随后删除该条目(见 main.go)。

平均存活堆的采样来源

avg live heap 并非估算,而是来自执行轨迹中的运行时指标事件(trace.EventMetric)。工具只关注名为 /memory/classes/heap/objects:bytes 的指标——即"存活对象 + 尚未被 GC 清扫的死亡对象"所占用的字节数——将其逐次累加并计数,最后除以采样数得到平均字节数,再换算成 MB(见 main.go)。该指标名对应 Go runtime/metrics/memory/classes/heap/objects:bytes 的标准定义,轨迹中每次指标采样都会产生一条这样的 EventMetric。

输出排序与表头

处理完所有事件后,代码把累计结果打包成 {name, count, dur} 结构,按 dur 降序排序,然后以宽度对齐的格式打印表头 range / count / total duration 与各数据行(见 main.go)。若运行期间一条 heap 指标都没采样到,则省略 avg live heap 一行。

在 scheduler_perf 全流程中的位置

traceparse 是 scheduler_perf 性能工作流的收尾一环。完整的对比链路如下:

  1. 开启执行轨迹采集:在基准参数中加入 -perf-trace(该标志在 scheduler_perf.go 中注册,描述为 "write an execution trace for each test")。
  2. 输出轨迹文件:每个测试执行期间调用 runtime/tracetrace.Start(f) 开始记录、结束时 trace.Stop() 并关闭文件,文件名以 -trace.out 结尾(见 executor.go)。配合 -data-items-dir=/tmp/perf-data 可以把所有数据文件集中到同一目录,方便批量对比。
  3. 用 traceparse 汇总:对 beforeafter 两次运行产生的轨迹文件运行 traceparse,逐行比对 GC 区间统计。

一个完整的基准运行示例(来自 scheduler_perf README):

rm -rf /tmp/perf-data
numactl --physcpubind=0-5 make test-integration WHAT=./test/integration/scheduler_perf/misc KUBE_CACHE_MUTATION_DETECTOR=false KUBE_TIMEOUT=-timeout=1h ETCD_LOGLEVEL=warn KUBE_TEST_VMODULE="''" FULL_LOG=y ARTIFACTS=/tmp SHORT=-short=false KUBE_TEST_ARGS='-run=^$ -benchtime=1x -bench=BenchmarkPerfScheduling -perf-cpuprofile -perf-memprofile -perf-blockprofile -perf-mutexprofile -perf-trace -data-items-dir=/tmp/perf-data'

值得注意的实践约束:scheduler_perf 文档明确提示,同时开启全部 profile 与 trace 会使平均调度吞吐量下降约 8%–10%(该观察基于 test/integration/dra),因此只能对比参数完全一致的两次运行,否则结论会被采集开销本身污染。

如果 GC 行为需要进一步深挖,scheduler_perf 还提供了辅助手段:以 GODEBUG=gctrace=1 FULL_LOG=y 运行,抓取 stdout/stderr 后用 grep -e "metrics collection" -e '^gc' 过滤;位于 "Started metrics collection" 与 "Stopped metrics collection" 之间的 GC 日志才是真正与性能测量区间对应的部分。

典型使用场景小结

结合仓库内文档与源码,traceparse 在 Kubernetes 开发中的典型用法可归纳为:

  1. 调度器改动前后的 GC 回归检测:同参数跑两轮 scheduler_perf(改动前/改动后),各留一份 -trace.out,用 traceparse 一次性对比,重点关注 GC mark assiststop-the-world (GC sweep termination) 两行。
  2. 热路径分配压力定位:当 avg live heap 变大时,GC 触发会变得更频繁,配合 GC concurrent mark phase 总耗时上升,即可判定"工作集膨胀"而不仅仅是"分配次数变多"。
  3. 面向延迟的调优:通过 stop-the-world 两行(sweep termination 与 mark termination)评估变更对调度尾延迟的影响,这两类停顿会冻结全部 goroutine。

整体而言,traceparse 把"Go GC 在调度热路径上是否拖后腿"这一抽象问题,转化为一张可读、可排序、可做前后 diff 的量化表格,是 Kubernetes 调度器性能工程中低成本高回报的分析工具。

延伸阅读

若需深入了解上下文,可继续在仓库内查阅:

登录后查看全文
热门项目推荐
相关项目推荐