闽公网安备 35020302035485号
Flight Recording (飞行记录): 飞行记录是一种更精妙的跟踪方法。它不像传统跟踪那样捕获所有内容,而是在一个循环缓冲区中维护最新的执行数据。这意味着它只保留最近的程序活动,并自动丢弃较旧的信息,以节省空间并显著减少开销。这种方法特别适合在生产环境中持续运行,因为它只会产生一个大小可控的跟踪文件。
package main
import (
"log"
"net/http"
"time"
)
// heavyLoad 模拟 CPU 密集型任务
func pow(targetBits int) [32]byte{
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
var hashInt big.Int
var hash [32]byte
nonce := 0
for {
data := "hello world " + strconv.Itoa(nonce)
hash = sha256.Sum256([]byte(data))
hashInt.SetBytes(hash[:])
if hashInt.Cmp(target) == -1 {
break
} else {
nonce++
}
if nonce%100 == 0 {
runtime.Gosched()
}
}
}
func handler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/heavy" {
heavyLoad()
}
w.Write([]byte("Hello, world!"))
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
2. 配置并启动 Trace Flight Recorderpackage main
import (
"log"
"net/http"
"time"
"runtime/trace"
"context"
"os"
)
var recorder *trace.FlightRecorder
// ... 前面定义的pow函数和handler ...
// 堆代码 duidaima.com
func main() {
// 配置 Trace Flight Recorder
cfg := trace.FlightRecorderConfig{
MinAge: 5 * time.Second, // 至少保留 5 秒
MaxBytes: 3 * 1024 * 1024, // 最大 3 MB
}
recorder = trace.StartFlightRecorder(cfg)
defer recorder.Stop()
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
3. 实现性能触发器并保存跟踪文件package main
import (
"log"
"net/http"
"time"
"runtime/trace"
"os"
)
// ... 前面定义的pow函数 ...
func handlerWithTrigger(w http.ResponseWriter, r *http.Request) {
hash := pow(rand.Intn(20) + 10) // 随机选择难度在 10 到 30 之间
if strings.HasPrefix(hash, "000000") {
// 请求耗时过长,保存跟踪快照
file, err := os.Create("trace.out")
if err != nil {
log.Println("failed to create trace file:", err)
return
}
defer file.Close()
// 保存最新的跟踪数据到文件
if _, err := recorder.WriteTo(file); err != nil {
log.Println("failed to write trace data:", err)
}
}
w.Write([]byte(hash))
}
func main() {
// 配置 Trace Flight Recorder
cfg := trace.FlightRecorderConfig{
MinAge: 5 * time.Second, // 至少保留 5 秒
MaxBytes: 3 * 1024 * 1024, // 最大 3 MB
}
recorder = trace.NewFlightRecorder(cfg)
if err := recorder.Start(); err != nil {
log.Fatalf("failed to start FlightRecorder: %v", err)
}
defer recorder.Stop()
http.HandleFunc("/", handlerWithTrigger)
log.Fatal(http.ListenAndServe(":8080", nil))
}
在上面的例子中,我们使用 WriteTo 函数将循环缓冲区中的最新跟踪数据写入到 trace.out 文件中。go tool trace trace.out这个命令会打开一个浏览器页面,展示丰富的可视化数据(trace event),帮助你理解程序的执行情况。你可以看到:

内存受限环境: 由于其低开销和可控的缓冲区大小,它非常适合在资源有限的设备上进行性能分析。
package trace
type FlightRecorder struct {
...
}
func NewFlightRecorder() *FlightRecorder
func (*FlightRecorder) SetMinAge(d time.Duration)
func (*FlightRecorder) MinAge() time.Duration
// 这个设置优先于SetMinAge
func (*FlightRecorder) SetMaxBytes(bytes uint64)
func (*FlightRecorder) MaxBytes() uint64
func (*FlightRecorder) Start() error
func (*FlightRecorder) Stop() error
func (*FlightRecorder) Enabled() bool
func (*FlightRecorder) WriteTo(w io.Writer) (n int64, err error)
如果你在 Go 代码库中搜索 trace.ok()关键字,会看到很多跟踪的代码:
func (tl traceLocker) ProcSteal(pp *p, inSyscall bool) {
// Grab the M ID we stole from.
mStolenFrom := pp.trace.mSyscallID
pp.trace.mSyscallID = -1
if !pp.trace.statusWasTraced(tl.gen) && pp.trace.acquireStatus(tl.gen) {
tl.writer().writeProcStatus(uint64(pp.id), tracev2.ProcSyscallAbandoned, pp.trace.inSweep).end()
}
goStatus := tracev2.GoRunning
procStatus := tracev2.ProcRunning
if inSyscall {
goStatus = tracev2.GoSyscall
procStatus = tracev2.ProcSyscallAbandoned
}
tl.eventWriter(goStatus, procStatus).event(tracev2.EvProcSteal, traceArg(pp.id), pp.trace.nextSeq(tl.gen), traceArg(mStolenFrom))
}
这个方法是 Go 运行时跟踪系统(runtime/trace)的一部分,它负责记录 Go 调度器中一个非常重要的事件:P 偷取(P stealing)。这个 Go 代码片段是 Go 运行时跟踪系统(runtime/trace)的一部分,它负责记录 Go 调度器中一个非常重要的事件:P 偷取(P stealing)。4.发出主跟踪事件
tl.eventWriter(goStatus, procStatus).event(...): 这是最后一步,也是最重要的。它使用之前确定的状态,记录主事件 EvProcSteal。