mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 10:46:16 +01:00
103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
package agent
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
|
|
"github.com/henrygd/beszel/internal/entities/system"
|
|
)
|
|
|
|
const (
|
|
intelGpuStatsCmd string = "intel_gpu_top"
|
|
intelGpuStatsInterval string = "3300" // in milliseconds
|
|
)
|
|
|
|
type intelGpuStats struct {
|
|
Power struct {
|
|
GPU float64 `json:"GPU"`
|
|
} `json:"power"`
|
|
Engines map[string]struct {
|
|
Busy float64 `json:"busy"`
|
|
} `json:"engines"`
|
|
}
|
|
|
|
// updateIntelFromStats updates aggregated GPU data from a single intelGpuStats sample
|
|
func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
|
|
gm.Lock()
|
|
defer gm.Unlock()
|
|
|
|
// only one gpu for now - cmd doesn't provide all by default
|
|
gpuData, ok := gm.GpuDataMap["0"]
|
|
if !ok {
|
|
gpuData = &system.GPUData{Name: "GPU", Engines: make(map[string]float64)}
|
|
gm.GpuDataMap["0"] = gpuData
|
|
}
|
|
|
|
if sample.Power.GPU > 0 {
|
|
gpuData.Power += sample.Power.GPU
|
|
}
|
|
|
|
if gpuData.Engines == nil {
|
|
gpuData.Engines = make(map[string]float64, len(sample.Engines))
|
|
}
|
|
for name, engine := range sample.Engines {
|
|
gpuData.Engines[name] += engine.Busy
|
|
}
|
|
|
|
gpuData.Count++
|
|
return true
|
|
}
|
|
|
|
// collectIntelStats executes intel_gpu_top in JSON mode and stream-decodes the array of samples
|
|
func (gm *GPUManager) collectIntelStats() error {
|
|
cmd := exec.Command(intelGpuStatsCmd, "-s", intelGpuStatsInterval, "-J")
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
dec := json.NewDecoder(stdout)
|
|
|
|
// Expect a JSON array stream: [ { ... }, { ... }, ... ]
|
|
tok, err := dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if delim, ok := tok.(json.Delim); !ok || delim != '[' {
|
|
return fmt.Errorf("unexpected JSON start token: %v", tok)
|
|
}
|
|
|
|
var sample intelGpuStats
|
|
for {
|
|
if dec.More() {
|
|
// Clear the engines map before decoding
|
|
if sample.Engines != nil {
|
|
for k := range sample.Engines {
|
|
delete(sample.Engines, k)
|
|
}
|
|
}
|
|
|
|
if err := dec.Decode(&sample); err != nil {
|
|
return fmt.Errorf("decode intel gpu: %w", err)
|
|
}
|
|
gm.updateIntelFromStats(&sample)
|
|
continue
|
|
}
|
|
// Attempt to read closing bracket (will only be present when process exits)
|
|
tok, err = dec.Token()
|
|
if err != nil {
|
|
// When the process is still running, decoder will block in More/Decode; any error here is terminal
|
|
return err
|
|
}
|
|
if delim, ok := tok.(json.Delim); ok && delim == ']' {
|
|
break
|
|
}
|
|
}
|
|
|
|
return cmd.Wait()
|
|
}
|