go 的 runtime/trace 不是函数级 profiler,仅记录运行时事件;需显式调用 trace.start/stop、留出采集窗口并避免 init 中启动,才能生成有效 trace.out 文件。

Go 的 runtime/trace 不是函数级 profiler,它只记录运行时事件(goroutine 调度、GC、系统调用、channel 阻塞等),想靠它看单个函数耗时?会白忙一场。
为什么 trace.Start() 后打开 go tool trace 显示 “No traces found” 或 EOF?
根本原因不是浏览器或命令写错,而是 trace 文件本身无效——常见于:
• trace.Stop() 没执行(比如 os.Exit(0) 或 panic 提前退出,defer trace.Stop() 失效)
• 程序运行时间太短(ProcStart 或 GCStart,没触发实际调度事件
• 用了 log.Writer() 或 os.Stdout 接 trace.Start(),混入日志导致二进制损坏
• 文件写在 NFS 或慢盘上,写入延迟污染采样,甚至写不全
验证方式:ls -l trace.out,大小应 > 1KB;若只有几十字节,说明 trace.Stop() 根本没跑完。
如何稳定生成可解析的 trace.out 文件?
最小可靠写法必须满足三个硬条件:显式文件句柄、强制留出采集窗口、避免 runtime 未就绪时启动。
• trace.Start() 必须传 *os.File,不能传路径字符串或 io.MultiWriter
• trace.Stop() 前必须确保有 goroutine 切换或 I/O —— 单纯 time.Sleep() 是最简单有效的手段
• 绝对不要在 init() 函数里调 trace.Start(),此时 runtime 尚未初始化,会静默失败
推荐模板:
package main
<p>import (
"os"
"runtime/trace"
"time"
)</p><p>func main() {
f, _ := os.Create("trace.out")
defer f.Close()</p><pre class="brush:php;toolbar:false;">_ = trace.Start(f)
defer trace.Stop()
go func() {
time.Sleep(10 * time.Millisecond)
}()
time.Sleep(200 * time.Millisecond)}
go tool trace 打开后看不到 handler 或业务逻辑?
runtime/trace 不理解业务语义,它不会自动标记 HTTP handler 入口、DB 查询开始点等。你看到的全是底层运行时事件:running/blocking 状态、network blocking fd、synchronization blocking mutex —— 但没有 “/api/user” 这类标签。
补救方法只有手动打点:
• 用 trace.WithRegion(ctx, "http-handler") 包裹 handler 主体,结尾调 region.End()
• 关键节点用 trace.Log(ctx, "db-query", "start"),这些会出现在 Events 视图里
• 注意 trace.Log() 开销约 50ns,高频循环里慎用
trace 和 pprof 该用哪个?
它们解决不同问题,不能互相替代:
• go tool trace 适合查“谁被卡住了”:goroutine 长期 running 但实际在等 sysread?大量 goroutine 堆在 channel receive 上?GC STW 时间异常?
• pprof(尤其是 cpu.pprof)才适合查“哪段代码吃 CPU”:函数调用栈、热点行号、火焰图
线上排查卡顿,建议两者同时采:先用 go tool trace 定位阻塞类型(比如发现 80% goroutine block 在 select),再用 pprof 看对应 goroutine 正在执行哪一行。
最容易被忽略的是:trace 文件生成后必须用 go tool trace trace.out 启动本地服务查看,直接双击 .out 文件或拖进浏览器会失败;且 Go 版本要匹配——1.21+ 生成的 trace 无法用 1.20 的 go tool trace 解析。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











