Add host temperature telemetry and history
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
import {
|
||||
cpuTemperature,
|
||||
historyTemperatures,
|
||||
validateTemperatures,
|
||||
type HostTemperatureSensor
|
||||
} from '../../src/lib/host-temperatures.js';
|
||||
|
||||
const sensor = (changes: Partial<HostTemperatureSensor> = {}): HostTemperatureSensor => ({
|
||||
id: 'package',
|
||||
device: 'coretemp',
|
||||
label: 'Package id 0',
|
||||
kind: 'cpu-package',
|
||||
celsius: 65,
|
||||
...changes
|
||||
});
|
||||
|
||||
describe('temperature readings', () => {
|
||||
it('keeps valid sensors while dropping malformed, duplicate and out-of-range entries', () => {
|
||||
const good = sensor();
|
||||
const readings = [
|
||||
good,
|
||||
good,
|
||||
null,
|
||||
'bad',
|
||||
...[NaN, Infinity, -Infinity, null, '65', true, -101, 201].map((celsius) => ({
|
||||
...good,
|
||||
id: 'invalid',
|
||||
celsius
|
||||
})),
|
||||
{ ...good, id: 'x'.repeat(101) },
|
||||
{ ...good, label: '' },
|
||||
{ ...good, kind: 'cpu-control' },
|
||||
sensor({ id: 'zero', celsius: 0 })
|
||||
];
|
||||
expect(validateTemperatures(readings)).toEqual([good, sensor({ id: 'zero', celsius: 0 })]);
|
||||
expect(validateTemperatures({})).toEqual([]);
|
||||
expect(validateTemperatures(undefined)).toEqual([]);
|
||||
});
|
||||
it('chooses the hottest package and uses die/core readings only as fallbacks', () => {
|
||||
const packages = [sensor(), sensor({ id: 'socket2', label: 'Package id 1', celsius: 72 })];
|
||||
const core = sensor({ id: 'core', label: 'Core 0', kind: 'cpu-core', celsius: 80 });
|
||||
const die = sensor({ id: 'die', label: 'Tdie', kind: 'cpu-die', celsius: 64 });
|
||||
expect(cpuTemperature([...packages, core])?.celsius).toBe(72);
|
||||
expect(cpuTemperature([die, core])?.celsius).toBe(64);
|
||||
expect(cpuTemperature([core])?.celsius).toBe(80);
|
||||
expect(cpuTemperature()).toBeNull();
|
||||
const drive = sensor({
|
||||
id: 'drive',
|
||||
kind: 'nvme',
|
||||
device: 'nvme',
|
||||
label: 'Composite',
|
||||
celsius: 50.9
|
||||
});
|
||||
expect(
|
||||
historyTemperatures([
|
||||
...packages,
|
||||
core,
|
||||
drive,
|
||||
{ ...drive, id: 'internal', label: 'Sensor 1' }
|
||||
])
|
||||
).toEqual([{ ...packages[1], id: 'cpu', label: 'CPU temperature' }, drive]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('temperature persistence', () => {
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
fixture = await newFixture([]);
|
||||
});
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await fixture.cleanup();
|
||||
});
|
||||
|
||||
it('accepts older agents and faulty temperature payloads without losing host metrics', async () => {
|
||||
const { createHost, heartbeat, validateInventory } =
|
||||
await import('../../src/lib/server/hosts/index.js');
|
||||
const host = createHost({ name: 'ec-2' });
|
||||
expect(validateInventory({ cpuUsagePercent: 25 }).temperatures).toBeUndefined();
|
||||
expect(
|
||||
heartbeat(host.id, {
|
||||
cpuUsagePercent: 25,
|
||||
temperatures: [sensor(), { celsius: 'bad' }]
|
||||
}).inventory
|
||||
).toMatchObject({ cpuUsagePercent: 25, temperatures: [sensor()] });
|
||||
expect(
|
||||
heartbeat(host.id, { cpuUsagePercent: 30, temperatures: [] }).inventory.temperatures
|
||||
).toEqual([]);
|
||||
expect(
|
||||
heartbeat(host.id, { cpuUsagePercent: 35, temperatures: 'bad' }).inventory.temperatures
|
||||
).toEqual([]);
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
expect(
|
||||
db()
|
||||
.prepare('SELECT celsius FROM ec_host_temperature_samples WHERE host_id=?')
|
||||
.all(host.id)
|
||||
).toEqual([{ celsius: 65 }]);
|
||||
});
|
||||
|
||||
it('retains one peak per minute and applies host history retention, including offline hosts', async () => {
|
||||
const { createHost } = await import('../../src/lib/server/hosts/index.js');
|
||||
const { recordTemperatures } = await import('../../src/lib/server/hosts/temperatures.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { fleetSettings, saveFleetSettings } =
|
||||
await import('../../src/lib/server/settings.js');
|
||||
const { pruneSampleHistory } = await import('../../src/lib/server/retention.js');
|
||||
const host = createHost({ name: 'ec-2' });
|
||||
const minute = Math.floor((Date.now() - 3 * 86400_000) / 60_000) * 60_000;
|
||||
for (const [i, celsius] of [65, 78, 67].entries())
|
||||
recordTemperatures(
|
||||
host.id,
|
||||
{ temperatures: [sensor({ celsius })] },
|
||||
new Date(minute + i * 15_000).toISOString()
|
||||
);
|
||||
expect(db().prepare('SELECT celsius FROM ec_host_temperature_samples').all()).toEqual([
|
||||
{ celsius: 78 }
|
||||
]);
|
||||
saveFleetSettings({ ...fleetSettings(), hostResourceKeepDays: 0 });
|
||||
pruneSampleHistory();
|
||||
expect(db().prepare('SELECT COUNT(*) AS n FROM ec_host_temperature_samples').get()).toEqual(
|
||||
{ n: 1 }
|
||||
);
|
||||
recordTemperatures(host.id, { temperatures: [sensor()] }, new Date().toISOString());
|
||||
saveFleetSettings({ ...fleetSettings(), hostResourceKeepDays: 1 });
|
||||
pruneSampleHistory();
|
||||
expect(db().prepare('SELECT celsius FROM ec_host_temperature_samples').all()).toEqual([
|
||||
{ celsius: 65 }
|
||||
]);
|
||||
});
|
||||
|
||||
it('upgrades an existing database without rewriting its resource samples', async () => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const { createHost } = await import('../../src/lib/server/hosts/index.js');
|
||||
const host = createHost({ name: 'old' });
|
||||
db()
|
||||
.prepare(
|
||||
'INSERT INTO ec_host_resource_samples(host_id,sampled_at,cpu_usage_percent) VALUES(?,?,?)'
|
||||
)
|
||||
.run(host.id, '2026-10-13T12:00:00Z', 25);
|
||||
db().exec('DROP TABLE ec_host_temperature_samples; PRAGMA user_version=22;');
|
||||
closeDb();
|
||||
expect(db().prepare('SELECT * FROM ec_host_temperature_samples').all()).toEqual([]);
|
||||
expect(
|
||||
db().prepare('SELECT cpu_usage_percent FROM ec_host_resource_samples').all()
|
||||
).toEqual([{ cpu_usage_percent: 25 }]);
|
||||
expect(db().pragma('foreign_key_check')).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user