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