官方文档 - 原文

引言

  Go生态系统提供许多适合API和工具来诊断Go程序的逻辑和性能问题。这篇文章主要分析总结存在的工具,帮助Go开发人员选择正确的工具来解决自己的问题。

  解决方案可以可以被分类为下面几种情况:

  • 性能图表:Profiling这个工具分析复杂的Go程序的花销,比如程序内存使用和方法调用频率来辨认出,GO程序昂贵的部分。
  • 追踪:Tracing是一种检测代码的方法,用于分析呼叫或用户请求的整个生命周期中的延迟。Tracing提供。跟踪概述了每个组件对系统总体延迟的影响。它可以跨越多个Go进程。
  • 调试:调试允许我们暂停Go程序并检查执行结果。程序状态和流可以通过Debug验证。
  • 运行时事件统计:收集和分析运行时状态和事件提供一个Go程序健康状态高等级概述。指标的尖峰/下降有助于我们识别吞吐量,利用率和性能的变化。

注意:某些诊断工具可能会相互干扰。 例如,精确的内存分析会扭曲CPU配置文件,而goroutine阻塞分析会影响调度程序跟踪。 单独使用工具可获得更精确的信息。

性能分析(Profiling)

  图表对于识别昂贵或经常调用的代码段很有用。Go在运行时,提供性能数据并通过性能可视化工具格式化为预期的格式。性能数据也可以在测试时(go test)收集或者通过网络的pprof包来收集。用户需要收集性能数据并使用工具过滤以及可视化代码路径。

  通过runtime/pprof包提供预定义的配置文件:

  • cpu:CPU图表确定程序花费时间主动消耗CPU周期(睡眠或等待I/O相反)。
  • heap:Heap图表报告内存分配示例;被用来监控当前和历史内存使用情况,并检查内存泄露。
  • threadcreate:线程创建图表报告程序引导创建系统新线程的部分
  • goroutine:Goroutine图表报告追踪所有goroutines的堆
  • block:阻塞图表显示Goroutine阻塞来等待同步原语(包括时间channel)。阻塞图表默认没有开启;使用runtime.SetBlockProfileRate来开启它。
  • mutex:锁图表报告锁定争用。当你认为因为锁争用,导致你的CPU没有充分利用,使用这个图表。默认它也没有启用,使用runtime.SetMutexProfileFraction来启用它。

我可以使用哪些其他分析器来Go程序图表?

  在Linux系统中,perl工具可以被用来分析Go程序图表。Perf可以剖析和展开cgo/SWIG代码和内核,因此深入了解本机/内核性能瓶颈非常有用。 在macOS上,Instruments套件可以使用profile Go程序。

我可以在线上产品生成图表吗?

  可以。它是安全的,但是开启一些图表(比如CPU图表)会增加开销。应该预期到服务器性能降低。在放到线上产品之前应该测量性能降低的开支。

  你可能希望周期性地执行生产图表在你的产品服务上。尤其是许多单个过程有多个副本,选择一个随机副本周期性执行是一个安全选项。选择一个生产过程,每Y秒对其进行一次X秒的分析,保存结果以进行可视化和分析;然后重复周期,结果可能手动或自动检查来发现问题。收集的图表之间可以交互,所以它推荐同一时间只收集单个图表。

可视化图表数据的最好方式?

  Go工具提供了文本、图 和 callgrind可视化概括数据使用 go tool pprof,阅读Profiling Go Programs 博客看他们的行动。

图片 文本显示开销最大的调用

图片 可视化图谱显示开销最大调用

  Weblist视图用html显示一行一行源码开销。下面的示例中,530ms花费在了runtime.concatstrings方法上,并且每一行的开销都呈现在下面的列表中。

图片 weblist显示开销最大调用

  还有一种方式显示profile数据是火焰图。火焰图允许你移动到原始的起源,所以你可以放大缩小代码明确的部分。上游pprof支持火焰图。

图片 火焰图提供可视化点开销最大调用

我需要限制内置的profiles吗?

  除了运行时提供的内容之外,Go用户还可以通过pprof.Profile创建自定义配置文件,并使用现有工具对其进行检查。

我可以在不同的路径和端口上提供探查器处理程序(/debug/pprof/…)吗?

  可以。net/http/pprof包默认情况下,将其处理程序注册到默认的mux,但您也可以使用从包中导出的处理程序自己注册它们。

比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"log"
	"net/http"
	"net/http/pprof"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
	log.Fatal(http.ListenAndServe(":7777", mux))
}

追踪(Tracing)

  跟踪是一种检测代码的方法,用于分析一系列调用的整个生命周期中的延迟。Go提供golang.org/x/net/trace包作为每个Go节点的最小跟踪后端,并提供带有简单仪表板的最小检测库。Go也提供一个执行跟踪器来跟踪一个时间间隔内运行时的事件。

  跟踪使我们能够:

  • 在Go过程中检测和分析应用程序延迟
  • 测量调用长链中具体调用的开销
  • 图形化显示利用和性能的改善。没有跟踪数据,瓶颈并不总是显而易见的。

  在单片巨大系统中,从程序的构建块收集诊断数据相对容易。所有的模块生存在一个程序并且共享相同模块比如logs,errors和其他诊断信息。一旦您的系统超出单个进程并开始分布式,从前端Web服务器到其所有后端的呼叫将变得更加困难,直到将响应返回给用户。这就是分布式跟踪在仪器和分析生产系统方面发挥重要作用的地方。

  分布式跟踪是一种检测代码的方法,用于分析用户请求的整个生命周期中的延迟。分布式系统并且传统的分析和调试工具无法扩展时,您可能希望使用分布式跟踪工具来分析用户请求和RPC的性能。

  跟踪使我们能够:

  • 检测和分析应用等待时间在一个大型系统中。
  • 跟踪用户请求生命周期内的所有RPC,并查看仅在生产中可见的集成问题。
  • 找出可应用于我们系统的性能改进。 在收集跟踪数据之前,许多瓶颈并不明显。

  Go生态系统提供许多分布式追踪库每个追踪系统和不可知后端。

有没有办法自动拦截每个函数调用并创建跟踪?

  Go不提供一种方式自动拦截每个函数调用来创建跟踪。您需要手动检测代码以创建,结束和注释跨度。

我应该如何跟踪Go库的头部

  你可以在context.Context跟踪标识符和tags。目前还没有行业中的规范跟踪密钥或跟踪标头的通用表示。 每个跟踪提供程序都负责在其Go库中提供传播实用程序。

为什么其他标准库或者运行时的低等级事件被包括在来trace中?

  标准库和运行时视图暴露多个额外APIs来提醒低等级内部事件。比如,httptrace.Client提供APIs来跟随低等级事件在传出请求的生命周期中。目前正在努力从运行时执行跟踪器中检索低级运行时事件,并允许用户定义和记录其用户事件。

调试(Debugging)

  debugging是用来辨别程序行为不端的过程。调试器允许我们明白程序执行流程和当前状态。这几种debug方式;这个章节会只关注将调试器附加到程序和核心转储调试。

  Go用户几乎使用下面的调试器: - Delve:Delve是Go语言调试器。它支持Go运行时和内置类型。Delve试图成为Go程序全功能可靠调试器。 - GDB:Go通过Go编译器和Gccgo提供GDB支持。堆管理、线程和运行时包含与执行模型不同的方面GDB期望它们可以混淆调试器,即使使用gccgo编译程序也是如此。尽管GDB可用于调试Go程序,但它并不理想,可能会产生混淆。

我们怎么左可以让调试器很好地调试Go程序?

  gc编译器对方法内联和bian l变量注册进行优化。这些优化有时让调试器调试变得更加困难。目前正在努力提高为优化二进制文件生成的DWARF信息的质量。直到这些提升前,我们建议在调试时禁用优化。下面的命令创建包时编译器不会优化:

1
go build -gcflags=all="-N -l"

  作为努力的一部分,Go 1.10介绍一个新的编译flag -dwarflocationlists。这个标签致使编译器添加本地lists帮助调试器在执行优化时也可以工作。接下来的命令创建包时同时优化并有DWARF本地lists。

1
go build -gcflags="-dwarflocationlists=true"

推荐的调试器用户界面?

  即使delve和GDB提供CLIs,大部分的编辑器和IDEs提供调试用户界面。

是否可以使用Go程序进行事后调试?

  一个核心转存文件包含运行的程序和它的执行状态的内存。它主要用户程序的事后调试,并在程序运行时了解它的状态。这两种情况使核心转储的调试成为一种良好的诊断辅助,可用于事后分析和分析生产服务。可以从Go程序获取核心文件并使用delve或gdb进行调试,请参阅核心转储调试页面以获取分步指南。

运行时统计和事件

  运行时提供状态和报告内部事件来诊断性能和使用问题。

  用户通过监控这些状态来更好地理解整体的健康和性能。一些经常监控的统计数据和状态:

  • runtime.ReadMemStats:报告 heap收集和垃圾收集。内存统计信息对于监视进程消耗的内存资源非常有用, 该过程是否可以很好地利用内存,并捕获内存泄漏。
  • debug.ReadGCStats: 读取垃圾收集统计。这对于统计GC暂停消耗多少资源非常有用。它还报告垃圾收集器暂停和暂停时间百分位数的时间线。
  • debug.Stack:返回当前堆栈。统计当前堆栈运行goroutines的数量统计,以及它们在做什么,它们是否阻塞。
  • debug.WriteHeapDump:悬挂执行的所有goroutines,允许你转存heap到文件。heap转存文件是给定时间Go程序内存快照。它包含所有分配的对象以及goroutines、终结器等。
  • runtime.NumGoroutine: 返回当前goroutines数量。监控是否有效利用goroutines的数量,或者检测goroutine泄露。

执行追踪器

  Go自带一个运行时执行追踪器来获取广泛的运行时事件。调度、系统调用、垃圾收集、堆栈大小和其他事件都会在运行时被收集,并且通过go tool trace实现可视化。执行追踪器是一个工具来检测等待期和利用问题。你可以检查CPU是否很好的利用,并且当网络或系统调用导致goroutine被抢占的原因。

  追踪器被用在:

  • 了解你的goroutines如何执行。
  • 了解一些核心运行时事件,比如GC运行。
  • 识别执行不良的并行化。

  然而,识别热点(如分析内存过多或CPU使用率的原因)并不是很好。 首先使用分析工具来解决它们。 图片

  上面,go工具跟踪可视化显示执行开始正常,然后它变得序列化。 它表明可能存在创建瓶颈的共享资源的锁争用。

  请参阅go tool trace以收集和分析运行时跟踪。

GODEBUG

  如果相应地设置了GODEBUG环境变量,运行时也会发出事件和信息。 - GODEBUG=gctrace=1 打印每次垃圾收集事件,总结内存收集数量和暂停的时间长度 - GODEBUG=schedtrace=X 打印每X毫米调度事件

  GODEBUG环境变量可用于禁用标准库和运行时中指令集扩展的使用。

  • GODEBUG=cpu.all=off 禁用所有可选扩展指令集
  • GODEBUG=cpu.extension=off 禁用具体扩展指令集

  extension是指令集扩展名的小写名称,例如sse41或avx。