Go pprof 和 火焰图

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

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

pprof 实现

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

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

对一个endpoint进行cpu pprof

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转化成火焰图