diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b02e0..499899e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,31 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/): - **MENOR** — características nuevas que no rompen lo anterior - **PARCHE** — arreglos de errores +--- +## [1.12.1] — 2026-07-27 +### Corregido +- **El monitor mostraba una CPU por proceso que engañaba.** La columna `%CPU` + de `ps aux` no es el consumo actual sino la **media desde que el proceso + arrancó** (tiempo de CPU dividido por tiempo de vida). Un proceso que trabajó + mucho hace tres días seguía apareciendo alto para siempre, y se leía como si + estuviera saturando la máquina. Se detectó porque los porcentajes por proceso + no cambiaban NUNCA entre lecturas mientras la CPU global sí variaba. + - Ahora se mide de verdad: dos lecturas de `/proc/PID/stat` separadas 500 ms, + el mismo método que ya usaba `getCpuUsage` para el total. Las dos esperas + corren en paralelo dentro del mismo `Promise.all`, así que no cuesta tiempo. + - La interfaz muestra el **porcentaje sobre el total de la máquina**, que es + lo que suele querer saberse, con el porcentaje de un núcleo y la media de + `ps` en el tooltip para poder comparar con `top`. + - Comprobado con carga artificial: el proceso ocupado marcaba 100% de un + núcleo (25% de un sistema de 4) mientras `ps` daba 152%, imposible para un + proceso de un solo hilo. + +### Cambiado +- El UTXO Map pedía las transacciones de contexto con `fetchWithTimeout` + directo, saltándose la caché añadida en la 1.11.0. Era el único punto que no + la aprovechaba. Ahora pasa por `get()`, así que esas transacciones —ya + confirmadas, e inmutables— se guardan toda la sesión. + --- ## [1.12.0] — 2026-07-27 ### Añadido diff --git a/dashboard.html b/dashboard.html index 0f80c04..14ceaab 100644 --- a/dashboard.html +++ b/dashboard.html @@ -2837,11 +2837,22 @@
{p.command}
- CPU {p.cpu}% + {/* Se muestra el % del sistema (lo que de verdad se + quiere saber: cuánto de la máquina se lleva). El + % de un núcleo va en el tooltip, para comparar con + top si hace falta. */} + 25?C.red:p.cpuSistema>10?C.amber:C.t2}> + + CPU {p.cpuSistema!=null?p.cpuSistema:p.cpu}% + + 30?C.red:p.mem>15?C.amber:C.t2}>RAM {p.mem}%
))} +
+ CPU sobre el total de la máquina ({sysData.cpu?.count||"?"} núcleos), medida en una ventana de 500 ms — no es la media desde el arranque que muestra ps, que engaña porque un proceso que trabajó mucho hace días sigue apareciendo alto. +
)} @@ -3571,13 +3582,15 @@ if(sorted.length>MAX_UTXOS) setErr(`Mostrando ${MAX_UTXOS} de ${sorted.length} UTXOs — se muestran los de mayor valor.`); // Cargar txs de origen para contexto (máx 10, las más grandes) + // Vía get() y no fetch directo: así pasa por la caché compartida. + // Las transacciones confirmadas son inmutables y se guardan toda la + // sesión, de modo que volver a este UTXO Map (o analizar una de estas + // txs en otra pestaña) no cuesta ni una petición más al nodo. const cache={}; const toFetch=limited.slice(0,10); await Promise.all(toFetch.map(async u=>{ - try{ - const res=await fetchWithTimeout(`${base}/api/tx/${u.txid}`); - if(res.ok) cache[u.txid]=await res.json(); - }catch{} + const t = await get(`/api/tx/${u.txid}`, null); + if (t) cache[u.txid] = t; })); setTxCache(cache); diff --git a/system-metrics.js b/system-metrics.js index 303c781..afc54bf 100644 --- a/system-metrics.js +++ b/system-metrics.js @@ -168,10 +168,33 @@ async function getDisk() { } // ── Procesos destacados (nombre limpio del binario) ──────────────────────── +// Tiempo de CPU consumido por un proceso, en ticks, desde /proc/PID/stat. +// Campos 14 (utime) y 15 (stime). El nombre del proceso va entre paréntesis y +// puede contener espacios, así que se corta por el ÚLTIMO ')' antes de partir. +function readProcCpuTicks(pid) { + try { + const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8"); + const resto = stat.slice(stat.lastIndexOf(")") + 2).split(" "); + // resto[0] es el campo 3 (estado), así que utime=campo14 → resto[11] + const utime = parseInt(resto[11], 10); + const stime = parseInt(resto[12], 10); + if (Number.isNaN(utime) || Number.isNaN(stime)) return null; + return utime + stime; + } catch { return null; } +} + +// OJO con la columna %CPU de `ps`: NO es el consumo actual, sino la media del +// proceso desde que arrancó (tiempo de CPU / tiempo de vida). Un proceso que +// trabajó mucho al principio y ahora está ocioso sigue mostrando un número +// alto días después, y se lee como si estuviera saturando la máquina. +// Aquí se mide de verdad: dos lecturas de /proc separadas 500 ms, el mismo +// método que ya usa getCpuUsage para el total del sistema. Las dos esperas +// corren en paralelo (van dentro del mismo Promise.all), así que no cuesta +// tiempo extra. async function getProcesses() { const result = await sh("ps aux --no-headers --sort=-%mem | head -8"); if (!result) return []; - return result.split("\n").map(line => { + const filas = result.split("\n").map(line => { const parts = line.trim().split(/\s+/); // Nombre limpio: basename del ejecutable; si es un intérprete (node, // python...), añade el basename del script que ejecuta. @@ -190,12 +213,33 @@ async function getProcesses() { } command = command.slice(0, 24); return { + pid: parts[1], user: parts[0], - cpu: parseFloat(parts[2]), + cpuMedia: parseFloat(parts[2]), // media desde el arranque (lo que da ps) mem: parseFloat(parts[3]), command, }; }).filter(p => p.mem > 0.5); + + const ticks1 = filas.map(p => readProcCpuTicks(p.pid)); + await sleep(500); + const ticks2 = filas.map(p => readProcCpuTicks(p.pid)); + + const USER_HZ = 100; // estándar en Linux + const VENTANA_S = 0.5; + const nucleos = os.cpus().length || 1; + + return filas.map((p, i) => { + let cpu = null, cpuSistema = null; + if (ticks1[i] !== null && ticks2[i] !== null) { + const seg = (ticks2[i] - ticks1[i]) / USER_HZ; + // % de UN núcleo (criterio de top/ps: puede pasar de 100 si va en varios) + cpu = Math.max(0, Math.round((seg / VENTANA_S) * 1000) / 10); + // % del total de la máquina, que es lo que suele querer saberse + cpuSistema = Math.round((cpu / nucleos) * 10) / 10; + } + return { user: p.user, command: p.command, mem: p.mem, cpu, cpuSistema, cpuMedia: p.cpuMedia }; + }); } // ── Load / Uptime ───────────────────────────────────────────────────────────