feat: add temperatures to dashboard

- Refactor temperature related code and move to standalone function
This commit is contained in:
henrygd
2025-02-07 21:27:15 -05:00
parent 31d52d5e15
commit e6054058b9
4 changed files with 82 additions and 33 deletions

View File

@@ -178,38 +178,9 @@ func (a *Agent) getSystemStats() system.Stats {
}
// temperatures (skip if sensors whitelist is set to empty string)
if a.sensorsWhitelist != nil && len(a.sensorsWhitelist) == 0 {
slog.Debug("Skipping temperature collection")
} else {
temps, err := sensors.TemperaturesWithContext(a.sensorsContext)
err = a.updateTemperatures(&systemStats)
if err != nil {
slog.Debug("Sensor error", "err", err)
}
slog.Debug("Temperature", "sensors", temps)
if len(temps) > 0 {
systemStats.Temperatures = make(map[string]float64, len(temps))
for i, sensor := range temps {
// skip if temperature is 0
if sensor.Temperature <= 0 || sensor.Temperature >= 200 {
continue
}
if _, ok := systemStats.Temperatures[sensor.SensorKey]; ok {
// if key already exists, append int to key
systemStats.Temperatures[sensor.SensorKey+"_"+strconv.Itoa(i)] = twoDecimals(sensor.Temperature)
} else {
systemStats.Temperatures[sensor.SensorKey] = twoDecimals(sensor.Temperature)
}
}
// remove sensors from systemStats if whitelist exists and sensor is not in whitelist
// (do this here instead of in initial loop so we have correct keys if int was appended)
if a.sensorsWhitelist != nil {
for key := range systemStats.Temperatures {
if _, nameInWhitelist := a.sensorsWhitelist[key]; !nameInWhitelist {
delete(systemStats.Temperatures, key)
}
}
}
}
slog.Error("Error getting temperatures", "err", err)
}
// GPU data
@@ -239,6 +210,54 @@ func (a *Agent) getSystemStats() system.Stats {
return systemStats
}
func (a *Agent) updateTemperatures(systemStats *system.Stats) error {
// skip if sensors whitelist is set to empty string
if a.sensorsWhitelist != nil && len(a.sensorsWhitelist) == 0 {
slog.Debug("Skipping temperature collection")
return nil
}
// reset high temp
a.systemInfo.HighTemp = 0
// get sensor data
temps, err := sensors.TemperaturesWithContext(a.sensorsContext)
if err != nil {
return err
}
slog.Debug("Temperature", "sensors", temps)
// return if no sensors
if len(temps) == 0 {
return nil
}
systemStats.Temperatures = make(map[string]float64, len(temps))
for i, sensor := range temps {
// skip if temperature is unreasonable
if sensor.Temperature <= 0 || sensor.Temperature >= 200 {
continue
}
sensorName := sensor.SensorKey
if _, ok := systemStats.Temperatures[sensorName]; ok {
// if key already exists, append int to key
sensorName = sensorName + "_" + strconv.Itoa(i)
}
// skip if not in whitelist
if a.sensorsWhitelist != nil {
if _, nameInWhitelist := a.sensorsWhitelist[sensorName]; !nameInWhitelist {
continue
}
}
// assign high temperature if sensor temp is higher than a.systemInfo.HighTemp
if sensor.Temperature > a.systemInfo.HighTemp {
a.systemInfo.HighTemp = sensor.Temperature
}
systemStats.Temperatures[sensorName] = twoDecimals(sensor.Temperature)
}
return nil
}
// Returns the size of the ZFS ARC memory cache in bytes
func getARCSize() (uint64, error) {
file, err := os.Open("/proc/spl/kstat/zfs/arcstats")

View File

@@ -75,6 +75,8 @@ type Info struct {
Bandwidth float64 `json:"b"`
AgentVersion string `json:"v"`
Podman bool `json:"p,omitempty"`
Gpu float64 `json:"g,omitempty"`
HighTemp float64 `json:"ht,omitempty"`
}
// Final data structure to return to the hub

View File

@@ -66,7 +66,7 @@ import { useStore } from "@nanostores/react"
import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils"
import AlertsButton from "../alerts/alert-button"
import { $router, Link, navigate } from "../router"
import { EthernetIcon } from "../ui/icons"
import { EthernetIcon, ThermometerIcon } from "../ui/icons"
import { Trans, t } from "@lingui/macro"
import { useLingui } from "@lingui/react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
@@ -180,6 +180,30 @@ export default function SystemsTable() {
icon: HardDriveIcon,
header: ({ column }) => sortableHeader(column),
},
{
accessorKey: "info.ht",
id: t`Temp`,
invertSorting: true,
sortUndefined: 0,
size: 50,
icon: ThermometerIcon,
header: ({ column }) => sortableHeader(column),
cell(info) {
const val = info.getValue() as number
if (!val) {
return null
}
return (
<span
className={cn("tabular-nums whitespace-nowrap", {
"ps-1": viewMode === "table",
})}
>
{decimalString(val)} °C
</span>
)
},
},
{
accessorFn: (originalRow) => originalRow.info.b || 0,
id: t`Net`,

View File

@@ -43,6 +43,10 @@ export interface SystemInfo {
v: string
/** system is using podman */
p?: boolean
/** highest gpu utilization */
g?: number
/** highest temperature */
ht?: number
}
export interface SystemStats {