Files
AIRouter/internal/resource/gpu.go
T
freedakgmail 93a469061d
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / security-scan (push) Has been cancelled
初始提交:边缘AI算力机统一AI通讯层
2026-08-03 07:44:05 +08:00

198 lines
4.2 KiB
Go

package resource
import (
"context"
"fmt"
"os/exec"
"strconv"
"strings"
"sync"
"time"
)
// GPUMetrics represents GPU utilization data from nvidia-smi.
type GPUMetrics struct {
Index int
Name string
TemperatureC int
UtilizationGPU int // percentage 0-100
MemoryUsedMB int
MemoryTotalMB int
MemoryUtilPct float64
PowerDrawW float64
PowerLimitW float64
Timestamp time.Time
}
// GPUCollector collects GPU metrics via nvidia-smi.
type GPUCollector struct {
mu sync.RWMutex
metrics []GPUMetrics
enabled bool
}
// NewGPUCollector creates a new GPU collector.
func NewGPUCollector() *GPUCollector {
return &GPUCollector{enabled: true}
}
// Collect runs nvidia-smi and parses the output.
func (c *GPUCollector) Collect(ctx context.Context) ([]GPUMetrics, error) {
if !c.enabled {
return nil, nil
}
// Use nvidia-smi with CSV format for structured output
cmd := exec.CommandContext(ctx, "nvidia-smi",
"--query-gpu=index,name,temperature.gpu,utilization.gpu,memory.used,memory.total,memory.utilization,power.draw,power.limit",
"--format=csv,noheader,nounits",
)
output, err := cmd.Output()
if err != nil {
// If nvidia-smi is not available, disable collector
c.mu.Lock()
c.enabled = false
c.mu.Unlock()
return nil, fmt.Errorf("nvidia-smi not available: %w", err)
}
metrics := parseNvidiaSMI(string(output))
c.mu.Lock()
c.metrics = metrics
c.mu.Unlock()
return metrics, nil
}
func parseNvidiaSMI(output string) []GPUMetrics {
lines := strings.Split(strings.TrimSpace(output), "\n")
metrics := make([]GPUMetrics, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := strings.Split(line, ",")
if len(fields) < 9 {
continue
}
m := GPUMetrics{Timestamp: time.Now()}
m.Index = parseIntSafe(fields[0])
m.Name = strings.TrimSpace(fields[1])
m.TemperatureC = parseIntSafe(fields[2])
m.UtilizationGPU = parseIntSafe(fields[3])
m.MemoryUsedMB = parseIntSafe(fields[4])
m.MemoryTotalMB = parseIntSafe(fields[5])
m.MemoryUtilPct = parseFloatSafe(fields[6])
m.PowerDrawW = parseFloatSafe(fields[7])
m.PowerLimitW = parseFloatSafe(fields[8])
metrics = append(metrics, m)
}
return metrics
}
func parseIntSafe(s string) int {
s = strings.TrimSpace(s)
v, err := strconv.Atoi(s)
if err != nil {
return 0
}
return v
}
func parseFloatSafe(s string) float64 {
s = strings.TrimSpace(s)
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0
}
return v
}
// GetMetrics returns the last collected metrics (thread-safe).
func (c *GPUCollector) GetMetrics() []GPUMetrics {
c.mu.RLock()
defer c.mu.RUnlock()
return c.metrics
}
// IsEnabled returns whether GPU collection is enabled.
func (c *GPUCollector) IsEnabled() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.enabled
}
// StartPeriodicCollection starts a background goroutine that collects GPU metrics at regular intervals.
func (c *GPUCollector) StartPeriodicCollection(ctx context.Context, interval time.Duration) {
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
c.Collect(ctx)
}
}
}()
}
// TotalMemoryUsedMB returns total GPU memory used across all GPUs.
func (c *GPUCollector) TotalMemoryUsedMB() int {
c.mu.RLock()
defer c.mu.RUnlock()
total := 0
for _, m := range c.metrics {
total += m.MemoryUsedMB
}
return total
}
// TotalMemoryTotalMB returns total GPU memory capacity across all GPUs.
func (c *GPUCollector) TotalMemoryTotalMB() int {
c.mu.RLock()
defer c.mu.RUnlock()
total := 0
for _, m := range c.metrics {
total += m.MemoryTotalMB
}
return total
}
// AverageUtilization returns average GPU utilization percentage.
func (c *GPUCollector) AverageUtilization() float64 {
c.mu.RLock()
defer c.mu.RUnlock()
if len(c.metrics) == 0 {
return 0
}
total := 0
for _, m := range c.metrics {
total += m.UtilizationGPU
}
return float64(total) / float64(len(c.metrics))
}
// MemoryUtilizationRatio returns memory used / memory total (0.0-1.0).
func (c *GPUCollector) MemoryUtilizationRatio() float64 {
total := c.TotalMemoryTotalMB()
if total == 0 {
return 0
}
return float64(c.TotalMemoryUsedMB()) / float64(total)
}