93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
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];
|
|
}
|