0.16.1-4 Hardware Monitoring

This commit is contained in:
2026-04-29 11:14:27 +00:00
parent 044ece0387
commit f9cf3cef09
8 changed files with 82 additions and 26 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "ripster-backend",
"version": "0.16.1-3",
"version": "0.16.1-4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-backend",
"version": "0.16.1-3",
"version": "0.16.1-4",
"dependencies": {
"archiver": "^7.0.1",
"cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ripster-backend",
"version": "0.16.1-3",
"version": "0.16.1-4",
"private": true,
"type": "commonjs",
"scripts": {
+60 -17
View File
@@ -34,6 +34,13 @@ function nowIso() {
return new Date().toISOString();
}
function isAbortError(error) {
if (!error || typeof error !== 'object') {
return false;
}
return error.name === 'AbortError' || error.code === 'ABORT_ERR';
}
function toBoolean(value) {
if (typeof value === 'boolean') {
return value;
@@ -337,6 +344,8 @@ class HardwareMonitorService {
this.running = false;
this.timer = null;
this.pollInFlight = false;
this.activePollAbortController = null;
this.reloadSettingsSeq = 0;
this.lastCpuTimes = null;
this.sensorsCommandAvailable = null;
this.nvidiaSmiAvailable = null;
@@ -390,6 +399,8 @@ class HardwareMonitorService {
}
async reloadFromSettings(options = {}) {
const requestSeq = this.reloadSettingsSeq + 1;
this.reloadSettingsSeq = requestSeq;
const forceBroadcast = Boolean(options?.forceBroadcast);
const forceImmediatePoll = Boolean(options?.forceImmediatePoll);
let settingsMap = {};
@@ -399,6 +410,9 @@ class HardwareMonitorService {
logger.warn('settings:load:failed', { error: errorToMeta(error) });
return this.getSnapshot();
}
if (requestSeq !== this.reloadSettingsSeq) {
return this.getSnapshot();
}
const nextEnabled = toBoolean(settingsMap.hardware_monitoring_enabled);
const nextIntervalMs = clampIntervalMs(settingsMap.hardware_monitoring_interval_ms);
@@ -521,6 +535,14 @@ class HardwareMonitorService {
this.running = false;
this.pollInFlight = false;
this.lastCpuTimes = null;
if (this.activePollAbortController) {
try {
this.activePollAbortController.abort();
} catch (_error) {
// ignore abort errors
}
this.activePollAbortController = null;
}
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
@@ -553,8 +575,10 @@ class HardwareMonitorService {
return;
}
this.pollInFlight = true;
const pollAbortController = new AbortController();
this.activePollAbortController = pollAbortController;
try {
const sample = await this.collectSample();
const sample = await this.collectSample(pollAbortController.signal);
this.lastSnapshot = {
enabled: true,
intervalMs: this.intervalMs,
@@ -564,6 +588,10 @@ class HardwareMonitorService {
};
this.broadcastUpdate();
} catch (error) {
if (isAbortError(error)) {
logger.debug('poll:aborted');
return;
}
logger.warn('poll:failed', { error: errorToMeta(error) });
this.lastSnapshot = {
...this.lastSnapshot,
@@ -574,6 +602,9 @@ class HardwareMonitorService {
};
this.broadcastUpdate();
} finally {
if (this.activePollAbortController === pollAbortController) {
this.activePollAbortController = null;
}
this.pollInFlight = false;
if (this.running && this.enabled) {
this.scheduleNext(this.intervalMs);
@@ -585,12 +616,12 @@ class HardwareMonitorService {
wsService.broadcast('HARDWARE_MONITOR_UPDATE', this.getSnapshot());
}
async collectSample() {
async collectSample(signal) {
const memory = this.collectMemoryMetrics();
const [cpu, gpu, storage] = await Promise.all([
this.collectCpuMetrics(),
this.collectGpuMetrics(),
this.collectStorageMetrics()
this.collectCpuMetrics(signal),
this.collectGpuMetrics(signal),
this.collectStorageMetrics(signal)
]);
return {
@@ -665,13 +696,13 @@ class HardwareMonitorService {
};
}
async collectCpuMetrics() {
async collectCpuMetrics(signal) {
const cpus = os.cpus() || [];
const currentTimes = this.getCpuTimes();
const usage = this.calculateCpuUsage(currentTimes, this.lastCpuTimes || []);
this.lastCpuTimes = currentTimes;
const tempMetrics = await this.collectCpuTemperatures();
const tempMetrics = await this.collectCpuTemperatures(signal);
const tempByCoreIndex = new Map(
(tempMetrics.perCore || []).map((item) => [Number(item.index), item.temperatureC])
);
@@ -708,8 +739,8 @@ class HardwareMonitorService {
};
}
async collectCpuTemperatures() {
const sensors = await this.collectTempsViaSensors();
async collectCpuTemperatures(signal) {
const sensors = await this.collectTempsViaSensors(signal);
if (sensors.available) {
return sensors;
}
@@ -732,7 +763,7 @@ class HardwareMonitorService {
};
}
async collectTempsViaSensors() {
async collectTempsViaSensors(signal) {
if (this.sensorsCommandAvailable === false) {
return {
source: 'sensors',
@@ -745,7 +776,8 @@ class HardwareMonitorService {
try {
const { stdout } = await execFileAsync('sensors', ['-j'], {
timeout: SENSORS_TIMEOUT_MS,
maxBuffer: 2 * 1024 * 1024
maxBuffer: 2 * 1024 * 1024,
signal
});
this.sensorsCommandAvailable = true;
const parsed = JSON.parse(String(stdout || '{}'));
@@ -756,6 +788,9 @@ class HardwareMonitorService {
...mapTemperatureCandidates(preferred)
};
} catch (error) {
if (isAbortError(error)) {
throw error;
}
if (isCommandMissingError(error)) {
this.sensorsCommandAvailable = false;
}
@@ -863,7 +898,7 @@ class HardwareMonitorService {
};
}
async collectGpuMetrics() {
async collectGpuMetrics(signal) {
if (this.nvidiaSmiAvailable === false) {
return {
source: 'nvidia-smi',
@@ -882,7 +917,8 @@ class HardwareMonitorService {
],
{
timeout: NVIDIA_SMI_TIMEOUT_MS,
maxBuffer: 1024 * 1024
maxBuffer: 1024 * 1024,
signal
}
);
@@ -910,6 +946,9 @@ class HardwareMonitorService {
message: null
};
} catch (error) {
if (isAbortError(error)) {
throw error;
}
const commandMissing = isCommandMissingError(error);
if (commandMissing) {
this.nvidiaSmiAvailable = false;
@@ -926,10 +965,10 @@ class HardwareMonitorService {
}
}
async collectStorageMetrics() {
async collectStorageMetrics(signal) {
const list = [];
for (const entry of this.monitoredPaths) {
const metric = await this.collectStorageForPath(entry);
const metric = await this.collectStorageForPath(entry, signal);
if (metric && !metric.hidden) {
list.push(metric);
}
@@ -959,7 +998,7 @@ class HardwareMonitorService {
return null;
}
async collectStorageForPath(entry) {
async collectStorageForPath(entry, signal) {
const key = String(entry?.key || '');
const label = String(entry?.label || key || 'Pfad');
const rawPath = String(entry?.path || '').trim();
@@ -1028,7 +1067,8 @@ class HardwareMonitorService {
try {
const { stdout } = await execFileAsync('df', ['-Pk', queryPath], {
timeout: DF_TIMEOUT_MS,
maxBuffer: 256 * 1024
maxBuffer: 256 * 1024,
signal
});
const parsed = parseDfStats(stdout);
if (!parsed) {
@@ -1077,6 +1117,9 @@ class HardwareMonitorService {
error: null
};
} catch (error) {
if (isAbortError(error)) {
throw error;
}
if (hideWhenUnavailable) {
return hiddenResult({
path: resolvedPath,