在类 Unix 系统中,我们常常会使用 ps 命令来查看系统当前所运行的进程信息,该命令为我们提供了较大的帮助,能够快速的定位到某些进程的运行情况和状态。

而在 Go 语言中,也有类似的命令工具,那就是 gops[1](Go Process Status)。

gops 是由 Google 官方出品的一个命令行工具,与 ps 命令的功能类似,能够查看并诊断当前系统中 Go 程序的运行状态及内部情况,在一些使用场景中具有较大的存在意义,属于常用工具。

在本文中我们将对 gops 进行全面的使用和介绍。

基本使用

我们先创建一个示例项目,然后在项目根目录执行下述模块安装命令:

$ go get -u github.com/google/gops

写入如下启动代码:

import (..."github.com/google/gops/agent"
)func main() {// 创建并监听 gops agent,gops 命令会通过连接 agent 来读取进程信息// 若需要远程访问,可配置 agent.Options{Addr: "0.0.0.0:6060"},否则默认仅允许本地访问if err := agent.Listen(agent.Options{}); err != nil {log.Fatalf("agent.Listen err: %v", err)}http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {_, _ = w.Write([]byte(`Go语言编程之旅`))})_ := http.ListenAndServe(":6060", http.DefaultServeMux)
}

在完成示例启动代码的写入后,我们启动该程序,并在命令行执行 gops 命令进行查看:

3739  3725  main  * go1.14   /private/var/folders/jm/.../b001/exe/main
3725  71093 go      go1.14   /usr/local/Cellar/go/1.14/libexec/bin/go
62357 46131 go      go1.14   /usr/local/Cellar/go/1.14/libexec/bin/go
3872  3742  gops    go1.14   /Users/eddycjy/go/bin/gops
62379 62357 main    go1.14   /private/var/folders/jm/.../b001/exe/main
...

在上述输出中,你很快就发现有一点不一样,那就是为什么某一行的输出结果中会包含一个 * 符号,如下:

3739  3725  main  * go1.14   /private/var/folders/jm/.../b001/exe/main

这实际上代表着该 Go 进程,包含了 agent,因此它可以启用更强大的诊断功能,包括当前堆栈跟踪,Go版本,内存统计信息等等。

在最后也有一个 main 的 Go 进程,它不包含 * 符号,这意味着它是一个普通的 Go 程序,也就是没有植入 agent,只能使用最基本的功能。

常规命令

gops 工具包含了大量的分析命令,我们可以通过 gops help 进行查看:

$ gops help
gops is a tool to list and diagnose Go processes.Usage:gops <cmd> <pid|addr> ...gops <pid> # displays process infogops help  # displays this help messageCommands:stack      Prints the stack trace.gc         Runs the garbage collector and blocks until successful.setgc      Sets the garbage collection target percentage.memstats   Prints the allocation and garbage collection stats.version    Prints the Go version used to build the program.stats      Prints runtime stats.trace      Runs the runtime tracer for 5 secs and launches "go tool trace".pprof-heap Reads the heap profile and launches "go tool pprof".pprof-cpu  Reads the CPU profile and launches "go tool pprof".

在接下来的小节中,我们将针对几个常用的分析功能进行概要分析。

查看指定进程信息

$ gops <pid>
parent PID: 3725
threads: 7
memory usage: 0.042%
cpu usage: 0.003%
username: eddycjy
cmd+args: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/go-build943691423/b001/exe/main
elapsed time: 10:56
local/remote: 127.0.0.1:59369 <-> :0 (LISTEN)
local/remote: *:6060 <-> :0 (LISTEN)

获取 Go 进程的概要信息,包括父级PID、线程数、内存/CPU使用率、运行者的账户名、进程的启动命令行参数、启动后所经过的时间以及 gops 的 agent 监听信息(若无植入 agent,则没有这项信息)。

查看调用栈信息

$ gops stack 3739
goroutine 19 [running]:
runtime/pprof.writeGoroutineStacks(0x1385aa0, 0xc000132038, 0x30, 0xd0).../Users/eddycjy/go/src/github.com/google/gops/agent/agent.go:185 +0x1af
github.com/google/gops/agent.listen()/Users/eddycjy/go/src/github.com/google/gops/agent/agent.go:133 +0x2bf
created by github.com/google/gops/agent.Listen/Users/eddycjy/go/src/github.com/google/gops/agent/agent.go:111 +0x36bgoroutine 1 [IO wait]:
internal/poll.runtime_pollWait(0x2f55e38, 0x72, 0x0)/usr/local/Cellar/go/1.14/libexec/src/runtime/netpoll.go:203 +0x55...

获取对应进程的代码调用堆栈信息,可用于分析调用链路。

查看内存使用情况

$ gops memstats 3739
alloc: 1.15MB (1205272 bytes)
total-alloc: 1.15MB (1205272 bytes)
sys: 69.45MB (72827136 bytes)
lookups: 0
mallocs: 644
frees: 12
heap-alloc: 1.15MB (1205272 bytes)
heap-sys: 63.66MB (66748416 bytes)
heap-idle: 62.05MB (65060864 bytes)
heap-in-use: 1.61MB (1687552 bytes)
heap-released: 62.02MB (65028096 bytes)
heap-objects: 632
...

获取 Go 在运行时的当前内存使用情况,主要是 runtime.MemStats[2] 的相关字段信息。

查看运行时信息

$ gops stats 3739
goroutines: 2
OS threads: 8
GOMAXPROCS: 4
num CPU: 4

获取 Go 运行时的基本信息,包括当前的 Goroutine 数量、系统线程、GOMAXPROCS 数值以及当前系统的 CPU 核数。

查看 trace 信息

$ gops trace 3739
Tracing now, will take 5 secs...
Trace dump saved to: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/trace092133110
Parsing trace...
Splitting trace...
Opening browser. Trace viewer is listening on http://127.0.0.1:53811

go tool trace 作用基本一致。

查看 profile 信息

$ gops pprof-cpu 3739
Profiling CPU now, will take 30 secs...
Profile dump saved to: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/profile563685966
Binary file saved to: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/binary265411413
File: binary265411413
Type: cpu
...
(pprof) $ gops pprof-heap 3739
Profile dump saved to: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/profile967076057
Binary file saved to: /var/folders/jm/pk20jr_s74x49kqmyt87n2800000gn/T/binary904879716
File: binary904879716
Type: inuse_space
...
(pprof)

go tool pprof 作用基本一致。

你怎么知道我是谁

在学习了 gops 的使用后,我们突然发现一个问题,那就是 gops 是怎么知道哪些进程是与 Go 相关的进程?

如果是植入了 agent 的应用程序还好说,可以理解为埋入了识别点。但实际情况是,没有植入 agent 的 Go 程序也被识别到了,说明 gops 本身并不是这么实现的,考虑植入agent 应当只是用于诊断信息的拓展使用,并不是一个识别点,那么 gops 到底是怎么发现哪些进程是 Go 相关的呢?

我们回归问题的前置需求,假设我们想知道哪些进程与 Go 相关,那么第一步我们要先知道我们当前系统中都运行了哪些进程,这些记录在哪里有?

认真思考一下,答案也就呼之欲出了,假设是 Linux 相关的系统下,其会将进程所有的相关信息都按照约定的数据结构写入 /proc 目录下,因此我们有充分的怀疑认为 gops 就是从 /proc 目录下读取到相关信息的,源代码如下:

func PidsWithContext(ctx context.Context) ([]int32, error) {var ret []int32d, err := os.Open(common.HostProc())if err != nil {return nil, err}defer d.Close()fnames, err := d.Readdirnames(-1)if err != nil {return nil, err}for _, fname := range fnames {pid, err := strconv.ParseInt(fname, 10, 32)if err != nil {continue}ret = append(ret, int32(pid))}return ret, nil
}// common.HostProc
func HostProc(combineWith ...string) string {return GetEnv("HOST_PROC", "/proc", combineWith...)
}

在上述代码中,该方法通过调用 os.Open 方法打开了 proc 目录,并利用 Readdirnames 方法对该目录进行了扫描,最终获取到了所有需要 pid,最终完成其使命,返回了所有 pid。

在确定了 gops 是通过扫描 /proc 目录得到的进程信息后,我们又遇到了一个新的疑问点,那就是 gops 是怎么确定这个进程是 Go 进程,又怎么知道它的具体版本信息的呢,源代码如下:

func isGo(pr ps.Process) (path, version string, agent, ok bool, err error) {...path, _ = pr.Path()if err != nil {return}var versionInfo goversion.VersionversionInfo, err = goversion.ReadExe(path)if err != nil {return}ok = trueversion = versionInfo.Releasepidfile, err := internal.PIDFile(pr.Pid())if err == nil {_, err := os.Stat(pidfile)agent = err == nil}return path, version, agent, ok, nil
}

我们可以看到该方法的主要作用是根据扫描 /proc 目录所得到的二进制文件地址中查找相关的标识,用于判断其是否 Go 程序,如果是 Go 程序,那么它将会返回该进程的 pid、二进制文件的名称以及二进制文件的完整存储路径,判断的标识如下:

    if name == "runtime.main" || name == "main.main" {isGo = true}if name == "runtime.buildVersion" {isGo = true}

而关于所编译的 Go 语言的版本,Go 编译器会在二进制文件中打入 runtime.buildVersion标识,这个标识能够快速我们快速识别它的编译信息,而 gops 也正正是利用了这一点。

我们可以利用 gdb 来进行查看 Go 所编译的二进制文件的版本信息,如下:

$ export GOFLAGS="-ldflags=-compressdwarf=false" && go build .$ gdb awesomeProject
...
(gdb) p 'runtime.buildVersion'
$1 = 0x131bbb0 "go1.14"

在上述输出中,我们先对示例项目进行了编译,然后利用 gdb 中查看了 runtime.buildVersion 变量,最终可得知编译这个 Go 程序的版本是 Go1.14。

但在编译时,有一点需要注意,就是我们在编译时指定了 export GOFLAGS="-ldflags=-compressdwarf=false" 参数。

如果不进行指定的话,就会出现 Reading symbols from awesomeProject...(no debugging symbols found)...done. 的相关报错,会将会影响部分功能使用。

这是因为在 Go1.11 版本开始,进行了调试信息的压缩,目的是为了减小所编译的二进制文件大小,但 Mac 上的 gdb 无法理解压缩的 DWARF,因此会产生问题。

需要进行指定在调试时不进行 DWARF 的压缩,便于 Mac 上的 gdb 使用。

需要注意的一点

假设我们在一些特殊场景下希望对 Go 所编译的二进制文件进行压缩,那么在最后我们常常会使用到 upx 工具来减少其整体大小,命令如下:

$ upx awesomeProject

这时候我们再重新运行所编译的 awesomeProject 文件,这时候需要思考的是,gops 能不能识别到它是一个 Go 程序呢?

答案是不行的,经过 upx 压缩后的二进制文件将无法被识别为 Go 程序,并且在我所使用的 gops v0.3.7版本中,由于这类加壳进程的存在,执行 gops 命令直接出现了空指针调用的恐慌(panic),显然,这是一个 BUG,大家在实际环境中需要多加留意,如果要使用 gops 则尽量不要使用 upx 进行压缩。

总结

在本文中我们针对 Google 官方出品的 gops 进行了基本使用和原理性的部分剖析。

如果你仔细研读了,就会发现其实 gops 几乎包含了大部分 Go 剖析工具的功能,是名副其实的进程诊断工具。

gops 集成了大量 Go 业界中常用的分析链,在排查问题上也会非常的方便,不需要一个个单独找特定工具在哪里,只需要使用 gops 即可,而更深层次的使用可以根据实际情况进行更一步的了解。

参考资料

[1]

gops: https://github.com/google/gops

[2]

runtime.MemStats: https://golang.org/pkg/runtime/#MemStats


分享 Go 语言、微服务架构和奇怪的系统设计

???? 长按关注煎鱼,在知识的海洋里遨游

学习资料: 关注后公众号内回复【000】,下载 LeetCode 题解大全。

必须要学的 Go 进程诊断工具 gops相关推荐

  1. Go 官方进程诊断工具 gops 详解 | 周末送书

    K8s已经成为一线大厂分布式平台的标配技术.你是不是还在惆怅怎么掌握它?来这里,大型互联网公司一线工程师亲授,不来虚的,直接上手实战,3天时间带你搭建K8s平台,快速学会K8s,点击下方图片可了解培训 ...

  2. Arthas : 在线分析诊断工具Arthas(阿尔萨斯)

    1.美图 2.背景 想学JDK自带的工具,BTrace然后,同事说这个过时了,但是我不是很相信,因为是JDK自带的工具,他推荐这个,于是我就来看看这个到底是什么东西. Arthas 是Alibaba开 ...

  3. arthas-Java诊断工具

    Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 官网:https://arthas.aliyun.com/zh-cn/ 当你遇到以下类似问题而束手无策时,Arthas可以帮助你 ...

  4. JVM学习笔记之-JVM性能监控-JVM监控及诊断工具-GUI方式-Visual VM-JProfiler-Arthas

    00-谈GUI工具前的补充 补充1:内存泄漏 内存泄漏的理解与分类 何为内存泄漏( memory leak) 可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用.那么对于这 ...

  5. JVM学习笔记之-JVM性能监控-JVM监控及诊断工具-命令行方式

    性能优化的步骤 第1步(发现问题):性能监控 一种以非强行或者入侵方式收集或查看应用运营性能数据的活动. 监控通常是指一种在生产.质量评估或者开发环境下实施的带有预防或主动性的活动. 当应用相关干系人 ...

  6. linux 系统监控、诊断工具之 IO wait

    1.问题: 最近在做日志的实时同步,上线之前是做过单份线上日志压力测试的,消息队列和客户端.本机都没问题,但是没想到上了第二份日志之后,问题来了: 集群中的某台机器 top 看到负载巨高,集群中的机器 ...

  7. 动手实现一个适用于.NET Core 的诊断工具

    前言 大家可能对诊断工具并不陌生,从大名鼎鼎的 dotTrace,到 .NET CLI 推出的一系列的高效诊断组件(dotnet trace,dotnet sos,dotnet dump)等, 这些工 ...

  8. .NET Core CLI 的性能诊断工具介绍

    前言 开发人员的.NET Core项目上线后,经常会出现各种问题,内存泄漏,CPU 100%,处理时间长等, 这个时候就需要快速并准确的发现问题,并解决问题, 除了项目本身的日志记录外,NET Cor ...

  9. 中小研发团队架构实践之生产环境诊断工具WinDbg

    生产环境偶尔会出现一些异常问题,WinDbg或GDB是解决此类问题的利器.调试工具WinDbg如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump文件类似于飞机的黑匣子,记录着生产环境程序 ...

最新文章

  1. 墙裂建议收藏,100道Python练手题目
  2. 12.figure/subplot多窗口技巧
  3. 一个神级般的 Python 调试神器
  4. C#关于伪静态页面的两种实现方法
  5. jvm破坏双亲委派_破坏JVM
  6. 信息学奥赛一本通(1109:开关灯)
  7. 华为Mate X折叠屏手机即将上市:支持5G 升级后置四摄
  8. .Net Attribute特性
  9. 首个月球旅客!SpaceX将送普通人上太空,马斯克暗示首单来自日本
  10. python处理excel的优势-推荐一款数据处理的神级工具,完全结合了Python和Excel的优势...
  11. hive启动报错 java.net.URISyntaxException: Relative path in absolute URI: ${system:java.io.tmpdir%7D/$%7B
  12. 企业网站建设要注意的四大准则
  13. 美林公司的尽职调查应用程序被选入Deloitte Tohmatsu的并购咨询解决方案
  14. Unity中摄像机跟随
  15. 姗姗来迟的苹果AR设备将要落地,但颠覆iPhone销量仍是空谈
  16. 电容麦克风测试软件,Precision sound
  17. FCC KDB 680106 无线充电(WPT)更新解读
  18. Win10 64位系统安装Microsoft Visual Studio和Intel Fortran(非正式名称)
  19. 信息系统分析与设计——第八章 领域对象建模
  20. 我的世界java版hud怎么设置_修改配置文件以关闭HUD和怪物隐身

热门文章

  1. 购物卡充值系统c语言代码,超市购物卡销售、存取货管理系统
  2. [Writeup]与佛论禅
  3. 汇编语言中的loop循环
  4. 2021年教会我的10个道理
  5. 高品质的蓝牙耳机有哪些?四款高品质蓝牙耳机推荐
  6. Android学习第1篇:J2EE,J2SE,J2ME,JDK,SDK,JRE,JVM区别
  7. poj 1925 Spiderman (dp)(疯狂TLE)
  8. Material Design设计规范与符合MD设计风格的库、APP
  9. SLIC超像素生成算法
  10. 关于javacore和dump文件