Files
code-utils/host_metrics.go

128 lines
3.1 KiB
Go
Raw Normal View History

2026-08-15 17:18:00 +08:00
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
}
}