fix(dashboard): measure V8 heap against its limit, not its current size
The Health Score's heap component divided heapUsed by heapTotal, which is only what V8 has grown the heap to so far, so a healthy server read 70-90% and dragged the score down. GET /api/settings/info now also reports server.memory.heapLimit (v8 heap_size_limit, the ceiling the process dies at); the V8 Heap bar and the Health Score divide by it, falling back to heapTotal against an older server.
This commit is contained in:
@@ -957,7 +957,14 @@ export const api = {
|
||||
node_version: string;
|
||||
platform: string;
|
||||
ws_connections: number;
|
||||
memory: { rss: number; heapTotal: number; heapUsed: number; external: number };
|
||||
memory: {
|
||||
rss: number;
|
||||
heapTotal: number;
|
||||
heapUsed: number;
|
||||
external: number;
|
||||
/** V8's heap_size_limit; absent on older servers (fall back to heapTotal). */
|
||||
heapLimit?: number;
|
||||
};
|
||||
cpu_load: number[];
|
||||
arch: string;
|
||||
total_mem: number;
|
||||
|
||||
@@ -132,7 +132,14 @@ interface SystemInfo {
|
||||
node_version: string;
|
||||
platform: string;
|
||||
ws_connections: number;
|
||||
memory: { rss: number; heapTotal: number; heapUsed: number; external: number };
|
||||
memory: {
|
||||
rss: number;
|
||||
heapTotal: number;
|
||||
heapUsed: number;
|
||||
external: number;
|
||||
/** V8's heap_size_limit; absent on older servers (fall back to heapTotal). */
|
||||
heapLimit?: number;
|
||||
};
|
||||
cpu_load: number[];
|
||||
arch: string;
|
||||
total_mem: number;
|
||||
@@ -1562,10 +1569,10 @@ function SystemHealthTab() {
|
||||
|
||||
const memUsedPct =
|
||||
info.server.total_mem > 0 ? (1 - info.server.free_mem / info.server.total_mem) * 100 : 0;
|
||||
const heapUsedPct =
|
||||
info.server.memory.heapTotal > 0
|
||||
? (info.server.memory.heapUsed / info.server.memory.heapTotal) * 100
|
||||
: 0;
|
||||
// Against the heap LIMIT: heapTotal is only what V8 has grown to so far, so
|
||||
// used/total reads 70-90% on a healthy process and dragged the health score.
|
||||
const heapMax = info.server.memory.heapLimit || info.server.memory.heapTotal;
|
||||
const heapUsedPct = heapMax > 0 ? (info.server.memory.heapUsed / heapMax) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalEntries,
|
||||
@@ -1701,7 +1708,7 @@ function SystemHealthTab() {
|
||||
</Tip>
|
||||
<Tip
|
||||
block
|
||||
raw={`V8 Heap: ${heapUsedPct.toFixed(1)}% used\n${formatBytes(info.server.memory.heapUsed)} / ${formatBytes(info.server.memory.heapTotal)}\nExternal: ${formatBytes(info.server.memory.external)}`}
|
||||
raw={`V8 Heap: ${heapUsedPct.toFixed(1)}% used\n${formatBytes(info.server.memory.heapUsed)} / ${formatBytes(info.server.memory.heapLimit || info.server.memory.heapTotal)} limit\nAllocated: ${formatBytes(info.server.memory.heapTotal)}\nExternal: ${formatBytes(info.server.memory.external)}`}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-[10px]">
|
||||
|
||||
@@ -1896,6 +1896,14 @@ describe("Transcript cache integration", () => {
|
||||
assert.equal(res.body.server.version, pkg.version);
|
||||
});
|
||||
|
||||
it("reports the V8 heap LIMIT, not just the currently allocated heapTotal", async () => {
|
||||
const res = await fetch("/api/settings/info");
|
||||
const { heapUsed, heapTotal, heapLimit } = res.body.server.memory;
|
||||
assert.ok(Number.isFinite(heapLimit), "heapLimit should be a number");
|
||||
// heapTotal is what V8 has grown to so far; the limit is the ceiling.
|
||||
assert.ok(heapLimit >= heapTotal && heapLimit > heapUsed);
|
||||
});
|
||||
|
||||
it("should evict cache entry on SessionEnd", async () => {
|
||||
const tmpTranscript = path.join(os.tmpdir(), `test-evict-${Date.now()}.jsonl`);
|
||||
fs.writeFileSync(
|
||||
|
||||
@@ -139,6 +139,7 @@ const SETTINGS_INFO_EXAMPLE = {
|
||||
heapTotal: 71303168,
|
||||
heapUsed: 58392104,
|
||||
external: 3211884,
|
||||
heapLimit: 4345298944,
|
||||
arrayBuffers: 1048576,
|
||||
},
|
||||
cpu_load: [2.13, 2.45, 2.31],
|
||||
|
||||
@@ -7,6 +7,7 @@ const { Router } = require("express");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const v8 = require("v8");
|
||||
const { db, stmts, DB_PATH, DEFAULT_PRICING, applyIntroPricing } = require("../db");
|
||||
const { getConnectionCount } = require("../websocket");
|
||||
const { transcriptCache } = require("./hooks");
|
||||
@@ -121,7 +122,10 @@ router.get("/info", (req, res) => {
|
||||
node_version: process.version,
|
||||
platform: process.platform,
|
||||
ws_connections: getConnectionCount(),
|
||||
memory: process.memoryUsage(),
|
||||
// heapTotal is only what V8 has grown the heap to SO FAR, so used/total
|
||||
// sits at 70-90% on a perfectly healthy process. heapLimit is the ceiling
|
||||
// the process actually dies at — the denominator a "heap used %" needs.
|
||||
memory: { ...process.memoryUsage(), heapLimit: v8.getHeapStatistics().heap_size_limit },
|
||||
cpu_load: os.loadavg(),
|
||||
arch: os.arch(),
|
||||
total_mem: os.totalmem(),
|
||||
|
||||
Reference in New Issue
Block a user