说到让Go程序监控自己进程的资源使用情况,我们先来说说需要监控哪些指标。一般来说,进程的指标最常显示的是进程的内存使用率、CPU使用率、创建线程数。因为Go语言在线程之上维护了Goroutines,所以Go进程的资源索引也需要加上创建的Goroutines数量。而且因为现在很多服务都部署在Kubernetes集群上,一个Go进程往往就是一个Pod,但是容器的资源是和宿主机共享的,只是在创建的时候就指定了它的资源使用上限,所以在获取CPU的时候和Memory这些信息需要在具体情况下单独讨论。如何使用Go获取进程的各种指标我们先讨论一下在普通宿主机和虚拟机的情况下如何获取这些指标,容器环境下节再说。获取Go进程的资源使用情况可以使用gopstuil库来完成,为我们屏蔽了各个系统之间的差异,方便我们获取各种系统和硬件信息。gopsutil将不同的功能分为不同的子包。它提供的模块主要包括:cpu:系统CPU相关模块;disk:系统盘相关模块;docker:docker相关模块;mem:内存相关模块;net:网络相关模块;process:流程相关模块;winservices:Windows服务相关的模块。我们这里只使用它的进程子包来获取进程相关的信息。声明:工程中导入“github.com/shirou/gopsutil/process”后需要导入process模块??。后面演示的代码中会用到的os等模块统一省略import相关信息和错误处理,提前说明。创建进程对象的进程模块的NewProcess会返回一个持有指定PID的Process对象。该方法将检查PID是否存在。如果不存在,将返回错误。我们可以通过定义在Process对象上的其他方法来获取进程的各种信息。p,_:=process.NewProcess(int32(os.Getpid()))进程的CPU使用率进程的CPU使用率需要通过计算指定时间内进程CPU使用时间的变化来计算cpuPercent,错误:=p。上面的Percent(time.Second)返回所有CPU时间的比例。如果想更直观的看比例,可以计算单核的比例。cp:=cpuPercent/float64(runtime.NumCPU())内存占用、线程数、goroutine数这三个指标的获取太简单了。放在一起说//获取进程占用内存的比例mp,_:=p.MemoryPercent()//创建的线程数threadCount:=pprof.Lookup("threadcreate").Count()//Goroutines个数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配比的方法需要做一些调整,使用我们上面给出的公式来计算容器可以使用的最大核数。cpuPeriod,err:=readUint("/sys/fs/cgroup/cpu/cpu.cfs_period_us")cpuQuota,err:=readUint("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")cpuNum:=float64(cpuQuota)/float64(cpuPeriod)然后将通过p.Percent得到的进程得到的机器所有CPU时间的比例除以计算出的核数,就可以计算出容器中Go进程占CPU的比例。cpuPercent,err:=p.Percent(time.Second)//cp:=cpuPercent/float64(runtime.NumCPU())//调整为cp:=cpuPercent/cpuNum容器可以使用的最大内存为自然是在memory.limit_in_bytes中指定的,所以Go进程在容器中占用的内存比例需要通过以下方法获取memLimit,err:=readUint("/sys/fs/cgroup/memory/memory.limit_in_bytes")memInfo,err:=p.MemoryInfomp:=memInfo.RSS*100/memLimit上面进程内存信息中的RSS称为常驻内存,是分配给进程在RAM中允许被访问的内存量过程。用于读取容器资源的readUint是cgroups实现中containerd组织提供的方法。funcreadUint(pathstring)(uint64,error){v,err:=ioutil.ReadFile(path)iferr!=nil{return0,err}returnparseUint(strings.TrimSpace(string(v)),10,64)}funcparseUint(sstring,base,bitSizeint)(uint64,error){v,err:=strconv.ParseUint(s,base,bitSize)iferr!=nil{intValue,intErr:=strconv.ParseInt(s,base,bitSize)//1.HandlenegativevaluesgreaterthanMinInt64(and)//2.HandlenegativevalueslesserthanMinInt64ifintErr==nil&&intValue<0{return0,nil}elseifintErr!=nil&&intErr.(*strconv.NumError).Err==strconv.ErrRange&&intValue<0{return0,nil}return0,errnil}我将在下面的参考链接中提供指向其源代码的链接。
