在Linux系统中,/proc 目录下的文件和文件夹代表了系统中的进程和硬件信息,包括CPU、内存、硬盘、网络等。之前的文章prometheus client_java实现进程的CPU、内存、IO、流量的可观测介绍了使用top命令来获取相关信息,本文使用原生的go库gopsutil来读取/proc目录的信息,实现系统和进程的性能监控
gopsutil是一个用于Go语言的库,旨在提供类似于Python的psutil库的功能,用于检索系统信息和进程管理。它支持多种操作系统,包括FreeBSD、Linux、Windows、macOS、OpenBSD和Solaris等。
github官方地址:https://github.com/shirou/gopsutil
API文档地址:https://pkg.go.dev/github.com/shirou/gopsutil
部分实现代码
import (
"container/list"
"github.com/prometheus/client_golang/prometheus"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/process"
"math"
"runtime"
"strconv"
"sync"
"time"
)
// ......忽略掉部分代码
// 通过gopsutil库读取/proc/pid目录,获取系统和进程信息
func handleSystemInfo() (TopData, error) {
var topData = TopData{ProcessList: list.New()}
// 获取系统1分钟、5分钟、15分钟负载
avg, uptimeError := load.Avg()
if uptimeError == nil {
topData.Uptime1 = avg.Load1
topData.Uptime5 = avg.Load5
topData.Uptime15 = avg.Load15
}
// 获取CPU使用率
cpuUse, _ := cpu.Percent(0, false)
topData.CupUsed = cpuUse[0]
// 获取物理内存
memInfo, _ := mem.VirtualMemory()
topData.MemTotal = float64(memInfo.Total / 1024)
topData.MemFree = float64(memInfo.Free / 1024)
topData.MemUsed = float64(memInfo.Used / 1024)
topData.MemBuff = float64(memInfo.Buffers / 1024)
// 获取交换区内存
topData.SwapTotal = float64(memInfo.SwapTotal / 1024)
topData.SwapFree = float64(memInfo.SwapFree / 1024)
topData.SwapUsed = float64((memInfo.SwapTotal - memInfo.SwapFree) / 1024)
topData.SwapAvail = float64(memInfo.SwapCached / 1024)
// 获取所有进程pid
info, _ := process.Pids() //获取当前所有进程的pid
// 计算各个进程状态的进程数量
taskMap := map[string]int{
process.Running: 0,
process.Blocked: 0,
process.Idle: 0,
process.Lock: 0,
process.Sleep: 0,
process.Stop: 0,
process.Wait: 0,
process.Zombie: 0,
}
for _, pid := range info {
p, _ := process.NewProcess(pid)
cmd, cmderror := p.Cmdline()
name, _ := p.Name()
exe, _ := p.Exe()
status, _ := p.Status()
statusCount, exists := taskMap[status[0]]
if exists {
taskMap[status[0]] = statusCount + 1
}
if isSystemProcess(name, exe) == true {
continue
}
proc := ProcessInfo{In: "n"}
proc.Pid = strconv.Itoa(int(p.Pid))
proc.User, _ = p.Username()
// p.CPUPercent() // 与top值不一样,
// p.Percent(time.Second) // 统计的CPU使用率,与top值相近,但是有阻塞
// CalculateCpuPercent自己实现CPU使用率的统计,与top值一致,参考p.Percent的实现
cpuPercent, _ := CalculateCpuPercent(p)
proc.Cpu = float64(cpuPercent)
memoryPercent, _ := p.MemoryPercent()
proc.Mem = math.Round(float64(memoryPercent)*100) / 100
proc.Res = math.Round(float64(topData.MemTotal*proc.Mem)) / 100
// 计算进程读写硬盘,基于p.IOCounters()数据统计
readBytes, writeBytes, _ := CalculateDiskPercent(p)
proc.ReadBytes = readBytes
proc.WriteBytes = writeBytes
topData.ProcessList.PushBack(proc)
}
topData.TaskRunning = taskMap[process.Running]
topData.TaskBlocked = taskMap[process.Blocked]
topData.TaskIdle = taskMap[process.Idle]
topData.TaskLock = taskMap[process.Lock]
topData.TaskSleeping = taskMap[process.Sleep]
topData.TaskStopped = taskMap[process.Stop]
topData.TaskWait = taskMap[process.Wait]
topData.TaskZombie = taskMap[process.Zombie]
for _, v := range taskMap {
topData.TaskTotal += v
}
return topData, nil
}
通过gopsutil,已经能够获取到系统的CPU使用率、内存使用率、进程数量、进程状态,还有进程的CPU使用率、内存使用率、进程读写硬盘等数据。不需要调用top、iotop命令的功能。
获取正在监听端口的进程
import (
"container/list"
"github.com/prometheus/client_golang/prometheus"
"github.com/shirou/gopsutil/v4/net"
"github.com/shirou/gopsutil/v4/process"
"strconv"
"syscall"
)
// 省略部分代码
func getListenPort() (*list.List, error) {
var portList = list.New()
conns, err := net.Connections("all")
if err != nil {
return nil, err
}
for _, conn := range conns {
if conn.Status == "LISTEN" {
pid := conn.Pid
if pid == 0 {
continue
}
p, _ := process.NewProcess(pid)
cmdName, _ := p.Name()
proc := ProcessInfo{In: "n",
Pid: strconv.Itoa(int(p.Pid)),
}
cmd, _ := p.Cmdline()
proc.Cmd = common.FilterAlias(cmd)
proc.User, _ = p.Username()
portInfo := PortInfo{
Proto: getProto(conn.Family, conn.Type),
Ip: conn.Laddr.IP,
Port: uint16(conn.Laddr.Port),
Pid: strconv.Itoa(int(conn.Pid)),
Cmd: cmdName,
Proc: proc,
}
portList.PushFront(portInfo)
}
}
return portList, nil
}
func getProto(family uint32, sockType uint32) string {
switch family {
case syscall.AF_INET:
if sockType == syscall.SOCK_STREAM {
return "tcp"
} else if sockType == syscall.SOCK_DGRAM {
return "udp"
}
case syscall.AF_INET6:
if sockType == syscall.SOCK_STREAM {
return "tcp6"
} else if sockType == syscall.SOCK_DGRAM {
return "udp6"
}
}
return ""
}
以上代码实现获取正在端口监控的进程的功能。不需要再调用netstat命令。