Add AMD GPU temperature monitoring support
Related to #600 - Add GPU field to Temperature model with edge, junction, and mem sensors - Add amdgpu chip recognition to temperature parser - Implement parseGPUTemps() to extract AMD GPU temperature data - Update frontend TypeScript types to include GPU temperatures - Display GPU temps in node table tooltip alongside CPU temps - Set hasGPU flag when GPU data is available This enables temperature monitoring for AMD GPUs (amdgpu sensors) that was previously being collected via SSH but silently discarded during parsing.
This commit is contained in:
parent
5b89b2371a
commit
d62259ffa7
4 changed files with 94 additions and 4 deletions
|
|
@ -628,7 +628,7 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
|||
online &&
|
||||
isPVE &&
|
||||
cpuTemperatureValue !== null &&
|
||||
(node!.temperature?.hasCPU ?? node!.temperature?.available) &&
|
||||
(node!.temperature?.hasCPU ?? node!.temperature?.hasGPU ?? node!.temperature?.available) &&
|
||||
isTemperatureMonitoringEnabled(node!)
|
||||
}
|
||||
fallback={
|
||||
|
|
@ -653,7 +653,10 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
|||
typeof cpuMax === 'number' &&
|
||||
cpuMax > 0;
|
||||
|
||||
if (hasMinMax) {
|
||||
const gpus = temp?.gpu ?? [];
|
||||
const hasGPU = gpus.length > 0;
|
||||
|
||||
if (hasMinMax || hasGPU) {
|
||||
const min = Math.round(cpuMin);
|
||||
const max = Math.round(cpuMax);
|
||||
|
||||
|
|
@ -669,7 +672,22 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
|||
{value}°C
|
||||
</span>
|
||||
<span class="invisible group-hover:visible absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs whitespace-nowrap bg-gray-900 dark:bg-gray-700 text-white rounded shadow-lg z-50 pointer-events-none">
|
||||
<span class="text-gray-300">Range:</span> <span class={getTooltipColor(min)}>{min}</span>-<span class={getTooltipColor(max)}>{max}</span>°C
|
||||
{hasMinMax && (
|
||||
<div>
|
||||
<span class="text-gray-300">CPU:</span> <span class={getTooltipColor(min)}>{min}</span>-<span class={getTooltipColor(max)}>{max}</span>°C
|
||||
</div>
|
||||
)}
|
||||
{hasGPU && gpus.map((gpu) => {
|
||||
const gpuTemp = gpu.edge ?? gpu.junction ?? gpu.mem ?? 0;
|
||||
return (
|
||||
<div>
|
||||
<span class="text-gray-300">GPU:</span> <span class={getTooltipColor(gpuTemp)}>{Math.round(gpuTemp)}</span>°C
|
||||
{gpu.edge && ` E:${Math.round(gpu.edge)}`}
|
||||
{gpu.junction && ` J:${Math.round(gpu.junction)}`}
|
||||
{gpu.mem && ` M:${Math.round(gpu.mem)}`}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -752,9 +752,11 @@ export interface Temperature {
|
|||
minRecorded?: string; // When minimum temperature was recorded
|
||||
maxRecorded?: string; // When maximum temperature was recorded
|
||||
cores?: CoreTemp[]; // Individual core temperatures
|
||||
gpu?: GPUTemp[]; // GPU temperatures
|
||||
nvme?: NVMeTemp[]; // NVMe drive temperatures
|
||||
available: boolean; // Whether any temperature data is available
|
||||
hasCPU?: boolean; // Whether CPU temperature data is available
|
||||
hasGPU?: boolean; // Whether GPU temperature data is available
|
||||
hasNVMe?: boolean; // Whether NVMe temperature data is available
|
||||
lastUpdate: string; // When this data was collected
|
||||
}
|
||||
|
|
@ -764,6 +766,13 @@ export interface CoreTemp {
|
|||
temp: number;
|
||||
}
|
||||
|
||||
export interface GPUTemp {
|
||||
device: string; // GPU device identifier (e.g., "amdgpu-pci-0400")
|
||||
edge?: number; // Edge temperature
|
||||
junction?: number; // Junction/hotspot temperature
|
||||
mem?: number; // Memory temperature
|
||||
}
|
||||
|
||||
export interface NVMeTemp {
|
||||
device: string;
|
||||
temp: number;
|
||||
|
|
|
|||
|
|
@ -773,9 +773,11 @@ type Temperature struct {
|
|||
MinRecorded time.Time `json:"minRecorded,omitempty"` // When minimum temperature was recorded
|
||||
MaxRecorded time.Time `json:"maxRecorded,omitempty"` // When maximum temperature was recorded
|
||||
Cores []CoreTemp `json:"cores,omitempty"` // Individual core temperatures
|
||||
GPU []GPUTemp `json:"gpu,omitempty"` // GPU temperatures
|
||||
NVMe []NVMeTemp `json:"nvme,omitempty"` // NVMe drive temperatures
|
||||
Available bool `json:"available"` // Whether any temperature data is available
|
||||
HasCPU bool `json:"hasCPU"` // Whether CPU temperature data is available
|
||||
HasGPU bool `json:"hasGPU"` // Whether GPU temperature data is available
|
||||
HasNVMe bool `json:"hasNVMe"` // Whether NVMe temperature data is available
|
||||
LastUpdate time.Time `json:"lastUpdate"` // When this data was collected
|
||||
}
|
||||
|
|
@ -786,6 +788,14 @@ type CoreTemp struct {
|
|||
Temp float64 `json:"temp"`
|
||||
}
|
||||
|
||||
// GPUTemp represents a GPU temperature sensor
|
||||
type GPUTemp struct {
|
||||
Device string `json:"device"` // GPU device identifier (e.g., "amdgpu-pci-0400")
|
||||
Edge float64 `json:"edge,omitempty"` // Edge temperature
|
||||
Junction float64 `json:"junction,omitempty"` // Junction/hotspot temperature
|
||||
Mem float64 `json:"mem,omitempty"` // Memory temperature
|
||||
}
|
||||
|
||||
// NVMeTemp represents an NVMe drive temperature
|
||||
type NVMeTemp struct {
|
||||
Device string `json:"device"`
|
||||
|
|
|
|||
|
|
@ -364,6 +364,14 @@ func (tc *TemperatureCollector) parseSensorsJSON(jsonStr string) (*models.Temper
|
|||
if strings.Contains(chipName, "nvme") {
|
||||
tc.parseNVMeTemps(chipName, chipMap, temp)
|
||||
}
|
||||
|
||||
// Handle GPU temperature sensors
|
||||
if strings.Contains(chipLower, "amdgpu") {
|
||||
log.Debug().
|
||||
Str("chip", chipName).
|
||||
Msg("Detected AMD GPU temperature chip")
|
||||
tc.parseGPUTemps(chipName, chipMap, temp)
|
||||
}
|
||||
}
|
||||
|
||||
// If we got CPU temps, calculate max from cores if package not available
|
||||
|
|
@ -379,9 +387,10 @@ func (tc *TemperatureCollector) parseSensorsJSON(jsonStr string) (*models.Temper
|
|||
// This prevents false negatives when sensors report 0°C during resets or temporarily
|
||||
temp.HasCPU = foundCPUChip
|
||||
temp.HasNVMe = len(temp.NVMe) > 0
|
||||
temp.HasGPU = len(temp.GPU) > 0
|
||||
|
||||
// Available means any temperature data exists (backward compatibility)
|
||||
temp.Available = temp.HasCPU || temp.HasNVMe
|
||||
temp.Available = temp.HasCPU || temp.HasNVMe || temp.HasGPU
|
||||
|
||||
// Log summary of what was detected
|
||||
if !foundCPUChip {
|
||||
|
|
@ -397,10 +406,12 @@ func (tc *TemperatureCollector) parseSensorsJSON(jsonStr string) (*models.Temper
|
|||
log.Debug().
|
||||
Bool("hasCPU", temp.HasCPU).
|
||||
Bool("hasNVMe", temp.HasNVMe).
|
||||
Bool("hasGPU", temp.HasGPU).
|
||||
Float64("cpuPackage", temp.CPUPackage).
|
||||
Float64("cpuMax", temp.CPUMax).
|
||||
Int("coreCount", len(temp.Cores)).
|
||||
Int("nvmeCount", len(temp.NVMe)).
|
||||
Int("gpuCount", len(temp.GPU)).
|
||||
Msg("Temperature data parsed successfully")
|
||||
}
|
||||
|
||||
|
|
@ -551,6 +562,48 @@ func (tc *TemperatureCollector) parseNVMeTemps(chipName string, chipMap map[stri
|
|||
}
|
||||
}
|
||||
|
||||
// parseGPUTemps extracts GPU temperature data from a sensor chip
|
||||
func (tc *TemperatureCollector) parseGPUTemps(chipName string, chipMap map[string]interface{}, temp *models.Temperature) {
|
||||
gpuTemp := models.GPUTemp{
|
||||
Device: chipName,
|
||||
}
|
||||
|
||||
// AMD GPU sensors typically have: edge, junction (hotspot), mem
|
||||
for sensorName, sensorData := range chipMap {
|
||||
sensorMap, ok := sensorData.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
sensorLower := strings.ToLower(sensorName)
|
||||
tempVal := extractTempInput(sensorMap)
|
||||
|
||||
if math.IsNaN(tempVal) || tempVal <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Map sensor names to struct fields
|
||||
if strings.Contains(sensorLower, "edge") {
|
||||
gpuTemp.Edge = tempVal
|
||||
} else if strings.Contains(sensorLower, "junction") || strings.Contains(sensorLower, "hotspot") {
|
||||
gpuTemp.Junction = tempVal
|
||||
} else if strings.Contains(sensorLower, "mem") {
|
||||
gpuTemp.Mem = tempVal
|
||||
}
|
||||
}
|
||||
|
||||
// Only add GPU entry if we got at least one valid temperature
|
||||
if gpuTemp.Edge > 0 || gpuTemp.Junction > 0 || gpuTemp.Mem > 0 {
|
||||
temp.GPU = append(temp.GPU, gpuTemp)
|
||||
log.Debug().
|
||||
Str("device", chipName).
|
||||
Float64("edge", gpuTemp.Edge).
|
||||
Float64("junction", gpuTemp.Junction).
|
||||
Float64("mem", gpuTemp.Mem).
|
||||
Msg("Parsed GPU temperatures")
|
||||
}
|
||||
}
|
||||
|
||||
// extractTempInput extracts temperature value from sensor data
|
||||
func extractTempInput(sensorMap map[string]interface{}) float64 {
|
||||
// Look for temp*_input fields
|
||||
|
|
|
|||
Loading…
Reference in a new issue