Go Diagnose - 诊断工具

https://golang.org/doc/diagnostics.html

1. 诊断工具分类

  • Profiling:内存、频繁调用函数
  • Tracing:调用或请求声明周期,每个组件耗时,可跨多个Go进程
  • Debugging:调试Debug
  • Runtime statistics and events:运行时资源收集和统计和分析

2. Profiling

  • 收集profiling data:https://golang.org/pkg/runtime/pprof/
  • 使用pprof工具来过滤和可视化顶部代码路径:https://github.com/google/pprof/blob/master/doc/README.md

收集数据:

  • cpu:cpu消耗周期
  • heap:内存分配样本,用于监视当前和历史内存使用情况,并检查内存泄漏
  • threadcreate:OS线程创建
  • goroutine:当前goroutines的所有数量
  • block:阻塞地方/同步原语/通道(设置runtime.SetBlockProfileRate=1
  • mutex:报告锁定争用情况,若怀疑由于互斥争用而未充分利用您的CPU时候可以使用(设置runtime.SetMutexProfileFraction=1

在Linux上,perf工具可用于分析Go程序。 Perf可以剖析和展开cgo/SWIG代码和内核,因此深入了解本机/内核性能瓶颈非常有用。

在macOS上,Instruments套件可以使用profile Go程序。

2.1. Linux使用 perf

2.2. Mac使用 InstrumentsUserGuide

2.3. 生产分析 ?

  • 生产分析是安全的
  • 启用某些配置,会增加性能开销
  • 考虑挑选生产副本,间隔Y秒,将其分析X秒并保存结果以进行可视化和分析
  • 配置文件配置可能相互干扰,尽量一次读取一份配置

2.4. 图形化分析

Go工具使用go工具pprof提供配置文件数据的文本,图形和callgrind可视化。

  • pprof图:https://blog.golang.org/profiling-go-programs
  • 火焰图

2.5. pprof使用

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))
}

3. Tracing - golang.org/x/net/trace

  • 检测和分析应用程序延迟
  • 耗时call调用
  • 弄清楚利用率和性能改进

在单体系统中,从程序的构建块收集诊断数据相对容易,所有模块都位于一个进程中,并共享公共资源以报告日志,错误和其他诊断信息。

一旦系统超出单个进程并开始分布式,从前端Web服务器到其所有后端的服务调用分析将变得更加困难,分布式跟踪在仪器和分析生产系统方面发挥重要作用的地方。

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

分布式跟踪特性:

  • 大型系统中应用程序延迟分析
  • 请求生命周期内的所有RPC
  • 找出可应用于我们系统的性能改进(没有分布式跟踪,性能分析许多瓶颈并不明显)

3.1. 跟踪点

GO没有办法自动拦截每个函数调用并创建跟踪,需要手动检测代码以创建,结束和注释跨度,不像Java有字节码注入技术

3.2. 跟踪头

可以在context.Context中传播跟踪标识符和标记,目前还没有行业中的规范跟踪密钥或跟踪标头的通用表示(最好方式是基于上下文带值跟踪)

3.3. 标准库或运行时内库事件跟踪

标准库和运行时试图公开几个额外的API来通知低级内部事件,例如,httptrace.ClientTrace提供API以跟踪传出请求生命周期中的低级事件。

目前GO官方正在努力从运行时执行跟踪器中检索低级运行时事件,并允许用户定义和记录其用户事件。

4. Debugging

调试器允许我们理解程序的执行流程和当前状态,以及识别程序错误行为的过程。

本节仅关注将调试器附加到程序和核心转储调试:

  • Delve:Delve是Go编程语言的调试器。它支持Go的运行时概念和内置类型。 Delve正试图成为Go程序的全功能可靠调试器。 1
  • GDB:尽管GDB可用于调试Go程序(堆栈管理,线程和运行时包含与执行模型),但它并不理想,可能会产生混淆。2

4.1. Debugging以及编译器优化带来的问题

Gc编译器执行优化,会函数内联和变量注册,这些优化有时会使调试调试更困难,可以在构建正在调试的代码时禁用优化:

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

作为改进工作的一部分,Go 1.10引入了一个新的编译器标志-dwarflocationlists,该标志使编译器添加位置列表,以帮助调试器使用优化的二进制文件。

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

尽管delve和gdb都提供了CLI,但大多数编辑器集成和IDE都提供了特定于调试的用户界面。

4.2. Coredump转存储再调试

核心转储文件是包含正在运行的进程的内存转储及其进程状态的文件,它主要用于程序的事后调试,并在程序运行时了解它的状态,可用于事后使用delve或gdb进行调试。3

5. Runtime事件和统计分析

运行时提供程序运行内部事件的统计信息和报告,用于诊断性能和利用率问题,用户可以监控这些统计数据,以更好地了解Go程序的整体运行状况和性能。

一些经常监控的统计数据和状态:

  • Runtime.ReadMemStats:堆分配和垃圾回收相关的度量,监视进程正在消耗多少内存资源,捕获内存泄漏非常有用;
  • Debug.ReadGCStats:有关垃圾收集的统计信息,报告垃圾收集器暂停和暂停时间百分位数的时间线;
  • Debug.Stack:当前堆栈跟踪,查看当前正在运行的goroutine数量,他们正在做些什么事情,goroutines是运行或阻塞;
  • Debug.WriteHeapDump:暂停所有goroutine的执行,并允许您将堆转储到文件中,堆转储是给定时间Go进程内存的快照,包含所有已分配的对象以及goroutine,finalizers等;
  • Runtime.NumGoroutine:返回当前goroutine的数量,可以监视该值以查看是否使用了足够的goroutine,或检测goroutine泄漏;

5.1. Go附带运行时执行跟踪器

Go附带了一个运行时执行跟踪器,用于捕获各种运行时事件:调度,系统调用,垃圾收集,堆大小和其他事件由运行时收集,并可通过go工具跟踪进行可视化。

执行跟踪器是一种检测延迟和利用率问题的工具,可以检查CPU的使用情况,以及何时联网或系统调用是goroutines抢占的原因。

Tracer对以下内容非常有用:4

  • 了解goroutines如何执行
  • 了解一些核心运行时事件,例如GC运行
  • 识别较差的并行化。

6. GODEBUG参数配置

如果相应地设置了GODEBUG环境变量,运行时也会发出事件和信息:

  • GODEBUG=gctrace=1:GC跟踪,在每个集合中打印垃圾收集器事件,总结收集的内存量和暂停的长度。
  • GODEBUG=schedtrace=X :调度跟踪,每X毫秒打印一次调度事件

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

  • GODEBUG=cpu.all=off :禁用指定指令集扩展中的指令
  • GODEBUG=cpu.avx=off :禁止使用指定指令,比如avx

  1. https://github.com/derekparker/delve ↩︎

  2. https://golang.org/doc/gdb ↩︎

  3. https://golang.org/wiki/CoreDumpDebugging ↩︎

  4. https://golang.org/cmd/trace/ ↩︎