Files
code-utils/host_metrics.go
2026-08-15 17:18:00 +08:00

128 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}