diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index f7e92b0d..b0fc25ca 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -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; diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx index 6e5ebdcb..aed2016c 100644 --- a/client/src/pages/Dashboard.tsx +++ b/client/src/pages/Dashboard.tsx @@ -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() {
diff --git a/server/__tests__/api.test.js b/server/__tests__/api.test.js index 2dc79ba6..34909884 100644 --- a/server/__tests__/api.test.js +++ b/server/__tests__/api.test.js @@ -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( diff --git a/server/openapi-extra/override-ops.js b/server/openapi-extra/override-ops.js index a4e51d2d..6185cd8d 100644 --- a/server/openapi-extra/override-ops.js +++ b/server/openapi-extra/override-ops.js @@ -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], diff --git a/server/routes/settings.js b/server/routes/settings.js index 15cc224f..79f9ae26 100644 --- a/server/routes/settings.js +++ b/server/routes/settings.js @@ -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(),