From f9cf3cef098b60fe9b001c7ea179a808739346a3 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Wed, 29 Apr 2026 11:14:27 +0000 Subject: [PATCH] 0.16.1-4 Hardware Monitoring --- backend/package-lock.json | 4 +- backend/package.json | 2 +- .../src/services/hardwareMonitorService.js | 77 +++++++++++++++---- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/App.jsx | 13 ++++ package-lock.json | 4 +- package.json | 2 +- 8 files changed, 82 insertions(+), 26 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index ca45b81..0fbdbd1 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -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", diff --git a/backend/package.json b/backend/package.json index d3fb3f7..a45498b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.16.1-3", + "version": "0.16.1-4", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/services/hardwareMonitorService.js b/backend/src/services/hardwareMonitorService.js index ae3e533..97bfe0b 100644 --- a/backend/src/services/hardwareMonitorService.js +++ b/backend/src/services/hardwareMonitorService.js @@ -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, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 28aceca..7fce5b5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.16.1-3", + "version": "0.16.1-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.16.1-3", + "version": "0.16.1-4", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 0b34065..b53928d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.16.1-3", + "version": "0.16.1-4", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d68a922..38eb3b3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -129,6 +129,11 @@ function getDownloadIndicatorMeta(summary) { }; } +const HARDWARE_MONITOR_SETTING_KEYS = new Set([ + 'hardware_monitoring_enabled', + 'hardware_monitoring_interval_ms' +]); + function App() { const appVersion = __APP_VERSION__; const [pipeline, setPipeline] = useState({ state: 'IDLE', progress: 0, context: {} }); @@ -511,10 +516,15 @@ function App() { const val = setting?.value; setExpertMode(val === 'true' || val === true); } + const normalizedKey = String(setting?.key || '').trim().toLowerCase(); + if (HARDWARE_MONITOR_SETTING_KEYS.has(normalizedKey)) { + refreshPipeline().catch(() => null); + } } if (message.type === 'SETTINGS_BULK_UPDATED') { const keys = message.payload?.keys || []; + const normalizedKeys = keys.map((key) => String(key || '').trim().toLowerCase()); if (keys.includes('ui_expert_mode')) { api.getSettings({ forceRefresh: true }) .then((response) => { @@ -524,6 +534,9 @@ function App() { }) .catch(() => null); } + if (normalizedKeys.some((key) => HARDWARE_MONITOR_SETTING_KEYS.has(key))) { + refreshPipeline().catch(() => null); + } } if (message.type === 'DOWNLOADS_UPDATED') { diff --git a/package-lock.json b/package-lock.json index faffc76..36afa70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.16.1-3", + "version": "0.16.1-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.16.1-3", + "version": "0.16.1-4", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index e6763ff..286794b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.16.1-3", + "version": "0.16.1-4", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",