package main import ( "os/exec" "strconv" "strings" "sync" "time" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/mem" gnet "github.com/shirou/gopsutil/v4/net" "view/platform" ) // HostMetrics 本机总览快照(端口监控页轮询)。 type HostMetrics struct { At string `json:"at"` CPUPercent float64 `json:"cpu"` MemUsedMB float64 `json:"memUsedMB"` MemTotalMB float64 `json:"memTotalMB"` MemPercent float64 `json:"memPercent"` GPUPercent float64 `json:"gpu"` // -1 表示不可用 GPUMemUsedMB float64 `json:"gpuMemUsedMB"` GPUMemTotalMB float64 `json:"gpuMemTotalMB"` GPUName string `json:"gpuName"` NetRecvKBs float64 `json:"netRecvKBs"` NetSentKBs float64 `json:"netSentKBs"` } var ( hostNetMu sync.Mutex hostNetAt time.Time hostNetRecv uint64 hostNetSent uint64 hostNetInit bool ) // GetHostMetrics 采集 CPU / 内存 / 网卡吞吐;GPU 在有 nvidia-smi 时附带利用率。 func (a *App) GetHostMetrics() (HostMetrics, error) { out := HostMetrics{At: time.Now().Format(time.RFC3339), GPUPercent: -1} if percents, err := cpu.Percent(0, false); err == nil && len(percents) > 0 { out.CPUPercent = percents[0] if out.CPUPercent < 0 { out.CPUPercent = 0 } } if vm, err := mem.VirtualMemory(); err == nil && vm != nil { out.MemUsedMB = float64(vm.Used) / 1024 / 1024 out.MemTotalMB = float64(vm.Total) / 1024 / 1024 out.MemPercent = vm.UsedPercent } recvKB, sentKB := sampleHostNet() out.NetRecvKBs = recvKB out.NetSentKBs = sentKB fillGPU(&out) return out, nil } func sampleHostNet() (recvKBs, sentKBs float64) { counters, err := gnet.IOCounters(false) if err != nil || len(counters) == 0 { return 0, 0 } c := counters[0] now := time.Now() hostNetMu.Lock() defer hostNetMu.Unlock() if !hostNetInit { hostNetInit = true hostNetAt = now hostNetRecv = c.BytesRecv hostNetSent = c.BytesSent return 0, 0 } wall := now.Sub(hostNetAt).Seconds() if wall <= 0 { return 0, 0 } recvKBs = float64(c.BytesRecv-hostNetRecv) / 1024 / wall sentKBs = float64(c.BytesSent-hostNetSent) / 1024 / wall if recvKBs < 0 { recvKBs = 0 } if sentKBs < 0 { sentKBs = 0 } hostNetAt = now hostNetRecv = c.BytesRecv hostNetSent = c.BytesSent return recvKBs, sentKBs } // fillGPU 优先 nvidia-smi;失败则保持 gpu=-1。无窗口执行,避免端口监控轮询闪 cmd。 func fillGPU(out *HostMetrics) { cmd := exec.Command("nvidia-smi", "--query-gpu=name,utilization.gpu,memory.used,memory.total", "--format=csv,noheader,nounits") platform.ConfigureHidden(cmd) raw, err := cmd.Output() if err != nil { return } line := strings.TrimSpace(strings.Split(string(raw), "\n")[0]) if line == "" { return } parts := strings.Split(line, ",") if len(parts) < 4 { return } out.GPUName = strings.TrimSpace(parts[0]) if v, e := strconv.ParseFloat(strings.TrimSpace(parts[1]), 64); e == nil { out.GPUPercent = v } if v, e := strconv.ParseFloat(strings.TrimSpace(parts[2]), 64); e == nil { out.GPUMemUsedMB = v } if v, e := strconv.ParseFloat(strings.TrimSpace(parts[3]), 64); e == nil { out.GPUMemTotalMB = v } }