Add host temperature telemetry and history
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
export const temperatureKinds = ['cpu-package', 'cpu-die', 'cpu-core', 'nvme', 'chipset'] as const;
|
||||
export type TemperatureKind = (typeof temperatureKinds)[number];
|
||||
export interface HostTemperatureSensor {
|
||||
id: string;
|
||||
device: string;
|
||||
label: string;
|
||||
kind: TemperatureKind;
|
||||
celsius: number;
|
||||
}
|
||||
|
||||
/** Optional telemetry must never make an otherwise valid heartbeat fail. */
|
||||
export function validateTemperatures(value: unknown): HostTemperatureSensor[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const seen = new Set<string>();
|
||||
return value.slice(0, 128).flatMap((sensor) => {
|
||||
if (
|
||||
!sensor ||
|
||||
typeof sensor !== 'object' ||
|
||||
Array.isArray(sensor) ||
|
||||
typeof sensor.id !== 'string' ||
|
||||
!/^[a-zA-Z0-9_-]{1,100}$/.test(sensor.id) ||
|
||||
seen.has(sensor.id) ||
|
||||
!['device', 'label'].every(
|
||||
(key) =>
|
||||
typeof sensor[key] === 'string' &&
|
||||
sensor[key].length > 0 &&
|
||||
sensor[key].length <= 120
|
||||
) ||
|
||||
!temperatureKinds.includes(sensor.kind) ||
|
||||
typeof sensor.celsius !== 'number' ||
|
||||
!Number.isFinite(sensor.celsius) ||
|
||||
sensor.celsius < -100 ||
|
||||
sensor.celsius > 200
|
||||
)
|
||||
return [];
|
||||
seen.add(sensor.id);
|
||||
return [
|
||||
{
|
||||
id: sensor.id,
|
||||
device: sensor.device,
|
||||
label: sensor.label,
|
||||
kind: sensor.kind,
|
||||
celsius: sensor.celsius
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
export function cpuTemperature(
|
||||
sensors: HostTemperatureSensor[] = []
|
||||
): HostTemperatureSensor | null {
|
||||
// Prefer physical package/die readings. Cores are a fallback, never mixed
|
||||
// into an average that might hide a hot package on a multi-socket host.
|
||||
for (const kind of ['cpu-package', 'cpu-die', 'cpu-core'] as const) {
|
||||
const candidates = sensors.filter((sensor) => sensor.kind === kind);
|
||||
if (candidates.length)
|
||||
return candidates.reduce((hot, sensor) =>
|
||||
sensor.celsius > hot.celsius ? sensor : hot
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function temperatureLabel(sensor: HostTemperatureSensor): string {
|
||||
if (sensor.kind === 'cpu-package')
|
||||
return `CPU package ${sensor.label.replace(/^Package id /, '')}`;
|
||||
if (sensor.kind === 'cpu-core') return `CPU ${sensor.label.toLowerCase()}`;
|
||||
if (sensor.kind === 'cpu-die') return `CPU die · ${sensor.label}`;
|
||||
if (sensor.kind === 'chipset') return 'Chipset';
|
||||
return `NVMe · ${sensor.label}`;
|
||||
}
|
||||
|
||||
export function formatTemperature(celsius: number | null | undefined): string {
|
||||
return typeof celsius === 'number' && Number.isFinite(celsius)
|
||||
? `${Number(celsius.toFixed(1))} °C`
|
||||
: 'Unavailable';
|
||||
}
|
||||
|
||||
/** One CPU series per host, plus bounded device series for the host drill-down. */
|
||||
export function historyTemperatures(
|
||||
sensors: HostTemperatureSensor[] = []
|
||||
): HostTemperatureSensor[] {
|
||||
const cpu = cpuTemperature(sensors);
|
||||
const devices = sensors
|
||||
.filter(
|
||||
(sensor) =>
|
||||
sensor.kind === 'chipset' ||
|
||||
(sensor.kind === 'nvme' && sensor.label.toLowerCase() === 'composite')
|
||||
)
|
||||
.slice(0, 16);
|
||||
return [...(cpu ? [{ ...cpu, id: 'cpu', label: 'CPU temperature' }] : []), ...devices];
|
||||
}
|
||||
Reference in New Issue
Block a user