脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 学会这几招让 Go 程序自己监控自己

学会这几招让 Go 程序自己监控自己

2021-11-23 22:27网管叨bi叨进击的网管 Golang

谈到让Go程序监控自己进程的资源使用情况,那么就让我们先来谈一谈有哪些指标是需要监控的,一般谈论进程的指标最常见的就是进程的内存占用率、CPU占用率、创建的线程数。因为Go语言又在线程之上自己维护了Goroutine,所以针

学会这几招让 Go 程序自己监控自己

谈到让Go程序监控自己进程的资源使用情况,那么就让我们先来谈一谈有哪些指标是需要监控的,一般谈论进程的指标最常见的就是进程的内存占用率、CPU占用率、创建的线程数。因为Go语言又在线程之上自己维护了Goroutine,所以针对Go进程的资源指标还需要加一个创建的Goroutine数量。

又因为现在服务很多都部署在Kubernetes集群上,一个Go进程往往就是一个Pod,但是容器的资源是跟宿主机共享的,只是在创建的时候指定了其资源的使用上限,所以在获取CPU和Memory这些信息的时候还需要具体情况分开讨论。

怎么用Go获取进程的各项指标

我们先来讨论普通的宿主机和虚拟机情况下怎么获取这些指标,容器环境放在下一节来说。

获取Go进程的资源使用情况使用gopstuil库即可完成,它我们屏蔽了各个系统之间的差异,帮助我们方便地获取各种系统和硬件信息。gopsutil将不同的功能划分到不同的子包中,它提供的模块主要有:

  • cpu:系统CPU 相关模块;
  • disk:系统磁盘相关模块;
  • docker:docker 相关模块;
  • mem:内存相关模块;
  • net:网络相关;
  • process:进程相关模块;
  • winservices:Windows 服务相关模块。

我们这里只用到了它的process子包,获取进程相关的信息。

声明:process 模块需要 import "github.com/shirou/gopsutil/process"后引入到项目,后面演示的代码会用到的os等模块会统一省略import相关的信息和错误处理,在此提前说明。

创建进程对象

process模块的NewProcess会返回一个持有指定PID的Process对象,方法会检查PID是否存在,如果不存在会返回错误,通过Process对象上定义的其他方法我们可以获取关于进程的各种信息。

  1. p, _ := process.NewProcess(int32(os.Getpid()))

进程的CPU使用率

进程的CPU使用率需要通过计算指定时间内的进程的CPU使用时间变化计算出来

  1. cpuPercent, err := p.Percent(time.Second)

上面返回的是占所有CPU时间的比例,如果想更直观的看占比,可以算一下占单个核心的比例。

  1. cp := cpuPercent / float64(runtime.NumCPU())

内存使用率、线程数和goroutine数

这三个指标的获取过于简单咱们就放在一块说

  1. // 获取进程占用内存的比例
  2. mp, _ := p.MemoryPercent()
  3. // 创建的线程数
  4. threadCount := pprof.Lookup("threadcreate").Count()
  5. // Goroutine数
  6. gNum := runtime.NumGoroutine()

上面获取进程资源占比的方法只有在虚拟机和物理机环境下才能准确。类似Docker这样的Linux容器是靠着Linux的Namespace和Cgroups技术实现的进程隔离和资源限制,是不行的。

现在的服务很多公司是K8s集群部署,所以如果是在Docker中获取Go进程的资源使用情况需要根据Cgroups分配给容器的资源上限进行计算才准确。

容器环境下获取进程指标

在Linux中,Cgroups给用户暴露出来的操作接口是文件系统,它以文件和目录的方式组织在操作系统的/sys/fs/cgroup路径下,在 /sys/fs/cgroup下面有很多诸cpuset、cpu、 memory这样的子目录,每个子目录都代表系统当前可以被Cgroups进行限制的资源种类。

针对我们监控Go进程内存和CPU指标的需求,我们只要知道cpu.cfs_period_us、cpu.cfs_quota_us 和memory.limit_in_bytes 就行。前两个参数需要组合使用,可以用来限制进程在长度为cfs_period的一段时间内,只能被分配到总量为cfs_quota的CPU时间, 可以简单的理解为容器能使用的核心数 = cfs_quota / cfs_period。

所以在容器里获取Go进程CPU的占比的方法,需要做一些调整,利用我们上面给出的公式计算出容器能使用的最大核心数。

  1. cpuPeriod, err := readUint("/sys/fs/cgroup/cpu/cpu.cfs_period_us")
  2.  
  3. cpuQuota, err := readUint("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")
  4.  
  5. cpuNum := float64(cpuQuota) / float64(cpuPeriod)

然后再把通过p.Percent获取到的进程占用机器所有CPU时间的比例除以计算出的核心数即可算出Go进程在容器里对CPU的占比。

  1. cpuPercent, err := p.Percent(time.Second)
  2.  
  3. // cp := cpuPercent / float64(runtime.NumCPU())
  4.  
  5. // 调整为
  6.  
  7. cp := cpuPercent / cpuNum

而容器的能使用的最大内存数,自然就是在memory.limit_in_bytes里指定的啦,所以Go进程在容器中占用的内存比例需要通过下面这种方法获取

  1. memLimit, err := readUint("/sys/fs/cgroup/memory/memory.limit_in_bytes")
  2.  
  3. memInfo, err := p.MemoryInfo
  4.  
  5. mp := memInfo.RSS * 100 / memLimit

上面进程内存信息里的RSS叫常驻内存,是在RAM里分配给进程,允许进程访问的内存量。而读取容器资源用的readUint,是containerd组织在cgroups实现里给出的方法。

  1. func readUint(path string) (uint64, error) {
  2.  
  3. v, err := ioutil.ReadFile(path)
  4.  
  5. if err != nil {
  6.  
  7. return 0, err
  8.  
  9. }
  10.  
  11. return parseUint(strings.TrimSpace(string(v)), 10, 64)
  12.  
  13. }
  14.  
  15. func parseUint(s string, base, bitSize int) (uint64, error) {
  16.  
  17. v, err := strconv.ParseUint(s, base, bitSize)
  18.  
  19. if err != nil {
  20.  
  21. intValue, intErr := strconv.ParseInt(s, base, bitSize)
  22.  
  23. // 1. Handle negative values greater than MinInt64 (and)
  24.  
  25. // 2. Handle negative values lesser than MinInt64
  26.  
  27. if intErr == nil && intValue < 0 {
  28.  
  29. return 0, nil
  30.  
  31. } else if intErr != nil &&
  32.  
  33. intErr.(*strconv.NumError).Err == strconv.ErrRange &&
  34.  
  35. intValue < 0 {
  36.  
  37. return 0, nil
  38.  
  39. }
  40.  
  41. return 0, err
  42.  
  43. }
  44.  
  45. return v, nil
  46.  
  47. }

我在下方参考链接里会给出他们源码的链接。

原文链接:https://mp.weixin.qq.com/s/kASxY9NDYsUpDltO2U3qEw

延伸 · 阅读

精彩推荐
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08