diff --git a/examples/monitor/main.go b/examples/monitor/main.go new file mode 100644 index 0000000..1c9bd52 --- /dev/null +++ b/examples/monitor/main.go @@ -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)) +} diff --git a/monitor/README.md b/monitor/README.md new file mode 100644 index 0000000..73f18e0 --- /dev/null +++ b/monitor/README.md @@ -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(需两个采样窗口),取值前至少等一个采集周期。 diff --git a/monitor/cpu.go b/monitor/cpu.go new file mode 100644 index 0000000..5407cc6 --- /dev/null +++ b/monitor/cpu.go @@ -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 +} diff --git a/monitor/disk.go b/monitor/disk.go new file mode 100644 index 0000000..df2fa63 --- /dev/null +++ b/monitor/disk.go @@ -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 +} diff --git a/monitor/host.go b/monitor/host.go new file mode 100644 index 0000000..1d7e246 --- /dev/null +++ b/monitor/host.go @@ -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 +} diff --git a/monitor/load.go b/monitor/load.go new file mode 100644 index 0000000..c114c33 --- /dev/null +++ b/monitor/load.go @@ -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 +} diff --git a/monitor/mem.go b/monitor/mem.go new file mode 100644 index 0000000..36d4666 --- /dev/null +++ b/monitor/mem.go @@ -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 +} diff --git a/monitor/monitor.go b/monitor/monitor.go new file mode 100644 index 0000000..cd78a00 --- /dev/null +++ b/monitor/monitor.go @@ -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)) + } +} diff --git a/monitor/monitor_test.go b/monitor/monitor_test.go new file mode 100644 index 0000000..d915c7d --- /dev/null +++ b/monitor/monitor_test.go @@ -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) + } +} diff --git a/monitor/net.go b/monitor/net.go new file mode 100644 index 0000000..e8f3d83 --- /dev/null +++ b/monitor/net.go @@ -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 +} diff --git a/monitor/process.go b/monitor/process.go new file mode 100644 index 0000000..d605716 --- /dev/null +++ b/monitor/process.go @@ -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 +}