Skip to content

go_profile

Xiaolin Zhang edited this page Nov 2, 2019 · 1 revision

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

Clone this wiki locally