诊断
介绍
Go 生态系统提供了大量的 API 和工具来诊断 Go 程序中的逻辑和性能问题。本页总结了可用的工具,并帮助 Go 用户为他们的特定问题选择合适的工具.
诊断解决方案可分为以下几组:
- Profiling:分析: 分析工具分析 Go 程序的复杂性和成本,例如内存使用和频繁调用的函数,以识别 Go 程序的昂贵部分.
- Tracing:跟踪: 跟踪是一种检测代码以分析调用或用户请求的整个生命周期中的延迟的方法。跟踪提供了每个组件对系统整体延迟的贡献程度的概览。跟踪可以跨越多个 Go 进程.
- Debugging:调试: 调试允许我们暂停 Go 程序并检查其执行情况。程序状态和流程可以通过调试来验证.
- 运行时统计和事件: 运行时统计和事件的收集和分析提供了对 Go 程序健康状况的高级概述。指标的峰值/下降有助于我们识别吞吐量、利用率和性能的变化.
注意:某些诊断工具可能会相互干扰。例如,精确的内存分析会扭曲 CPU profiles,而 goroutine 阻塞分析会影响调度程序跟踪。单独使用工具来获取更精确的信息.
Profiling:分析
分析对于识别昂贵或经常调用的代码段非常有用。Go 运行时以 pprof 可视化工具所需的格式提供
分析数据。分析数据可以在测试期间通过go
test
或从
net/http/pprof 包中提供的端点收集。用户需要收集分析数据,并使用 pprof 工具来筛选和可视化顶级代码路径.
runtime/pprof包提供的预定义配置文件(profiles):
- cpu: CPU 配置文件(profile)确定程序在主动消耗 CPU 周期时花费时间的位置(而不是在睡眠或等待 I/O 时).
- heap: 堆配置文件报告内存分配样本;用于监视当前和历史内存使用情况,并检查内存泄漏.
- threadcreate: 线程创建配置文件报告导致创建新操作系统线程的程序部分.
- goroutine: Goroutine配置文件报告所有当前 goroutine 的堆栈跟踪.
-
block: Block配置文件显示 goroutine 阻塞等待同步原语(包括计时器通道)的位置。默认情况下不启用阻塞配置文件;使用
runtime.SetBlockProfileRate
启用它. -
mutex: Mutex(互斥体) 配置文件报告锁定争用。当您认为您的 CPU 由于互斥争用而未充分利用时,请使用此配置文件。Mutex 配置文件默认未启用,请参阅
runtime.SetMutexProfileFraction
启用它。
我可以使用哪些其他分析器来分析 Go 程序?
在 Linux 上,perf tools 可用于分析 Go 程序。Perf 可以分析和展开 cgo/SWIG 代码和内核,因此有助于深入了解本机/内核性能瓶颈。在 macOS 上, Instruments 套件可用于分析 Go 程序.
我可以分析我的生产环境的服务吗?
是的。在生产环境中分析程序是安全的,但是启用某些profile(例如 CPU profile)会增加成本。您应该会看到性能降级。性能损失可以通过在生产中打开分析器之前测量分析器的开销来估计。
您可能希望定期分析您的生产环境的服务。特别是在具有单个进程的多个副本的系统中,定期选择随机副本是一个安全的选择。选择一个生产过程,每 Y 秒对其进行 X 秒分析,并保存结果以供可视化和分析;然后定期重复。可以手动和/或自动检查结果以发现问题。profile的收集可能会相互干扰,因此建议一次只收集一个profile。
可视化分析数据的最佳方法是什么?
Go 工具使用 go tool pprof
提供profile数据的文本、图形和callgrind可视化。阅读 分析 Go 程序以查看其运行情况。
以文本形式列出最昂贵的调用.
将最昂贵的调用可视化为图表.
Weblist 视图在 HTML 页面中逐行显示源代码的昂贵部分。在下面的示例中,在runtime.concatstrings
中花费了 530 毫秒,每行的成本显示在列表中
将最昂贵的调用可视化为weblist.
可视化分析数据的另一种方法是火焰图。火焰图允许您在特定的祖先路径中移动,因此您可以放大/缩小代码的特定部分。上游 pprof 支持火焰图.
火焰图提供可视化来发现最昂贵的代码路径.
我是否仅限于内置profiles?
除了运行时提供的内容之外,Go 用户还可以通过pprof.Profile创建他们的自定义profiles, 并使用现有工具来检查它们。
我可以在不同的路径和端口上提供分析器处理程序 (/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 还提供了一个执行跟踪器来跟踪一个时间间隔内的运行时事件.
跟踪使我们能够:
- 在 Go 进程中检测和分析应用程序延迟.
- 在一长串调用中测量特定调用的成本.
- 找出利用率和性能改进。如果没有跟踪数据,瓶颈并不总是显而易见的。
在整体系统中,从程序的构建块中收集诊断数据相对容易。所有模块都存在于一个进程中,并共享公共资源来报告日志、错误和其他诊断信息。一旦您的系统超出单个进程并开始变得分布式,就很难跟踪从前端 Web 服务器开始到其所有后端的调用,直到响应返回给用户。这就是分布式跟踪在检测和分析生产系统方面发挥重要作用的地方。
分布式跟踪是一种检测代码以分析用户请求整个生命周期中的延迟的方法。当系统是分布式的并且传统的分析和调试工具无法扩展时,您可能希望使用分布式跟踪工具来分析用户请求和 RPC 的性能。
分布式跟踪使我们能够:
- 在大型系统中检测和分析应用程序延迟.
- 跟踪用户请求生命周期内的所有 RPC,并查看仅在生产环境中可见的集成问题。
- 找出可以应用于我们系统的性能改进。很多瓶颈在收集追踪数据之前并不明显。
Go 生态系统为每个跟踪系统和后端无关的系统提供各种分布式跟踪库。
有没有办法自动拦截每个函数调用并创建跟踪?
Go 没有提供自动拦截每个函数调用并创建跟踪跨度的方法。您需要手动检测代码以创建、结束和注释跨度。
我应该如何在 Go 库中传播跟踪标头?
您可以在context.Context
中传播跟踪标识符和标记。业界尚无规范的跟踪键或跟踪标头的常见表示形式。每个跟踪提供程序都负责在其 Go 库中提供传播实用程序。
来自标准库或运行时的其他哪些低级事件可以包含在跟踪中?
标准库和运行时正在尝试公开几个额外的 API 来通知低级别的内部事件。例如,httptrace.ClientTrace
提供 API 来跟踪传出请求生命周期中的低级事件。从运行时执行跟踪程序中检索低级运行时事件并允许用户定义和记录其用户事件的工作正在进行中。
Debugging:调试
调试是识别程序行为异常的过程。调试器使我们能够了解程序的执行流程和当前状态。有几种调试方式;本节将只关注将调试器附加到程序和核心转储调试.
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 程序进行事后调试?
核心转储文件是包含正在运行的进程的内存转储及其进程状态的文件。它主要用于对程序进行事后调试,并在程序仍在运行时了解其状态。这两种情况使核心转储调试成为事后分析和分析生产服务的良好诊断帮助。可以从 Go 程序中获取核心文件并使用 delve 或 gdb 进行调试,请参阅 核心转储调试页面以获取分步指南。
运行时统计和事件
运行时提供内部事件的统计信息和报告,供用户在运行时级别诊断性能和利用率问题。
用户可以监控这些统计数据,以更好地了解 Go 程序的整体运行状况和性能。一些经常监控的统计数据和状态:
runtime.ReadMemStats
报告与堆分配和垃圾收集相关的指标。内存统计信息对于监控进程消耗了多少内存资源、进程是否可以很好地利用内存以及捕获内存泄漏很有用。debug.ReadGCStats
读取有关垃圾收集的统计信息。查看在 GC 暂停上花费了多少资源非常有用。它还报告垃圾收集器暂停和暂停时间百分位数的时间线。debug.Stack
返回当前堆栈跟踪。堆栈跟踪有助于查看当前正在运行的 goroutine 数量、它们在做什么以及它们是否被阻塞。debug.WriteHeapDump
暂停所有 goroutine 的执行,并允许您将堆转储到文件中。堆转储是给定时间 Go 进程内存的快照。它包含所有已分配的对象以及 goroutine、终结器(finalizers)等。runtime.NumGoroutine
返回当前 goroutine 的数量。可以监视该值以查看是否使用了足够的 goroutine,或者检测 goroutine 泄漏.
执行跟踪器
Go 带有一个运行时执行跟踪器来捕获广泛的运行时事件。调度、系统调用、垃圾回收、堆大小和其他事件由运行时收集,并可用于 go 工具跟踪的可视化。执行跟踪器是一种检测延迟和利用率问题的工具。您可以检查 CPU 的使用情况,以及网络或系统调用何时成为 goroutine 抢占的原因.
Tracer 可用于:
- 了解你的 goroutines 是如何执行的.
- 了解一些核心运行时事件,例如 GC 运行.
- 识别并行性较差的执行.
但是,它对于识别热点(例如分析内存或 CPU 使用率过高的原因)并不是很好。请先使用分析工具来解决这些问题.
上面,go 工具跟踪可视化显示执行开始正常,然后变得序列化。这表明共享资源可能存在锁争用,从而产生瓶颈.
参阅 go
tool
trace
收集和分析运行时跟踪.
GODEBUG
如果相应地设置了 GODEBUG 环境变量,运行时也会发出事件和信息。
- GODEBUG=gctrace=1 在每次收集时打印垃圾收集器事件,汇总收集的内存量和暂停时间。
- GODEBUG=inittrace=1 打印完成的包初始化工作的执行时间和内存分配信息的摘要。
- GODEBUG=schedtrace=X 每 X 毫秒打印一次调度事件.
GODEBUG 环境变量可用于禁用在标准库和运行时中使用指令集扩展。
- GODEBUG=cpu.all=off 禁用所有可选指令集扩展的使用。
- GODEBUG=cpu.extension=off 禁止使用来自指定指令集扩展的指令.
extension是指令集扩展的小写名称,例如sse41 或 avx.