Add host temperature telemetry and history
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import { db } from '../db/index.js';
|
||||
import { historyTemperatures } from '../../host-temperatures.js';
|
||||
import type { HostInventory } from '../../hosts.js';
|
||||
import type { CapacityQuery, TemperatureHistorySeries } from '../../capacity.js';
|
||||
|
||||
export function migrateTemperatures(handle: Database.Database): void {
|
||||
handle.exec(`CREATE TABLE IF NOT EXISTS ec_host_temperature_samples (
|
||||
host_id TEXT NOT NULL REFERENCES ec_hosts(id), bucket TEXT NOT NULL,
|
||||
sensor_id TEXT NOT NULL, kind TEXT NOT NULL, label TEXT NOT NULL,
|
||||
celsius REAL NOT NULL CHECK(celsius BETWEEN -100 AND 200),
|
||||
PRIMARY KEY(host_id,bucket,sensor_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ec_host_temperatures_retention ON ec_host_temperature_samples(bucket);`);
|
||||
}
|
||||
|
||||
export function recordTemperatures(
|
||||
hostId: string,
|
||||
inventory: HostInventory,
|
||||
timestamp: string
|
||||
): void {
|
||||
const sensors = historyTemperatures(inventory.temperatures);
|
||||
if (!sensors.length) return;
|
||||
const bucket = new Date(Math.floor(Date.parse(timestamp) / 60_000) * 60_000).toISOString();
|
||||
const insert = db().prepare(`INSERT INTO ec_host_temperature_samples
|
||||
(host_id,bucket,sensor_id,kind,label,celsius) VALUES(?,?,?,?,?,?)
|
||||
ON CONFLICT(host_id,bucket,sensor_id) DO UPDATE SET
|
||||
celsius=MAX(celsius,excluded.celsius),kind=excluded.kind,label=excluded.label`);
|
||||
for (const sensor of sensors)
|
||||
insert.run(hostId, bucket, sensor.id, sensor.kind, sensor.label, sensor.celsius);
|
||||
}
|
||||
|
||||
/** Runs on the capacity report worker, returning at most 168 points per series. */
|
||||
export function readTemperatureHistory(
|
||||
query: CapacityQuery,
|
||||
hosts: { id: string; name: string; created_at: string }[],
|
||||
startAt: number,
|
||||
endAt: number,
|
||||
bucketMs: number
|
||||
): TemperatureHistorySeries[] {
|
||||
const count = Math.round((endAt - startAt) / bucketMs);
|
||||
const statement = db().prepare(`SELECT sensor_id,kind,label,
|
||||
MIN(?,MAX(0,CAST((CAST(strftime('%s',bucket) AS INTEGER)*1000-?) / ? AS INTEGER))) AS period,
|
||||
MAX(celsius) AS celsius
|
||||
FROM ec_host_temperature_samples WHERE host_id=? AND bucket>=? AND bucket<=?
|
||||
${query.hostId ? '' : "AND sensor_id='cpu'"}
|
||||
GROUP BY sensor_id,period ORDER BY sensor_id,period`);
|
||||
const result: TemperatureHistorySeries[] = [];
|
||||
for (const host of hosts) {
|
||||
const bySensor = new Map<string, TemperatureHistorySeries>();
|
||||
const add = (id: string, kind: TemperatureHistorySeries['kind'], label: string) => {
|
||||
const series = {
|
||||
id: `${host.id}:${id}`,
|
||||
hostId: host.id,
|
||||
kind,
|
||||
label,
|
||||
points: Array.from({ length: count }, (_, i) => ({
|
||||
at: startAt + i * bucketMs,
|
||||
celsius: null as number | null
|
||||
}))
|
||||
};
|
||||
bySensor.set(id, series);
|
||||
result.push(series);
|
||||
return series;
|
||||
};
|
||||
add('cpu', 'cpu', query.hostId ? 'CPU temperature' : host.name);
|
||||
const rows = statement.all(
|
||||
count - 1,
|
||||
startAt,
|
||||
bucketMs,
|
||||
host.id,
|
||||
new Date(
|
||||
Math.max(
|
||||
Math.floor(Date.parse(host.created_at) / 60_000) * 60_000,
|
||||
Math.floor(startAt / 60_000) * 60_000
|
||||
)
|
||||
).toISOString(),
|
||||
new Date(endAt).toISOString()
|
||||
) as { sensor_id: string; kind: string; label: string; period: number; celsius: number }[];
|
||||
for (const row of rows) {
|
||||
let series = bySensor.get(row.sensor_id);
|
||||
if (!series) {
|
||||
if (!['nvme', 'chipset'].includes(row.kind) || bySensor.size >= 17) continue;
|
||||
const kind = row.kind as 'nvme' | 'chipset';
|
||||
const number = [...bySensor.values()].filter((s) => s.kind === kind).length + 1;
|
||||
series = add(
|
||||
row.sensor_id,
|
||||
kind,
|
||||
`${kind === 'nvme' ? 'NVMe drive' : 'Chipset'} ${number}`
|
||||
);
|
||||
}
|
||||
if (series.points[row.period]) series.points[row.period].celsius = row.celsius;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user