Skip to content

gops 的实现

gops 是一个用来列举和调查当前系统中运行的 Go 进程的工具。


gops 的代码仓库位置: 。这个工具我看到时已经多年没有更新实现了,调试作用也有限。它的功能大多可以用 Go pprof 来做,而且做的更好。在看到其他 Go 开发的监控工具时看到 gops 这份实现。虽然功能简单,不过实现简洁好懂故做一记。


gops 的使用

gops 主要的 3 个作用:

  1. 列出当前 os 上所有 Go processes 以及这些 Go Process 用的 Go version。比如:
    $ gops
    983   980    uplink-soecks  go1.9   /usr/local/bin/uplink-soecks
    52697 52695  gops           go1.10  /Users/jbd/bin/gops
    4132  4130   foops        * go1.9   /Users/jbd/bin/foops
    51130 51128  gocode         go1.9.2 /Users/jbd/bin/gocode
    
  2. 通过 pid 查看对应 Go process 的运行信息 (stack, memstat, pprof-cpu 等等)。比如查看对应 Go process 的 stack 信息:
    $ gops stack (<pid>|<addr>)
    gops stack 85709
    goroutine 8 [running]:
    runtime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)
        /Users/jbd/go/src/runtime/pprof/pprof.go:603 +0x79
    runtime/pprof.writeGoroutine(0x13c7bc0, 0xc42000e008, 0x2, 0xc428f1c048, 0xc420ec8608)
        /Users/jbd/go/src/runtime/pprof/pprof.go:592 +0x44
    runtime/pprof.(*Profile).WriteTo(0x13eeda0, 0x13c7bc0, 0xc42000e008, 0x2, 0xc42000e008, 0x0)
        /Users/jbd/go/src/runtime/pprof/pprof.go:302 +0x3b5
    github.com/google/gops/agent.handle(0x13cd560, 0xc42000e008, 0xc420186000, 0x1, 0x1, 0x0, 0x0)
        /Users/jbd/src/github.com/google/gops/agent/agent.go:150 +0x1b3
    github.com/google/gops/agent.listen()
        /Users/jbd/src/github.com/google/gops/agent/agent.go:113 +0x2b2
    created by github.com/google/gops/agent.Listen
        /Users/jbd/src/github.com/google/gops/agent/agent.go:94 +0x480
    
  3. 操作对应的 Go process 比如:
    # 设置 GC 百分比到 10%
    $ gops setgc (<pid>|<addr>) 10
    # 关掉 GC
    $ gops setgc (<pid>|<addr>) off
    

gops 的实现

列出当前 OS 中的 Go Process

要列举当前 OS 中所有 Go Porcess 只需要 2 个步骤: - 列出 OS 上所有的 processes - 判断出对应的 process 是否是 Go Process 并且分析出对应的 Go verison

列出 OS 上所有的 processes ,这个步骤可以用 gopsutil 来实现。接下来只要解析出对应的进程用什么 Go 版本来编译的就可以得出 gops 的 Go processes list。判断方法为,通过 Go 系统库的 debug/buildinfo 来解析对应可执行文件。关键代码如下:

import "debug/buildinfo"

// path 为 gopsutil 枚举到的 Go process filename
func goVersion(path string) (string, error) {
    info, err := buildinfo.ReadFile(path)
    if err != nil {
        return "", err
    }
    return info.GoVersion, nil
}

这里要注意的是:Go 1.18 以上才提供 debug/buildinfo 的这个功能。如果早于 Go 1.18 需要用一个第三方实现 rsc.io/goversion/version

获取调试信息和操作对应 Go Process

要实现调试和设置对应 Go Process 的功能有一个前提:对应的 Go Process 必须包含 gops 的 agent 。否则无法调试的,比如下面这段 code 需要被引入对应的调试的 Go Process:

import (
    "log"
    "time"

    "github.com/google/gops/agent"
)

func main() {
    if err := agent.Listen(agent.Options{}); err != nil {
        log.Fatal(err)
    }
    time.Sleep(time.Hour)
}

这个 gops/agent 会在对应的 Go process 里监听一个 port 默认会在 127.0.0.1 上。gops 的命令在调试和操作进程时,会通过 pid 找到对应的通讯 port 用 tcp 连接过去通讯。来获取调试信息以及操作对用的 process 行为。

这个做法其实和 Go 的 pprof 类似,只是这里用 127.0.0.1 并且是用 tcp 本地连接。所有相对 pprof 安全些。

当然这点用 Go pprof 也可以做到,所以 gops 没有什么太大的实际用处。只能参考些实现代码。