mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-04 03:51:49 +02:00
intel_gpu_top testing
This commit is contained in:
44
agent/gpu.go
44
agent/gpu.go
@@ -27,13 +27,10 @@ const (
|
|||||||
nvidiaSmiInterval string = "4" // in seconds
|
nvidiaSmiInterval string = "4" // in seconds
|
||||||
tegraStatsInterval string = "3700" // in milliseconds
|
tegraStatsInterval string = "3700" // in milliseconds
|
||||||
rocmSmiInterval time.Duration = 4300 * time.Millisecond
|
rocmSmiInterval time.Duration = 4300 * time.Millisecond
|
||||||
|
|
||||||
// Command retry and timeout constants
|
// Command retry and timeout constants
|
||||||
retryWaitTime time.Duration = 5 * time.Second
|
retryWaitTime time.Duration = 5 * time.Second
|
||||||
maxFailureRetries int = 5
|
maxFailureRetries int = 5
|
||||||
|
|
||||||
cmdBufferSize uint16 = 10 * 1024
|
|
||||||
|
|
||||||
// Unit Conversions
|
// Unit Conversions
|
||||||
mebibytesInAMegabyte float64 = 1.024 // nvidia-smi reports memory in MiB
|
mebibytesInAMegabyte float64 = 1.024 // nvidia-smi reports memory in MiB
|
||||||
milliwattsInAWatt float64 = 1000.0 // tegrastats reports power in mW
|
milliwattsInAWatt float64 = 1000.0 // tegrastats reports power in mW
|
||||||
@@ -45,6 +42,7 @@ type GPUManager struct {
|
|||||||
nvidiaSmi bool
|
nvidiaSmi bool
|
||||||
rocmSmi bool
|
rocmSmi bool
|
||||||
tegrastats bool
|
tegrastats bool
|
||||||
|
intelGpuStats bool
|
||||||
GpuDataMap map[string]*system.GPUData
|
GpuDataMap map[string]*system.GPUData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +64,7 @@ type gpuCollector struct {
|
|||||||
cmdArgs []string
|
cmdArgs []string
|
||||||
parse func([]byte) bool // returns true if valid data was found
|
parse func([]byte) bool // returns true if valid data was found
|
||||||
buf []byte
|
buf []byte
|
||||||
|
bufSize uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
var errNoValidData = fmt.Errorf("no valid GPU data found") // Error for missing data
|
var errNoValidData = fmt.Errorf("no valid GPU data found") // Error for missing data
|
||||||
@@ -99,7 +98,7 @@ func (c *gpuCollector) collect() error {
|
|||||||
|
|
||||||
scanner := bufio.NewScanner(stdout)
|
scanner := bufio.NewScanner(stdout)
|
||||||
if c.buf == nil {
|
if c.buf == nil {
|
||||||
c.buf = make([]byte, 0, cmdBufferSize)
|
c.buf = make([]byte, 0, c.bufSize)
|
||||||
}
|
}
|
||||||
scanner.Buffer(c.buf, bufio.MaxScanTokenSize)
|
scanner.Buffer(c.buf, bufio.MaxScanTokenSize)
|
||||||
|
|
||||||
@@ -244,20 +243,24 @@ func (gm *GPUManager) GetCurrentData() map[string]system.GPUData {
|
|||||||
// copy / reset the data
|
// copy / reset the data
|
||||||
gpuData := make(map[string]system.GPUData, len(gm.GpuDataMap))
|
gpuData := make(map[string]system.GPUData, len(gm.GpuDataMap))
|
||||||
for id, gpu := range gm.GpuDataMap {
|
for id, gpu := range gm.GpuDataMap {
|
||||||
gpuAvg := *gpu
|
// avoid division by zero
|
||||||
|
count := max(gpu.Count, 1)
|
||||||
|
|
||||||
|
// average the data
|
||||||
|
gpuAvg := *gpu
|
||||||
gpuAvg.Temperature = twoDecimals(gpu.Temperature)
|
gpuAvg.Temperature = twoDecimals(gpu.Temperature)
|
||||||
gpuAvg.MemoryUsed = twoDecimals(gpu.MemoryUsed)
|
gpuAvg.MemoryUsed = twoDecimals(gpu.MemoryUsed)
|
||||||
gpuAvg.MemoryTotal = twoDecimals(gpu.MemoryTotal)
|
gpuAvg.MemoryTotal = twoDecimals(gpu.MemoryTotal)
|
||||||
|
gpuAvg.Usage = twoDecimals(gpu.Usage / count)
|
||||||
// avoid division by zero
|
gpuAvg.Power = twoDecimals(gpu.Power / count)
|
||||||
if gpu.Count > 0 {
|
gpuAvg.Engines = make(map[string]float64, len(gpu.Engines))
|
||||||
gpuAvg.Usage = twoDecimals(gpu.Usage / gpu.Count)
|
for name, engine := range gpu.Engines {
|
||||||
gpuAvg.Power = twoDecimals(gpu.Power / gpu.Count)
|
gpuAvg.Engines[name] = twoDecimals(engine / count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset accumulators in the original
|
// reset accumulators in the original gpu data for next collection
|
||||||
gpu.Usage, gpu.Power, gpu.Count = 0, 0, 0
|
gpu.Usage, gpu.Power, gpu.Count = gpuAvg.Usage, gpuAvg.Power, 1
|
||||||
|
gpu.Engines = gpuAvg.Engines
|
||||||
|
|
||||||
// append id to the name if there are multiple GPUs with the same name
|
// append id to the name if there are multiple GPUs with the same name
|
||||||
if nameCounts[gpu.Name] > 1 {
|
if nameCounts[gpu.Name] > 1 {
|
||||||
@@ -284,18 +287,28 @@ func (gm *GPUManager) detectGPUs() error {
|
|||||||
gm.tegrastats = true
|
gm.tegrastats = true
|
||||||
gm.nvidiaSmi = false
|
gm.nvidiaSmi = false
|
||||||
}
|
}
|
||||||
if gm.nvidiaSmi || gm.rocmSmi || gm.tegrastats {
|
if _, err := exec.LookPath(intelGpuStatsCmd); err == nil {
|
||||||
|
slog.Info("Intel GPU stats found")
|
||||||
|
gm.intelGpuStats = true
|
||||||
|
}
|
||||||
|
if gm.nvidiaSmi || gm.rocmSmi || gm.tegrastats || gm.intelGpuStats {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("no GPU found - install nvidia-smi, rocm-smi, or tegrastats")
|
return fmt.Errorf("no GPU found - install nvidia-smi, rocm-smi, tegrastats, or intel_gpu_top")
|
||||||
}
|
}
|
||||||
|
|
||||||
// startCollector starts the appropriate GPU data collector based on the command
|
// startCollector starts the appropriate GPU data collector based on the command
|
||||||
func (gm *GPUManager) startCollector(command string) {
|
func (gm *GPUManager) startCollector(command string) {
|
||||||
collector := gpuCollector{
|
collector := gpuCollector{
|
||||||
name: command,
|
name: command,
|
||||||
|
bufSize: 10 * 1024,
|
||||||
}
|
}
|
||||||
switch command {
|
switch command {
|
||||||
|
case intelGpuStatsCmd:
|
||||||
|
slog.Info("Starting Intel GPU stats collector")
|
||||||
|
collector.cmdArgs = []string{"-s", intelGpuStatsInterval, "-J"}
|
||||||
|
collector.parse = gm.parseIntelData
|
||||||
|
go collector.start()
|
||||||
case nvidiaSmiCmd:
|
case nvidiaSmiCmd:
|
||||||
collector.cmdArgs = []string{
|
collector.cmdArgs = []string{
|
||||||
"-l", nvidiaSmiInterval,
|
"-l", nvidiaSmiInterval,
|
||||||
@@ -344,6 +357,9 @@ func NewGPUManager() (*GPUManager, error) {
|
|||||||
if gm.tegrastats {
|
if gm.tegrastats {
|
||||||
gm.startCollector(tegraStatsCmd)
|
gm.startCollector(tegraStatsCmd)
|
||||||
}
|
}
|
||||||
|
if gm.intelGpuStats {
|
||||||
|
gm.startCollector(intelGpuStatsCmd)
|
||||||
|
}
|
||||||
|
|
||||||
return &gm, nil
|
return &gm, nil
|
||||||
}
|
}
|
||||||
|
|||||||
53
agent/gpu_intel.go
Normal file
53
agent/gpu_intel.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
intelGpuStatsCmd string = "intel_gpu_top"
|
||||||
|
intelGpuStatsInterval string = "3800" // in milliseconds
|
||||||
|
)
|
||||||
|
|
||||||
|
type intelGpuStats struct {
|
||||||
|
Power struct {
|
||||||
|
GPU float64 `json:"gpu"`
|
||||||
|
} `json:"power"`
|
||||||
|
Engines map[string]struct {
|
||||||
|
Busy float64 `json:"busy"`
|
||||||
|
} `json:"engines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gm *GPUManager) parseIntelData(output []byte) bool {
|
||||||
|
slog.Info("Parsing Intel GPU stats")
|
||||||
|
var intelGpuStats intelGpuStats
|
||||||
|
if err := json.Unmarshal(output, &intelGpuStats); err != nil {
|
||||||
|
slog.Error("Error parsing Intel GPU stats", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
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, len(intelGpuStats.Engines))}
|
||||||
|
gm.GpuDataMap["0"] = gpuData
|
||||||
|
}
|
||||||
|
|
||||||
|
if intelGpuStats.Power.GPU > 0 {
|
||||||
|
gpuData.Power += intelGpuStats.Power.GPU
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, engine := range intelGpuStats.Engines {
|
||||||
|
gpuData.Engines[name] += engine.Busy
|
||||||
|
}
|
||||||
|
|
||||||
|
gpuData.Count++
|
||||||
|
|
||||||
|
slog.Info("GPU Data", "gpuData", gpuData)
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ type GPUData struct {
|
|||||||
Usage float64 `json:"u" cbor:"3,keyasint"`
|
Usage float64 `json:"u" cbor:"3,keyasint"`
|
||||||
Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"`
|
Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"`
|
||||||
Count float64 `json:"-"`
|
Count float64 `json:"-"`
|
||||||
|
Engines map[string]float64 `json:"e,omitempty" cbor:"5,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FsStats struct {
|
type FsStats struct {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export function useNetworkInterfaces(interfaces: SystemStats["ni"]) {
|
|||||||
data: (index = 3) => {
|
data: (index = 3) => {
|
||||||
return sortedKeys.map((key) => ({
|
return sortedKeys.map((key) => ({
|
||||||
label: key,
|
label: key,
|
||||||
dataKey: (stats: SystemStatsRecord) => stats.stats?.ni?.[key]?.[index],
|
dataKey: ({ stats }: SystemStatsRecord) => stats?.ni?.[key]?.[index],
|
||||||
color: `hsl(${220 + (((sortedKeys.indexOf(key) * 360) / sortedKeys.length) % 360)}, 70%, 50%)`,
|
color: `hsl(${220 + (((sortedKeys.indexOf(key) * 360) / sortedKeys.length) % 360)}, 70%, 50%)`,
|
||||||
|
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
@@ -123,3 +123,15 @@ export function useNetworkInterfaces(interfaces: SystemStats["ni"]) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generates chart configurations for GPU engines */
|
||||||
|
export function useGpuEngines(systemStats?: SystemStatsRecord) {
|
||||||
|
const keys = Object.keys(systemStats?.stats.g?.[0]?.e ?? {})
|
||||||
|
const sortedKeys = keys.sort()
|
||||||
|
return sortedKeys.map((engine) => ({
|
||||||
|
label: engine,
|
||||||
|
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[0]?.e?.[engine] ?? 0,
|
||||||
|
color: `hsl(${220 + ((sortedKeys.indexOf(engine) * 360) / sortedKeys.length) % 360}, 65%, 52%)`,
|
||||||
|
opacity: 0.35,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import AreaChartDefault from "@/components/charts/area-chart"
|
|||||||
import ContainerChart from "@/components/charts/container-chart"
|
import ContainerChart from "@/components/charts/container-chart"
|
||||||
import DiskChart from "@/components/charts/disk-chart"
|
import DiskChart from "@/components/charts/disk-chart"
|
||||||
import GpuPowerChart from "@/components/charts/gpu-power-chart"
|
import GpuPowerChart from "@/components/charts/gpu-power-chart"
|
||||||
import { useContainerChartConfigs } from "@/components/charts/hooks"
|
import { useContainerChartConfigs, useGpuEngines } from "@/components/charts/hooks"
|
||||||
import LoadAverageChart from "@/components/charts/load-average-chart"
|
import LoadAverageChart from "@/components/charts/load-average-chart"
|
||||||
import MemChart from "@/components/charts/mem-chart"
|
import MemChart from "@/components/charts/mem-chart"
|
||||||
import SwapChart from "@/components/charts/swap-chart"
|
import SwapChart from "@/components/charts/swap-chart"
|
||||||
@@ -61,6 +61,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
|||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
|
||||||
import NetworkSheet from "./system/network-sheet"
|
import NetworkSheet from "./system/network-sheet"
|
||||||
|
import LineChartDefault from "../charts/line-chart"
|
||||||
|
|
||||||
type ChartTimeData = {
|
type ChartTimeData = {
|
||||||
time: number
|
time: number
|
||||||
@@ -398,6 +399,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {})
|
const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {})
|
||||||
const hasGpuData = lastGpuVals.length > 0
|
const hasGpuData = lastGpuVals.length > 0
|
||||||
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
|
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
|
||||||
|
const hasGpuEnginesData = lastGpuVals.some((gpu) => gpu.e !== undefined)
|
||||||
|
|
||||||
let translatedStatus: string = system.status
|
let translatedStatus: string = system.status
|
||||||
if (system.status === SystemStatus.Up) {
|
if (system.status === SystemStatus.Up) {
|
||||||
@@ -770,6 +772,17 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
<GpuPowerChart chartData={chartData} />
|
<GpuPowerChart chartData={chartData} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasGpuEnginesData && (
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={t`GPU Engines`}
|
||||||
|
description={t`Average utilization of GPU engines`}
|
||||||
|
>
|
||||||
|
<GpuEnginesChart chartData={chartData} />
|
||||||
|
</ChartCard>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* GPU charts */}
|
{/* GPU charts */}
|
||||||
@@ -897,6 +910,13 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
|
||||||
|
const engineData = useGpuEngines(chartData.systemStats.at(-1))
|
||||||
|
return (
|
||||||
|
<LineChartDefault legend={true} chartData={chartData} dataPoints={engineData} tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} contentFormatter={({ value }) => `${decimalString(value)}%`} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
||||||
const containerFilter = useStore(store)
|
const containerFilter = useStore(store)
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
|
|||||||
2
internal/site/src/types.d.ts
vendored
2
internal/site/src/types.d.ts
vendored
@@ -158,6 +158,8 @@ export interface GPUData {
|
|||||||
u: number
|
u: number
|
||||||
/** power (w) */
|
/** power (w) */
|
||||||
p?: number
|
p?: number
|
||||||
|
/** engines */
|
||||||
|
e?: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtraFsStats {
|
export interface ExtraFsStats {
|
||||||
|
|||||||
Reference in New Issue
Block a user