Go pprof 和 火焰图

一直对性能优化感兴趣, 但是没有入手的点, 转到GO之后, 这些就不是问题了.

Go 提供两个工具, 一个在net包下, 一个在pprof包下, net包通过启动一个网络服务, 对外暴露你程序信息, 而pprof提供一种编程的方式, 从你指定时间开始到指定时间结束, 把信息记录到一个writer中.

重要更新

现在可以获得一个交互式的前端页面, 不需要下面那么繁琐去获得火焰图

go tool pprof -http=:8080 profile_10.10.73.173_20181206-070730.pb.gz

pprof 实现

具体实现我没看过, 但是猜一下, 应该是通过采样的方式给cpu快照, 读取寄存器的状态,

快照这一点很重要, 尤其是一个大型web服务, 必须通过压测才能准确的记录下来, cpu执行的代码, 否者看到的都是runtime的一些方法.

对一个代码片段进行profile

func Handler(meta endpoint.Meta, next endpoint.Endpoint) endpoint.Endpoint {
    profMapLock.Lock()
    defer profMapLock.Unlock()
    traceBytes := []byte{}
    tracePtr := &traceBytes

    var lock sync.Mutex
    var buf bytes.Buffer
    var traceBuf bytes.Buffer
    var hasMerged bool

    setEnabled()

    // Prepare buffers for this endpoint
    merged := new(profile.Profile)
    profKey := meta.Service + "." + meta.Name
    profMap[profKey] = &profileDump{
        profile: merged,
        trace:   tracePtr,
    }
    profWriter := bufio.NewWriter(&buf)
    traceWriter := bufio.NewWriter(&traceBuf)

    return func(ctx context.Context, request interface{}) (metadata metadata.Metadata, response interface{}, err error) {

        // Lock to ensure only one endpoint is profiled at a time
        lock.Lock()
        defer lock.Unlock()

        // Profile just this endpoint
        if err := pprof.StartCPUProfile(profWriter); err != nil {
            // If already enabled, don't profile
            return next(ctx, request)
        }

        if err := trace.Start(traceWriter); err != nil {
            // If already enabled, don't trace
            return next(ctx, request)
        }

        metadata, response, err = next(ctx, request)

        trace.Stop()
        _ = traceWriter.Flush()

        pprof.StopCPUProfile()
        _ = profWriter.Flush()

        *tracePtr = traceBuf.Bytes()

        // Parse and merge profiles ready for converting to flamegraph
        // This is done eagerly but could be deferred until the flamegraph is called
        thisProf, parseErr := profile.Parse(bufio.NewReader(&buf))

        buf.Reset()
        traceBuf.Reset()

        if parseErr == nil {
            if hasMerged {
                m, merr := profile.Merge([]*profile.Profile{merged, thisProf})
                if merr == nil {
                    *merged = *m
                }
                return
            }
            *merged = *thisProf
            hasMerged = true
        }

        return  
    }
}

上面是一个middleware实现, 最终cpu信息会记录到profMap中. (要多次调用接口才能看到效果).

火焰图

1.首先,我们要配置FlameGraph的脚本

FlameGraph 是profile数据的可视化层工具,已被广泛用于Python和Node

git clone https://github.com/brendangregg/FlameGraph.git

2.检出完成后,把flamegraph.pl拷到我们机器环境变量$PATH的路径中去

3.在终端输入 flamegraph.pl -h 是否安装FlameGraph成功

诸如flamegraph.pl是pl写的分析脚本, 就是用来生成火焰图的, 在调用他之前, 还要用stackcollapse-go.pl专门处理一下Go语言的数据.

go-touch提供更方便的方式, 直接可以将cpu.prof转化成火焰图