diff --git a/relay/src/index.ts b/relay/src/index.ts index ff3837c7..6b6fecf0 100644 --- a/relay/src/index.ts +++ b/relay/src/index.ts @@ -169,12 +169,15 @@ async function initializeServer() { if (msg && msg.headers && msg.headers.token) { const hasValidAuth = validateAdminToken(msg.headers.token); if (hasValidAuth) { - loggers.server.info(`🔍 PUT allowed - valid token: ${msg.headers}`); + loggers.server.debug("🔍 PUT allowed - valid token"); return true; } } - loggers.server.warn(`❌ Operation denied - no valid auth: ${JSON.stringify(msg.headers)}`); + // Do not log header contents: they may contain (attempted) tokens + loggers.server.warn( + `❌ Operation denied - no valid auth (token present: ${!!(msg && msg.headers && msg.headers.token)})` + ); return false; } diff --git a/relay/src/public/dashboard/src/views/Charts.tsx b/relay/src/public/dashboard/src/views/Charts.tsx index 0bb4c5b3..07c713d8 100644 --- a/relay/src/public/dashboard/src/views/Charts.tsx +++ b/relay/src/public/dashboard/src/views/Charts.tsx @@ -64,10 +64,11 @@ function Charts() { }, [isAuthenticated, loadStats]); const formatBytes = (bytes: number) => { - if (!bytes && bytes !== 0) return "0 B"; + // Math.log(0) is -Infinity, which rendered "NaN undefined" for 0 bytes + if (!bytes || bytes <= 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); + const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; diff --git a/relay/src/public/dashboard/src/views/Files.tsx b/relay/src/public/dashboard/src/views/Files.tsx index d2db70d6..ec83871c 100644 --- a/relay/src/public/dashboard/src/views/Files.tsx +++ b/relay/src/public/dashboard/src/views/Files.tsx @@ -312,7 +312,7 @@ function Files() { if (bytes < 1024) return bytes + ' B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) + const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } diff --git a/relay/src/utils/memory-utils.test.ts b/relay/src/utils/memory-utils.test.ts index f2b19ff1..c61bd613 100644 --- a/relay/src/utils/memory-utils.test.ts +++ b/relay/src/utils/memory-utils.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; +import v8 from 'v8'; import { checkMemoryPressure } from './memory-utils'; describe('memory-utils', () => { @@ -7,66 +8,44 @@ describe('memory-utils', () => { vi.restoreAllMocks(); }); - // The default heap limit is 4096 MB. + // Fixed heap limit for deterministic tests (mocked below). const HEAP_LIMIT = 4096 * 1024 * 1024; // 4096 MB in bytes - it('should return false when memory usage is below the default threshold (80%)', () => { + const mockHeap = (usedFraction: number) => { + vi.spyOn(v8, 'getHeapStatistics').mockReturnValue({ + heap_size_limit: HEAP_LIMIT, + } as ReturnType); vi.spyOn(process, 'memoryUsage').mockReturnValue({ - heapUsed: Math.floor(HEAP_LIMIT * 0.70), // 70% used + heapUsed: Math.floor(HEAP_LIMIT * usedFraction), heapTotal: HEAP_LIMIT, external: 0, rss: HEAP_LIMIT, arrayBuffers: 0, }); + }; + it('should return false when memory usage is below the default threshold (80%)', () => { + mockHeap(0.70); expect(checkMemoryPressure()).toBe(false); }); it('should return true when memory usage is exactly at the default threshold (80%)', () => { - vi.spyOn(process, 'memoryUsage').mockReturnValue({ - heapUsed: Math.floor(HEAP_LIMIT * 0.80), // 80% used - heapTotal: HEAP_LIMIT, - external: 0, - rss: HEAP_LIMIT, - arrayBuffers: 0, - }); - + mockHeap(0.80); expect(checkMemoryPressure()).toBe(true); }); it('should return true when memory usage is above the default threshold (80%)', () => { - vi.spyOn(process, 'memoryUsage').mockReturnValue({ - heapUsed: Math.floor(HEAP_LIMIT * 0.90), // 90% used - heapTotal: HEAP_LIMIT, - external: 0, - rss: HEAP_LIMIT, - arrayBuffers: 0, - }); - + mockHeap(0.90); expect(checkMemoryPressure()).toBe(true); }); it('should return false when memory usage is below a custom threshold', () => { - vi.spyOn(process, 'memoryUsage').mockReturnValue({ - heapUsed: Math.floor(HEAP_LIMIT * 0.85), // 85% used - heapTotal: HEAP_LIMIT, - external: 0, - rss: HEAP_LIMIT, - arrayBuffers: 0, - }); - + mockHeap(0.85); expect(checkMemoryPressure(90)).toBe(false); }); it('should return true when memory usage is at or above a custom threshold', () => { - vi.spyOn(process, 'memoryUsage').mockReturnValue({ - heapUsed: Math.floor(HEAP_LIMIT * 0.60), // 60% used - heapTotal: HEAP_LIMIT, - external: 0, - rss: HEAP_LIMIT, - arrayBuffers: 0, - }); - + mockHeap(0.60); expect(checkMemoryPressure(50)).toBe(true); expect(checkMemoryPressure(60)).toBe(true); }); diff --git a/relay/src/utils/memory-utils.ts b/relay/src/utils/memory-utils.ts index e69fb6a7..5b2f3481 100644 --- a/relay/src/utils/memory-utils.ts +++ b/relay/src/utils/memory-utils.ts @@ -7,6 +7,7 @@ * @module utils/memory-utils */ +import v8 from "v8"; import { loggers } from "./logger"; const log = loggers.server; @@ -23,12 +24,25 @@ interface MemoryStats { } /** - * Default heap limit in MB (Node.js default or from --max-old-space-size) + * Actual heap limit in MB as reported by V8 (accounts for --max-old-space-size, + * container limits, and machine defaults). The previous implementation assumed + * a fixed 4096 MB when NODE_OPTIONS wasn't set, so memory pressure was never + * detected on smaller machines (and mis-detected on larger ones). */ -const DEFAULT_HEAP_LIMIT_MB = parseInt( - process.env.NODE_OPTIONS?.match(/--max-old-space-size=(\d+)/)?.[1] || "4096", - 10 -); +function getHeapLimitMB(): number { + try { + const limitBytes = v8.getHeapStatistics().heap_size_limit; + if (limitBytes > 0) { + return Math.round(limitBytes / (1024 * 1024)); + } + } catch (e) { + // fall through to env-based fallback + } + return parseInt( + process.env.NODE_OPTIONS?.match(/--max-old-space-size=(\d+)/)?.[1] || "4096", + 10 + ); +} /** * Get current memory usage statistics @@ -45,7 +59,7 @@ function getMemoryUsage(): MemoryStats { heapTotalMB, externalMB, rssMemoryMB, - heapUsagePercent: Math.round((heapUsedMB / DEFAULT_HEAP_LIMIT_MB) * 100), + heapUsagePercent: Math.round((heapUsedMB / getHeapLimitMB()) * 100), }; }