feat(monitor): 自 go-hua 迁入系统资源监控

- Start 后台每秒采集(CPU/内存/磁盘/网络/进程/连接/主机), GetInfo 取快照
- 基于 gopsutil, 附测试、examples/monitor 例程与包 README
This commit is contained in:
2026-09-20 12:29:37 +08:00
parent 138c929bd4
commit 30a0889ffa
11 changed files with 963 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
// monitor 包示例:系统资源监控(CPU/内存/磁盘/网络/进程/连接),Start 后台采集,GetInfo 随时取最新快照
package main
import (
"encoding/json"
"fmt"
"time"
"git.zeroonesoft.cn/golib/zogo/monitor"
)
func main() {
// 启动后台采集(每秒刷新;主机/连接信息每 5 秒刷新一次)
monitor.Start("1.0.0")
// 等待采集几轮数据
time.Sleep(6 * time.Second)
info := monitor.GetInfo()
fmt.Printf("主机: %s 系统: %s 开机时长: %d 秒\n", info.Host.HostName, info.Host.Platform, info.Host.Uptime)
fmt.Printf("CPU: 物理核心 %d 逻辑核心 %d 总使用率 %.1f%%\n", info.Cpu.Core, info.Cpu.Logic, info.Cpu.UsedPercent)
fmt.Printf("内存: 总计 %d MB 已用 %.1f%%\n", info.Mem.Total/1024/1024, info.Mem.UsedPercent)
fmt.Printf("TCP 连接: %d UDP 连接: %d 监听端口: %d 个\n", info.Conn.Tcp, info.Conn.Udp, len(info.Conn.Listen))
fmt.Printf("磁盘分区: %d 个 网卡: %d 个 进程: %d 个\n", len(info.Disk), len(info.Net), len(info.Process))
for _, d := range info.Disk {
if d.Total > 0 {
fmt.Printf(" 磁盘 %s 挂载 %s 已用 %.1f%% (%s)\n", d.Device, d.MountPoint, d.UsedPercent, d.UsedVolume)
}
}
// 精简版信息(适合定时上报),JSON 序列化输出
lite := monitor.ToLiteInfo(info)
buf, _ := json.Marshal(lite)
fmt.Printf("Lite JSON: %d 字节\n", len(buf))
}
+36
View File
@@ -0,0 +1,36 @@
# monitor
系统资源监控:CPU/内存/磁盘/网络/进程/TCP·UDP 连接/主机信息,基于 `gopsutil`。
`Start` 后台每秒采集(主机/连接每 5 秒),`GetInfo` 随时取最新快照。
> 迁移自 go-hua/monitor,代码保持原样。
## 用法
```go
import (
"time"
"git.zeroonesoft.cn/golib/zogo/monitor"
)
// 启动后台采集
monitor.Start("1.0.0")
time.Sleep(6 * time.Second) // 等采集几轮
info := monitor.GetInfo() // MonInfo 全量快照
fmt.Println(info.Cpu.UsedPercent, info.Mem.UsedPercent)
// 精简版快照(面向上报的轻量结构)
lite := monitor.ToLiteInfo(info)
// 单项直查(不经后台采集)
procs, err := monitor.GetProcessInfo()
```
完整可运行例程:[examples/monitor/main.go](../examples/monitor/main.go)
## 注意
- `Start` 起后台 goroutine,进程退出无需显式停止;重复调用会起多份采集。
- CPU 使用率首次采样为 0(需两个采样窗口),取值前至少等一个采集周期。
+93
View File
@@ -0,0 +1,93 @@
package monitor
import (
"fmt"
"strconv"
"github.com/shirou/gopsutil/v3/cpu"
)
// CPU CPU 采集信息
type CPU struct {
CPU int32 `json:"cpu"`
VendorID string `json:"vendorId"`
ModelName string `json:"modelName"`
Mhz float64 `json:"mhz"`
Cores int32 `json:"cores"`
}
// CpuBaseInfo CPU 基本信息(型号/核心数/频率等,gopsutil cpu.Info)
type CpuBaseInfo struct {
Core int `json:"core"`
Logic int `json:"logic"`
Info []CPU `json:"info"`
}
// CpuInfo 每逻辑核心使用率
type CpuInfo struct {
CpuBaseInfo
CpuUsedInfo
}
// CpuUsedInfo CPU 使用率明细(含用户/系统/空闲等占比)
type CpuUsedInfo struct {
UsedPercent float64 `json:"usedPercent"`
Used []float64 `json:"used"`
}
// CpuInfoLite 精简版 CPU 信息
type CpuInfoLite struct {
Core int `json:"core"`
Logic int `json:"logic"`
Name []string `json:"name"`
UsedPercent float64 `json:"usedPercent"`
Used []float64 `json:"used"`
}
// getCpuInfo 采集 CPU 基础信息与每核心使用率
func getCpuInfo() (CpuInfo, error) {
info := CpuInfo{}
cpuInfo, err := cpu.Info()
if err != nil {
return info, err
}
info.Info = make([]CPU, len(cpuInfo))
for i, v := range cpuInfo {
info.Info[i].CPU = v.CPU
info.Info[i].VendorID = v.VendorID
info.Info[i].ModelName = v.ModelName
info.Info[i].Mhz = v.Mhz
info.Info[i].Cores = v.Cores
}
core, err := cpu.Counts(false)
if err != nil {
return info, err
}
logic, err := cpu.Counts(true)
if err != nil {
return info, err
}
info.Core = core
info.Logic = logic
return info, nil
}
// getCpuUsed 采集 CPU 使用率明细
func getCpuUsed() (CpuUsedInfo, error) {
info := CpuUsedInfo{}
percent, err := cpu.Percent(0, true)
if err != nil {
return info, err
}
var usedPercent float64 = 0.0
for i, v := range percent {
percent[i], _ = strconv.ParseFloat(fmt.Sprintf("%.1f", v), 64)
usedPercent += v
}
usedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", usedPercent/float64(len(percent))), 64)
info.Used = percent
info.UsedPercent = usedPercent
return info, nil
}
+117
View File
@@ -0,0 +1,117 @@
package monitor
import (
"fmt"
"github.com/shirou/gopsutil/v3/disk"
"path/filepath"
"runtime"
"strconv"
)
// DiskInfo 磁盘分区信息(空间占用 + IO 统计)
type DiskInfo struct {
Device string `json:"device"`
MountPoint string `json:"mountPoint"`
FsType string `json:"fsType"`
Path string `json:"path"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
UsedVolume string `json:"usedVolume"`
ReadCount uint64 `json:"readCount"`
WriteCount uint64 `json:"writeCount"`
ReadBytes uint64 `json:"readBytes"`
WriteBytes uint64 `json:"writeBytes"`
ReadTime uint64 `json:"readTime"`
WriteTime uint64 `json:"writeTime"`
TotalReadCount uint64 `json:"totalReadCount"`
TotalWriteCount uint64 `json:"totalWriteCount"`
TotalReadBytes uint64 `json:"totalReadBytes"`
TotalWriteBytes uint64 `json:"totalWriteBytes"`
TotalReadTime uint64 `json:"totalReadTime"`
TotalWriteTime uint64 `json:"totalWriteTime"`
}
// DiskInfoLite 精简版磁盘信息
type DiskInfoLite struct {
Device string `json:"device"`
MountPoint string `json:"mountPoint"`
UsedPercent float64 `json:"usedPercent"`
UsedVolume string `json:"usedVolume"`
ReadBytes uint64 `json:"readBytes"`
WriteBytes uint64 `json:"writeBytes"`
}
// getDiskInfo 采集所有磁盘分区信息
func getDiskInfo() ([]DiskInfo, error) {
partitions, err := disk.Partitions(false)
// fmt.Printf("disk partitions:%v\n\n", partitions)
if err != nil {
// fmt.Printf("get disk partitions err:%s\n", err.Error())
// return nil, err
}
names := make([]string, len(partitions))
// fmt.Println(len(names))
for i, _ := range partitions {
names[i] = partitions[i].Device
}
// fmt.Println(names)
io, err := disk.IOCounters(names...)
// fmt.Printf("disk io:%v\n\n", io)
if err != nil {
//return nil, err
// fmt.Printf("get disk IOCounters err:%s\n", err.Error())
}
disks := make([]DiskInfo, len(partitions))
for i, _ := range disks {
disks[i].Device = partitions[i].Device
disks[i].MountPoint = partitions[i].Mountpoint
disks[i].FsType = partitions[i].Fstype
// 使用量 用路径获取
usage, err := disk.Usage(partitions[i].Mountpoint)
if err != nil {
// fmt.Printf("get disk Usage err:%s\n", err.Error())
break
}
disks[i].Path = usage.Path
disks[i].Total = usage.Total
disks[i].Free = usage.Total
disks[i].Used = usage.Used
disks[i].UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", usage.UsedPercent), 64)
disks[i].UsedVolume = formatFileSize(usage.Used) + "/" + formatFileSize(usage.Total)
name := partitions[i].Device
if runtime.GOOS == "linux" {
name = filepath.Base(name)
}
//fmt.Println(name, partitions[i].Device)
disks[i].TotalReadCount = io[name].ReadCount
disks[i].TotalWriteCount = io[name].WriteCount
disks[i].TotalReadBytes = io[name].ReadBytes
disks[i].TotalWriteBytes = io[name].WriteBytes
disks[i].TotalReadTime = io[name].ReadTime
disks[i].TotalWriteTime = io[name].WriteTime
// FIXME:分区会变化,应该用name获取
if len(monInfo.Disk) > i {
disks[i].ReadCount = io[name].ReadCount - monInfo.Disk[i].TotalReadCount
disks[i].WriteCount = io[name].WriteCount - monInfo.Disk[i].TotalWriteCount
disks[i].ReadBytes = io[name].ReadBytes - monInfo.Disk[i].TotalReadBytes
disks[i].WriteBytes = io[name].WriteBytes - monInfo.Disk[i].TotalWriteBytes
disks[i].ReadTime = io[name].ReadTime - monInfo.Disk[i].TotalReadTime
disks[i].WriteTime = io[name].WriteTime - monInfo.Disk[i].TotalWriteTime
}
}
if err != nil {
return disks, err
}
return disks, nil
}
+34
View File
@@ -0,0 +1,34 @@
package monitor
import (
"strings"
"github.com/shirou/gopsutil/v3/host"
)
// HostInfo 主机信息(主机名/系统/开机时长等,gopsutil host.InfoStat)
type HostInfo struct {
HostName string `json:"hostName"`
Uptime uint64 `json:"uptime"`
BootTime uint64 `json:"bootTime"`
Procs uint64 `json:"procs"`
Os string `json:"os"`
Platform string `json:"platform"`
}
// getHostInfo 采集主机信息
func getHostInfo() (HostInfo, error) {
info := HostInfo{}
h, err := host.Info()
if err != nil {
return info, err
}
info.HostName = h.Hostname
info.Uptime = h.Uptime
info.BootTime = h.BootTime
info.Procs = h.Procs
info.Os = h.OS
info.Platform = strings.Trim(strings.Replace(h.Platform, "Microsoft", "", -1), " ")
return info, nil
}
+29
View File
@@ -0,0 +1,29 @@
package monitor
import (
"fmt"
"strconv"
"github.com/shirou/gopsutil/v3/load"
)
// LoadInfo 系统负载信息(1/5/15 分钟均值)
type LoadInfo struct {
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
}
// getLoadInfo 采集系统负载
func getLoadInfo() (LoadInfo, error) {
info := LoadInfo{}
v, err := load.Avg()
if err != nil {
return info, err
}
info.Load1, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", v.Load1), 64)
info.Load5, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", v.Load5), 64)
info.Load15, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", v.Load15), 64)
//fmt.Printf("%v\n", info)
return info, nil
}
+67
View File
@@ -0,0 +1,67 @@
package monitor
import (
"fmt"
"strconv"
"github.com/shirou/gopsutil/v3/mem"
)
// SwapDevice 交换分区设备信息
type SwapDevice struct {
Name string `json:"name"`
UsedBytes uint64 `json:"usedBytes"`
FreeBytes uint64 `json:"freeBytes"`
}
// MemInfo 内存信息(总量/已用/可用/使用率等,gopsutil mem.VirtualMemoryStat)
type MemInfo struct {
Total uint64 `json:"total"`
Available uint64 `json:"available"`
Used uint64 `json:"used"`
Free uint64 `json:"free"`
UsedPercent float64 `json:"usedPercent"`
SwapTotal uint64 `json:"swapTotal"`
SwapAvailable uint64 `json:"SwapAvailable"`
SwapUsed uint64 `json:"swapUsed"`
SwapFree uint64 `json:"swapFree"`
SwapUsedPercent float64 `json:"swapUsedPercent"`
SwapDevices []SwapDevice `json:"swapDevices"`
}
// getMemInfo 采集内存与交换区信息
func getMemInfo() (MemInfo, error) {
info := MemInfo{}
v, err := mem.VirtualMemory()
if err != nil {
return info, err
}
info.Total = v.Total
info.Used = v.Used
info.Free = v.Free
info.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", v.UsedPercent), 64)
s, err := mem.SwapMemory()
if err != nil {
return info, err
}
info.SwapTotal = s.Total
info.SwapUsed = s.Used
info.SwapFree = s.Free
info.SwapUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", s.UsedPercent), 64)
devices, err := mem.SwapDevices()
if err != nil {
return info, err
}
for _, device := range devices {
info.SwapDevices = append(info.SwapDevices, SwapDevice{
Name: device.Name,
UsedBytes: device.UsedBytes,
FreeBytes: device.FreeBytes,
})
}
return info, err
}
+172
View File
@@ -0,0 +1,172 @@
// Package monitor 提供系统资源监控(CPU/内存/磁盘/网络/进程/连接/主机),后台定时采集,GetInfo 随时取最新快照。
package monitor
import (
"fmt"
"sync"
"time"
)
// MonInfo 全量监控信息快照
type MonInfo struct {
Host HostInfo `json:"host"`
Cpu CpuInfo `json:"cpu"`
Mem MemInfo `json:"mem"`
Load LoadInfo `json:"load"`
Process []ProcessInfo `json:"process"`
Disk []DiskInfo `json:"disk"`
Net []NetInfo `json:"net"`
Conn NetConnInfo `json:"conn"`
Ver string `json:"ver"`
}
// MonInfoSimple 精简版监控信息(适合定时上报)
type MonInfoSimple struct {
Host HostInfo `json:"host"`
Cpu CpuInfoLite `json:"cpu"`
Mem MemInfo `json:"mem"`
Load LoadInfo `json:"load"`
Conn NetConnInfo `json:"conn"`
Net NetInfoLite `json:"net"`
Disk []DiskInfoLite `json:"disk"`
Ver string `json:"ver"`
}
var (
interval int = 1
monInfo MonInfo
mux sync.Mutex
one sync.Once
)
// Start 启动后台采集
// 普通信息每秒刷新,主机/连接信息每 5 秒刷新一次;ver 为程序版本号
func Start(ver string) {
monInfo.Ver = ver
one.Do(proc)
}
// GetInfo 获取最新的监控信息快照
func GetInfo() MonInfo {
mux.Lock()
info := monInfo
mux.Unlock()
return info
}
// proc 监控采集主循环
func proc() {
go func() {
var i uint64 = 0
// CPU信息应该不会变,CPU热插拨除外
{
cpuBaseVal, _ := getCpuInfo()
monInfo.Cpu.Core = cpuBaseVal.Core
monInfo.Cpu.Logic = cpuBaseVal.Logic
monInfo.Cpu.Info = cpuBaseVal.Info
monInfo.Cpu.Core = cpuBaseVal.Core
}
// 循环获取信息
for {
i = i + uint64(interval)
//每次都获取
diskVal, err := getDiskInfo()
if err != nil {
fmt.Println("获取硬盘信出错:", err.Error())
}
cpuUsedVal, _ := getCpuUsed()
memVal, _ := getMemInfo()
netVal, _ := getNetInfo()
loadVal, _ := getLoadInfo()
process, _ := GetProcessInfo()
mux.Lock()
//只获取CPU使用率,CPU基本信息一般不会发生改变
monInfo.Cpu.UsedPercent = cpuUsedVal.UsedPercent
monInfo.Cpu.Used = cpuUsedVal.Used
monInfo.Load = loadVal
monInfo.Disk = diskVal
monInfo.Mem = memVal
monInfo.Net = netVal
monInfo.Process = process
mux.Unlock()
// 间隔5次调用一次 // 建议60秒获取一次,例如更改计算机名,导致获取不准确
if i%5 == 0 {
hostVal, _ := getHostInfo()
connVal, _ := getNetConnInfo()
mux.Lock()
monInfo.Host = hostVal
monInfo.Conn = connVal
mux.Unlock()
}
time.Sleep(time.Second * time.Duration(interval))
}
}()
}
// ToLiteInfo 将全量信息转换为精简版
func ToLiteInfo(info MonInfo) MonInfoSimple {
lite := MonInfoSimple{}
lite.Host = info.Host
lite.Load = info.Load
lite.Mem = info.Mem
lite.Conn = info.Conn
lite.Cpu.Core = info.Cpu.Core
lite.Cpu.Logic = info.Cpu.Logic
lite.Cpu.UsedPercent = info.Cpu.UsedPercent
lite.Cpu.Logic = info.Cpu.Logic
lite.Cpu.Name = make([]string, len(info.Cpu.Info))
for index, c := range info.Cpu.Info {
lite.Cpu.Name[index] = c.ModelName
}
lite.Disk = make([]DiskInfoLite, len(info.Disk))
for index, d := range info.Disk {
lite.Disk[index].Device = d.Device
lite.Disk[index].MountPoint = d.MountPoint
lite.Disk[index].UsedPercent = d.UsedPercent
lite.Disk[index].UsedVolume = d.UsedVolume
lite.Disk[index].ReadBytes = d.ReadBytes
lite.Disk[index].WriteBytes = d.WriteBytes
}
lite.Net = NetInfoLite{}
for _, n := range info.Net {
lite.Net.BytesSent += n.BytesSent
lite.Net.BytesRecv += n.BytesRecv
lite.Net.PacketsSent += n.PacketsSent
lite.Net.PacketsRecv += n.PacketsRecv
lite.Net.TotalBytesSent += n.TotalBytesSent
lite.Net.TotalBytesRecv += n.TotalBytesRecv
}
lite.Ver = info.Ver
return lite
}
// 字节的单位转换 保留两位小数
func formatFileSize(fileSize uint64) (size string) {
if fileSize < 1024 {
//return strconv.FormatInt(fileSize, 10) + "B"
return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
} else if fileSize < (1024 * 1024) {
return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
} else if fileSize < (1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
} else if fileSize < (1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
} else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
} else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
}
+82
View File
@@ -0,0 +1,82 @@
package monitor
import "testing"
func TestToLiteInfo(t *testing.T) {
info := MonInfo{
Ver: "1.0.0",
Cpu: CpuInfo{
CpuBaseInfo: CpuBaseInfo{
Core: 4,
Logic: 8,
Info: []CPU{{CPU: 0, ModelName: "TestCPU"}},
},
CpuUsedInfo: CpuUsedInfo{UsedPercent: 55.5},
},
Mem: MemInfo{Total: 16 * 1024 * 1024 * 1024, UsedPercent: 60},
Load: LoadInfo{Load1: 0.5},
Net: []NetInfo{
{Name: "eth0", BytesSent: 10, BytesRecv: 20, PacketsSent: 1, PacketsRecv: 2, TotalBytesSent: 100, TotalBytesRecv: 200},
{Name: "eth1", BytesSent: 30, BytesRecv: 40, PacketsSent: 3, PacketsRecv: 4, TotalBytesSent: 300, TotalBytesRecv: 400},
},
Disk: []DiskInfo{
{Device: "C:", MountPoint: "C:\\", UsedPercent: 80, UsedVolume: "80GB", ReadBytes: 5, WriteBytes: 6},
},
}
lite := ToLiteInfo(info)
if lite.Ver != "1.0.0" {
t.Errorf("Ver = %q", lite.Ver)
}
if lite.Cpu.Core != 4 || lite.Cpu.Logic != 8 {
t.Errorf("Cpu Core/Logic = %d/%d, want 4/8", lite.Cpu.Core, lite.Cpu.Logic)
}
if lite.Cpu.UsedPercent != 55.5 {
t.Errorf("Cpu.UsedPercent = %v", lite.Cpu.UsedPercent)
}
if len(lite.Cpu.Name) != 1 || lite.Cpu.Name[0] != "TestCPU" {
t.Errorf("Cpu.Name = %v", lite.Cpu.Name)
}
if lite.Mem.Total != 16*1024*1024*1024 {
t.Errorf("Mem.Total = %d", lite.Mem.Total)
}
if lite.Load.Load1 != 0.5 {
t.Errorf("Load1 = %v", lite.Load.Load1)
}
// Net 多网卡应累加
if lite.Net.BytesSent != 40 || lite.Net.BytesRecv != 60 {
t.Errorf("Net.BytesSent/Recv = %d/%d, want 40/60", lite.Net.BytesSent, lite.Net.BytesRecv)
}
if lite.Net.TotalBytesSent != 400 || lite.Net.TotalBytesRecv != 600 {
t.Errorf("Net.Total = %d/%d, want 400/600", lite.Net.TotalBytesSent, lite.Net.TotalBytesRecv)
}
if len(lite.Disk) != 1 || lite.Disk[0].Device != "C:" || lite.Disk[0].ReadBytes != 5 {
t.Errorf("Disk = %+v", lite.Disk)
}
}
func TestFormatFileSize(t *testing.T) {
cases := []struct {
in uint64
want string
}{
{512, "512.00B"},
{1536, "1.50KB"},
{1024 * 1024, "1.00MB"},
{1024 * 1024 * 1024, "1.00GB"},
}
for _, c := range cases {
if got := formatFileSize(c.in); got != c.want {
t.Errorf("formatFileSize(%d) = %q, want %q", c.in, got, c.want)
}
}
}
func TestGetInfoZeroValue(t *testing.T) {
// 未调用 Start 时 GetInfo 返回零值快照且不 panic
info := GetInfo()
if info.Ver != "" {
t.Errorf("未启动时 Ver 应为空: %q", info.Ver)
}
}
+217
View File
@@ -0,0 +1,217 @@
package monitor
import (
"sort"
"github.com/shirou/gopsutil/v3/net"
)
// NetInfo 网卡信息(gopsutil net.InterfaceStat)
type NetInfo struct {
Name string `json:"name"` // interface name
BytesSent uint64 `json:"bytesSent"` // number of bytes sent
BytesRecv uint64 `json:"bytesRecv"` // number of bytes received
PacketsSent uint64 `json:"packetsSent"` // number of packets sent
PacketsRecv uint64 `json:"packetsRecv"` // number of packets received
TotalBytesSent uint64 `json:"totalBytesSent"` // number of bytes sent
TotalBytesRecv uint64 `json:"totalBytesRecv"` // number of bytes received
TotalPacketsSent uint64 `json:"totalPacketsSent"` // number of packets sent
TotalPacketsRecv uint64 `json:"totalPacketsRecv"` // number of packets received
}
// NetInfoLite 精简版网卡信息
type NetInfoLite struct {
BytesSent uint64 `json:"bytesSent"` // number of bytes sent
BytesRecv uint64 `json:"bytesRecv"` // number of bytes received
PacketsSent uint64 `json:"packetsSent"` // number of packets sent
PacketsRecv uint64 `json:"packetsRecv"` // number of packets received
TotalBytesSent uint64 `json:"totalBytesSent"` // number of bytes sent
TotalBytesRecv uint64 `json:"totalBytesRecv"` // number of bytes received
}
// Addr 网卡地址
type Addr struct {
Ip string `json:"ip"`
Port uint32 `json:"port"`
}
// ConnInfo 连接汇总:TCP/UDP 计数、监听端口列表与每进程连接计数
type ConnInfo struct {
Fd uint32 `json:"fd"`
Family uint32 `json:"family"`
Type uint32 `json:"type"`
LocalAddr Addr `json:"localAddr"`
RemoteAddr Addr `json:"remoteAddr"`
Status string `json:"status"`
Pid int32 `json:"pid"`
}
// NetListen 监听端口详情
type NetListen struct {
Addr string `json:"addr"`
Port uint32 `json:"port"`
Conn int `json:"conn"`
ConnInfo
}
// ProcConn 每进程连接计数
type ProcConn struct {
Pid int `json:"pid"`
Name string `json:"name"`
Conn int `json:"conn"`
}
// NetConnInfo 单条网络连接信息(gopsutil net.ConnectionStat)
type NetConnInfo struct {
Tcp int `json:"tcp"`
Udp int `json:"udp"`
Listen []NetListen `json:"listen"`
}
// getNetInfo 采集网卡列表
func getNetInfo() ([]NetInfo, error) {
netIo, err := net.IOCounters(true)
if err != nil {
return nil, err
}
nets := make([]NetInfo, len(netIo))
for i, v := range netIo {
nets[i].Name = netIo[i].Name
nets[i].TotalBytesSent = v.BytesSent
nets[i].TotalBytesRecv = v.BytesRecv
nets[i].TotalPacketsSent = v.PacketsSent
nets[i].TotalPacketsRecv = v.PacketsRecv
// 应该使用网卡名 未处理
if len(monInfo.Net) > i {
nets[i].BytesSent = v.BytesSent - monInfo.Net[i].TotalBytesSent
nets[i].BytesRecv = v.BytesRecv - monInfo.Net[i].TotalBytesRecv
nets[i].PacketsSent = v.PacketsSent - monInfo.Net[i].TotalPacketsSent
nets[i].PacketsRecv = v.PacketsRecv - monInfo.Net[i].TotalPacketsRecv
}
}
return nets, err
}
// getNetConnInfo 采集 TCP/UDP 连接计数与监听端口汇总
func getNetConnInfo() (NetConnInfo, error) {
// linux TcpStatus
/* {
"01": "ESTABLISHED",
"02": "SYN_SENT",
"03": "SYN_RECV",
"04": "FIN_WAIT1",
"05": "FIN_WAIT2",
"06": "TIME_WAIT",
"07": "CLOSE",
"08": "CLOSE_WAIT",
"09": "LAST_ACK",
"0A": "LISTEN",
"0B": "CLOSING",
} */
// windows TcpStatus
/* {
1: "CLOSED",
2: "LISTEN",
3: "SYN_SENT",
4: "SYN_RECEIVED",
5: "ESTABLISHED",
6: "FIN_WAIT_1",
7: "FIN_WAIT_2",
8: "CLOSE_WAIT",
9: "CLOSING",
10: "LAST_ACK",
11: "TIME_WAIT",
12: "DELETE",
} */
/* {
"all": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
"tcp": {kindTCP4, kindTCP6},
"tcp4": {kindTCP4},
"tcp6": {kindTCP6},
"udp": {kindUDP4, kindUDP6},
"udp4": {kindUDP4},
"udp6": {kindUDP6},
"inet": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
"inet4": {kindTCP4, kindUDP4},
"inet6": {kindTCP6, kindUDP6},
} */
info := NetConnInfo{}
tcp, err := net.Connections("tcp")
if err != nil {
return info, nil
}
udp, err := net.Connections("udp")
if err != nil {
return info, nil
}
//logger.Infof("net conn tcp:%d udp:%d", info.Tcp, info.Udp)
// 监听列表
listen := make([]NetListen, 0)
for _, v := range tcp {
if v.Status == "LISTEN" {
inPort := false
for _, l := range listen {
if l.Port == v.Laddr.Port {
inPort = true
break
}
}
if !inPort {
listen = append(listen, NetListen{
Addr: v.Laddr.IP,
Port: v.Laddr.Port,
Conn: 0,
ConnInfo: ConnInfo{
Fd: v.Fd,
Family: v.Family,
Type: v.Type,
LocalAddr: Addr{
Ip: v.Laddr.IP,
Port: v.Laddr.Port,
},
RemoteAddr: Addr{
Ip: v.Raddr.IP,
Port: v.Raddr.Port,
},
Status: v.Status,
Pid: v.Pid,
},
})
}
}
}
// 连接计数
count := 0
for _, v := range tcp {
if v.Status == "ESTABLISHED" {
for index, l := range listen {
if l.Port == v.Laddr.Port {
listen[index].Conn++
break
}
}
count++
}
}
//排序
sort.Slice(listen, func(i, j int) bool {
return listen[i].Conn > listen[j].Conn
})
// TCP和UDP连接数
info.Tcp = count //len(tcp)
info.Udp = len(udp)
info.Listen = listen
return info, nil
}
+80
View File
@@ -0,0 +1,80 @@
package monitor
import (
"github.com/shirou/gopsutil/v3/process"
"path/filepath"
)
// MemoryInfoStat 进程内存信息(RSS/虚拟内存)
type MemoryInfoStat struct {
Rss uint64 `json:"res"`
Vms uint64 `json:"vms"`
}
// ProcessInfo 进程信息(PID/路径/CPU/内存占用与启动时间)
type ProcessInfo struct {
Pid int32 `json:"pid"`
Name string `json:"name"`
ExE string `json:"exe"`
Cmd string `json:"cmd"`
Cpu float64 `json:"cpu"`
Mem MemoryInfoStat `json:"mem"`
CreateTime int64 `json:"createTime"`
}
// GetProcessInfo 获取所有进程信息
func GetProcessInfo() ([]ProcessInfo, error) {
v, err := process.Processes()
if err != nil {
return nil, err
}
info := make([]ProcessInfo, len(v))
for i, p := range v {
name := ""
exe, _ := p.Exe()
cmd, _ := p.Cmdline()
// 先从执行文件中获取进程名
// 如果执行文件为空,再调用 Name() 方式,Name()方式很占用CPU
if exe == "" {
name, _ = p.Name()
} else {
name = filepath.Base(exe)
}
mem, _ := p.MemoryInfo()
cpu, _ := p.CPUPercent()
createTime, _ := p.CreateTime()
p.Times()
// 不获取线程数 太占用用CPU
// p.NumThreads()
info[i].Pid = p.Pid
info[i].Name = name
info[i].ExE = exe
info[i].Cmd = cmd
info[i].Cpu = cpu
if mem != nil {
info[i].Mem = MemoryInfoStat{
Rss: mem.RSS,
Vms: mem.VMS,
}
}
info[i].CreateTime = createTime
if name == "QQ.exe" {
//fmt.Printf("Pid:%d name:%s exe:%s\n", p.Pid, name, exe)
//fmt.Printf("工作集:%d 提交大小:%d\n", mem.RSS, mem.VMS)
//fmt.Printf("CPU:%.2f%% \n", cpu)
}
}
if err != nil {
return nil, err
}
return info, nil
}