最常见错误是将 os.Stdout 或带日志前缀的 io.Writer 传给 trace.Start(),因 runtime/trace 写二进制格式,混入文本会破坏 magic number;必须用 os.Create("trace.out") 获取原始 *os.File,且确保 trace.Stop() 执行以 flush 缓冲并写 footer,否则文件残缺。

trace.Start() 传错参数导致 trace.out 无法解析
最常见错误是把 os.Stdout 或带日志前缀的 io.Writer 传给 trace.Start()。runtime/trace 写的是二进制格式,混入任何文本(比如 log 输出)都会破坏 magic number,go tool trace 直接报 unknown magic number。
必须用 os.Create("trace.out") 显式创建文件,且传入原始 *os.File;不能传路径字符串、不能包装成 log.New(f, "", 0)、也不能用 bytes.Buffer 临时中转。
-
trace.Start(f)中的f必须是未被其他 goroutine 并发写入的独立文件句柄 - 如果程序本身已有日志输出到 stdout/stderr,别试图“复用”它们——哪怕只是加个
io.MultiWriter(f, os.Stderr)也会让 trace 文件失效 - 验证方式:运行
file trace.out,输出必须是Go execution trace,不是data或empty
trace.Stop() 没执行,trace.out 只有 header 或截断
trace.Stop() 不只是“关开关”,它负责 flush 缓冲区、写 footer、关闭底层 writer。一旦跳过,文件就残缺——go tool trace 报 EOF 或干脆无事件。
这些情况会让 defer trace.Stop() 失效:
-
os.Exit(0)或log.Fatal():直接终止进程,不走 defer 链 -
panic()发生在trace.Start()之前:整个 trace 初始化失败,静默无文件 - 在
init()里调trace.Start():runtime 尚未 ready,调用无效,trace.out是空文件 - main 函数末尾没留出缓冲 flush 时间:即使写了
defer trace.Stop(),若程序秒退,buffer 可能来不及落盘
可靠写法是:先 os.Create,再 trace.Start(f),再 defer trace.Stop(),最后确保主逻辑至少触发一次调度(如 time.Sleep(100 * time.Millisecond) 或启动一个 goroutine)。
trace 不是 pprof,别指望它查函数耗时
runtime/trace 记录的是运行时事件:goroutine 创建/阻塞/唤醒、GC 周期、系统调用进出、网络/同步原语阻塞点。它不采样 CPU 时间,也不记录函数调用栈。
想定位 HTTP handler 执行慢?该用 pprof.StartCPUProfile;想知道哪行代码分配了最多内存?查 pprof.Lookup("heap");想确认 goroutine 是否卡在 channel 上?trace 的 Goroutine view 能看到 block event,但看不出具体是哪一行 channel 操作。
- trace 的价值在于“看见并发行为”:比如大量 goroutine 在
select{ case 上阻塞,或频繁 sysmon 抢占导致调度延迟 - 它对高并发瓶颈诊断有效,前提是瓶颈体现为调度失衡、锁竞争、系统调用堆积或 GC 频繁——而不是单个函数计算慢
- 如果 trace.out 里只有
ProcStart和GCStart两个事件,说明程序纯计算、无阻塞、无调度,trace 对你当前问题没帮助
采集窗口太短或数据量失控,反而掩盖问题
trace.Start() 默认每 ~10ms 采样一次,但只在发生调度/GC/I/O 等事件时才写数据。窗口太短(如 time.Sleep(10 * time.Millisecond))可能只录到头尾,看不出阻塞链;窗口太长(如全程 trace)则文件暴涨,尤其高频 syscall 场景下每秒 MB 级,磁盘 I/O 成新瓶颈。
生产环境建议:
- 用
time.AfterFunc(30 * time.Second, trace.Stop)主动限窗,避免无限增长 - 不要在高 QPS 服务里反复
trace.Start()/trace.Stop():频繁开闭文件 + buffer 分配会引入可观测延迟 - 若目标是查某次请求的调度路径,应在 handler 入口 start、出口 stop,并确保该请求触发了真实阻塞(如 DB 查询、HTTP 调用)
- trace 文件大小 >1KB 才算基本可用;小于 500 字节基本可判定采集失败
真正难的不是启动 trace,而是判断什么时候该用它——它只回答“为什么并发没跑起来”,不回答“为什么这段代码慢”。

















