mirror of
https://github.com/tw93/Mole.git
synced 2026-02-05 04:29:45 +00:00
132 lines
2.9 KiB
Go
132 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
systemProfilerTimeout = 4 * time.Second
|
|
macGPUInfoTTL = 10 * time.Minute
|
|
)
|
|
|
|
func (c *Collector) collectGPU(now time.Time) ([]GPUStatus, error) {
|
|
if runtime.GOOS == "darwin" {
|
|
if len(c.cachedGPU) > 0 && !c.lastGPUAt.IsZero() && now.Sub(c.lastGPUAt) < macGPUInfoTTL {
|
|
return c.cachedGPU, nil
|
|
}
|
|
if gpus, err := readMacGPUInfo(); err == nil && len(gpus) > 0 {
|
|
c.cachedGPU = gpus
|
|
c.lastGPUAt = now
|
|
return gpus, nil
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 600*time.Millisecond)
|
|
defer cancel()
|
|
|
|
if !commandExists("nvidia-smi") {
|
|
return []GPUStatus{{
|
|
Name: "No GPU metrics available",
|
|
Note: "Install nvidia-smi or use platform-specific metrics",
|
|
}}, nil
|
|
}
|
|
|
|
out, err := runCmd(ctx, "nvidia-smi", "--query-gpu=utilization.gpu,memory.used,memory.total,name", "--format=csv,noheader,nounits")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
var gpus []GPUStatus
|
|
for _, line := range lines {
|
|
fields := strings.Split(line, ",")
|
|
if len(fields) < 4 {
|
|
continue
|
|
}
|
|
util, _ := strconv.ParseFloat(strings.TrimSpace(fields[0]), 64)
|
|
memUsed, _ := strconv.ParseFloat(strings.TrimSpace(fields[1]), 64)
|
|
memTotal, _ := strconv.ParseFloat(strings.TrimSpace(fields[2]), 64)
|
|
name := strings.TrimSpace(fields[3])
|
|
|
|
gpus = append(gpus, GPUStatus{
|
|
Name: name,
|
|
Usage: util,
|
|
MemoryUsed: memUsed,
|
|
MemoryTotal: memTotal,
|
|
})
|
|
}
|
|
|
|
if len(gpus) == 0 {
|
|
return []GPUStatus{{
|
|
Name: "GPU read failed",
|
|
Note: "Verify nvidia-smi availability",
|
|
}}, nil
|
|
}
|
|
|
|
return gpus, nil
|
|
}
|
|
|
|
func readMacGPUInfo() ([]GPUStatus, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), systemProfilerTimeout)
|
|
defer cancel()
|
|
|
|
if !commandExists("system_profiler") {
|
|
return nil, errors.New("system_profiler unavailable")
|
|
}
|
|
|
|
out, err := runCmd(ctx, "system_profiler", "-json", "SPDisplaysDataType")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var data struct {
|
|
Displays []struct {
|
|
Name string `json:"_name"`
|
|
VRAM string `json:"spdisplays_vram"`
|
|
Vendor string `json:"spdisplays_vendor"`
|
|
Metal string `json:"spdisplays_metal"`
|
|
} `json:"SPDisplaysDataType"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var gpus []GPUStatus
|
|
for _, d := range data.Displays {
|
|
if d.Name == "" {
|
|
continue
|
|
}
|
|
noteParts := []string{}
|
|
if d.VRAM != "" {
|
|
noteParts = append(noteParts, "VRAM "+d.VRAM)
|
|
}
|
|
if d.Metal != "" {
|
|
noteParts = append(noteParts, d.Metal)
|
|
}
|
|
if d.Vendor != "" {
|
|
noteParts = append(noteParts, d.Vendor)
|
|
}
|
|
note := strings.Join(noteParts, " · ")
|
|
gpus = append(gpus, GPUStatus{
|
|
Name: d.Name,
|
|
Usage: -1,
|
|
Note: note,
|
|
})
|
|
}
|
|
|
|
if len(gpus) == 0 {
|
|
return []GPUStatus{{
|
|
Name: "GPU info unavailable",
|
|
Note: "Unable to parse system_profiler output",
|
|
}}, nil
|
|
}
|
|
|
|
return gpus, nil
|
|
}
|