简介
Go生态系统提供了一整套用于诊断Go程序中的逻辑和性能问题的API和工具。该篇文章总结了可用的工具,并帮助Go用户针对他们的特定问题选择合适的工具。
诊断解决方案可以分为以下几类:
- Profiling: Profiling工具分析Go程序的复杂性和成本,例如其内存使用情况和经常调用的函数,以识别Go程序的最耗费资源的部分。
- Tracing: Tracing是一种工具代码,可以在调用或用户请求的整个生命周期中分析延迟。跟踪提供了每个组件对系统总体延迟造成多少延迟的概述。跟踪可以跨越多个Go进程。
- Debugging: Debugging允许我们暂停Go程序并检查其执行。程序状态和流程可以通过调试来验证。
- Runtime statistics and events: 运行时状态和事件的收集和分析提供了Go程序运行状况的高级概述。指标的峰/谷有助于我们识别吞吐量,利用率和性能的变化。
注意:某些诊断工具可能会相互干扰。例如,内存分析会影响CPU profiles,goroutine 阻塞分析会影响调度程序跟踪。一次只使用一种工具以获得更精确的信息。
Profiling
分析对于识别消耗资源严重或经常调用的代码段很有用。 Go运行时以pprof可视化工具所需的格式提供概要分析数据。可以在测试过程中通过go test或 net/http/pprof 程序包中提供的端点收集性能分析数据。用户需要收集性能分析数据并使用pprof工具来过滤和可视化代码路径。
runtime/pprof 软件包提供的预定义profiles:
- cpu: CPU profile确定了程序在真实消耗CPU时间片(而不是在睡眠或等待I/O)所花费的时间。
- heap: heap profile报告内存分配样本;用于监视当前和历史内存使用情况,并检查内存泄漏。
- threadcreate: 线程创建profile报告了导致创建新OS线程的程序部分。
- goroutine: Goroutine profile报告所有当前goroutine的堆栈跟踪。
- block: Block profile显示goroutine在哪里阻塞同步原语(包括计时器通道)的等待。默认情况下未启用Block profile;使用runtime.SetBlockProfileRate启用它。
- mutex: Mutex profile报告锁争用。如果您认为由于互斥锁争用而无法充分利用CPU,请使用此profile。Mutex profile默认情况下未启用,请参见runtime.SetMutexProfileFraction启用它。
我还可以使用哪些其他分析器来分析Go程序?
在Linux上,可以使用perf工具来分析Go程序。 Perf可以分析和解散cgo /SWIG代码和内核,因此深入了解本机/内核性能瓶颈可能很有用。在macOS上,Instruments套件可用于profile Go程序。
我可以profile我的生产服务吗?
是。在生产环境中对程序进行概要分析是安全的,但是启用某些概要文件(例如CPU概要文件)会增加成本。您应该期望看到性能下降。可以通过在生产之前打开Profiler来测量Profiler的开销来估算性能损失。
您可能需要定期分析生产服务。特别是在具有单个进程多个副本的系统中,定期选择随机副本是一个安全的选择。选择一个生产过程,每隔Y秒将其概要分析X秒,并保存结果以进行可视化和分析;然后定期重复。可以手动或自动查看结果以发现问题。profile的收集可能会相互干扰,因此建议一次仅收集一个profile。
可视化分析数据的最佳方法是什么?
Go工具使用go tool pprof提供profile数据的文本,图形和callgrind可视化。阅读Profiling Go程序以查看它们的运行情况。
以文本形式列出最消耗资源的调用
将最消耗资源的调用可视化为图表
Weblist视图在HTML页面中逐行显示源代码最消耗资源的部分。在下面的示例中,运行时花费了530毫秒。runtime.concatstrings和清单中列出了每行的成本。
将最消耗资源的调用可视化为weblist
可视化轮廓数据的另一种方法是火焰图。火焰图使您可以在特定的祖先路径中移动,因此可以放大/缩小代码的特定部分。上游pprof支持火焰图。
我是否限于内置profiles?
除了运行时提供的功能外,Go用户还可以通过pprof.Profile创建其自定义profile,并使用现有工具进行检查。
我可以在其他路径和端口上提供事件探查器处理程序(/debug/pprof/ ...)吗?
是。 net/http/pprof软件包默认情况下将其处理程序注册到默认的多路复用器,但是您也可以使用从软件包中导出的处理程序自己注册它们。
例如,以下示例将在 /custom_debug_path/profile上的:7777上提供pprof.Profile处理程序:
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还提供了一个执行跟踪器,以跟踪一个间隔内的运行时事件。
Tracing 可以在以下方面帮助到我们:
- 在Go流程中检测和分析应用程序延迟。
- 在一连串的通话中衡量特定通话的费用。
- 找出利用率和性能改进。如果不跟踪数据,瓶颈并不总是很明显。
在单片系统中,从程序的构建块中收集诊断数据相对容易。所有模块都生活在一个过程中,并共享公共资源来报告日志,错误和其他诊断信息。一旦您的系统超出了单个进程并开始分布,就很难跟踪从前端Web服务器到其所有后端的调用,直到将响应返回给用户为止。在这里,分布式跟踪对仪器和分析生产系统起着重要作用。
分布式跟踪是一种工具代码,可以分析用户请求整个生命周期中的延迟。当系统是分布式的并且常规的性能分析和调试工具无法扩展时,您可能希望使用分布式跟踪工具来分析用户请求和RPC的性能。
分布式跟踪在以下几个方面可以帮助到我们:
- 大型系统中的仪器和配置文件应用程序延迟。
- 跟踪用户请求生命周期内的所有RPC,并查看仅在生产中可见的集成问题。
- 找出可以应用于我们的系统的性能改进。在收集跟踪数据之前,许多瓶颈并不明显。
Go生态系统为每个跟踪系统和与后端无关的数据库提供了各种分布式跟踪库。
有没有一种方法可以自动拦截每个函数调用并创建跟踪?
Go无法提供一种自动拦截每个函数调用并创建跟踪范围的方法。您需要手动检测代码以创建,结束和注释跨度。
我应该如何在Go库中传播跟踪头?
您可以在context.Context中传播跟踪标识符和标记。业界尚无规范的跟踪键或跟踪头的通用表示形式。每个跟踪提供者都有责任在其Go库中提供传播实用程序。
标准库或运行时中还有哪些其他低级事件可以包含在跟踪中?
标准库和运行时正尝试公开一些其他API,以在发生低级内部事件时进行通知。例如,httptrace.ClientTrace提供API以在传出请求的生命周期中跟踪低级事件。正在进行中的工作是从运行时执行跟踪器中检索低级运行时事件,并允许用户定义和记录其用户事件。
Debugging
调试是确定程序行为异常的过程。调试器使我们能够了解程序的执行流程和当前状态。调试有几种样式。本节仅关注将调试器附加到程序和core dump调试。
Go用户通常使用以下调试器:
- Delve: Delve是Go编程语言的调试器。它支持Go的运行时概念和内置类型。 Delve试图成为Go程序的全功能可靠调试器。
- GDB: Go通过标准的Go编译器和Gccgo提供了GDB支持。堆栈管理,线程和运行时所包含的方面与执行模型有很大不同,GDB希望它们会混淆调试器,即使使用gccgo编译程序也是如此。即使可以使用GDB调试Go程序,它也不是理想的选择,并且可能会造成混乱。
调试器与Go程序的配合情况如何?
gc编译器执行优化,例如函数内联和变量注册。这些优化有时会使调试器的调试更加困难。正在进行不断的努力来改善为优化的二进制文件生成的DWARF信息的质量。在这些改进可用之前,我们建议在构建要调试的代码时禁用优化。以下命令生成没有编译器优化的软件包:
$ go build -gcflags=all="-N -l"
作为改进工作的一部分,Go 1.10引入了新的编译器标志-dwarflocationlists。该标志使编译器添加位置列表,以帮助调试器使用优化的二进制文件。以下命令使用DWARF位置列表构建具有优化的软件包:
$ go build -gcflags="-dwarflocationlists=true"
推荐的调试器用户界面是什么?
尽管delve和gdb都提供了CLI,但是大多数编辑器集成和IDE都提供了特定于调试的用户界面。
可以使用Go程序进行事后调试吗?
core dump文件是一个文件,其中包含正在运行的进程的内存dump及其进程状态。它主要用于程序的事后调试,并在程序仍在运行时了解其状态。这两种情况使core dump的调试成为进行事后分析和分析生产服务的良好诊断工具。可以从Go程序中获取core文件,并使用delve或gdb进行调试,有关逐步指南,请参见core dump调试页面。
Runtime statistics and events
运行时为用户提供内部事件的统计信息和报告,以在运行时级别诊断性能和利用率问题。
用户可以监视这些统计信息,以更好地了解Go程序的整体运行状况和性能。一些经常监视的统计信息和状态:
- runtime.ReadMemStats报告与堆分配和垃圾收集相关的指标。内存统计信息对于监视进程正在消耗多少内存资源,进程是否可以充分利用内存以及捕获内存泄漏很有用。
- debug.ReadGCStats读取有关垃圾收集的统计信息。查看在GC暂停上花费了多少资源是很有用的。它还报告了垃圾收集器暂停和暂停时间百分比的时间线。
- debug.Stack返回当前的堆栈跟踪。堆栈跟踪对于查看当前正在运行多少个goroutine,它们在做什么以及是否被阻塞非常有用。
- debug.WriteHeapDump暂停所有goroutine的执行,并允许您将堆dump到文件中。堆dump是给定时间Go进程内存的快照。它包含所有分配的对象以及goroutine,finalizer等。
- runtime.NumGoroutine返回当前goroutine的数量。可以监视该值以查看是否使用了足够的goroutine,或者检测goroutine泄漏。
Execution tracer
Go附带了一个运行时执行跟踪程序,以捕获各种运行时事件。调度,系统调用,垃圾回收,堆大小和其他事件由运行时收集,并且可以通过go工具跟踪进行可视化。执行跟踪器是检测延迟和利用率问题的工具。您可以检查CPU的利用率,以及何时网络连接或系统调用会导致goroutine抢占。
跟踪器在一下几个方面可以帮助到您:
- 了解您的goroutine如何执行。
- 了解一些核心运行时事件,例如GC运行。
- 确定并行化执行不佳。
但是,这对于识别热点(例如分析过多的内存或CPU使用率的原因)不是很好。而是首先使用分析工具来解决它们。
上面的go工具跟踪可视化显示执行开始良好,然后序列化了。它表明可能存在导致瓶颈的共享资源的锁争用。
请参阅go tool trace以收集和分析运行时跟踪。
GODEBUG
如果相应地设置了GODEBUG环境变量,则运行时还会发出事件和信息。
- GODEBUG=gctrace=1 在每个收集时打印垃圾收集器事件,总结收集的内存量和暂停的时间。
- GODEBUG=schedtrace=X 每X毫秒打印一次调度事件。
GODEBUG环境变量可用于在标准库和运行时中禁用指令集扩展。
- GODEBUG=cpu.all=off 禁用所有可选指令集扩展的使用。
- GODEBUG=cpu.extension=off 禁止使用指定指令集扩展中的指令。 扩展名是指令集扩展名的小写名称,例如sse41或avx。
有疑问加站长微信联系(非本文作者)