Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions relay/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
5 changes: 3 additions & 2 deletions relay/src/public/dashboard/src/views/Charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};

Expand Down
2 changes: 1 addition & 1 deletion relay/src/public/dashboard/src/views/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}

Expand Down
49 changes: 14 additions & 35 deletions relay/src/utils/memory-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import v8 from 'v8';
import { checkMemoryPressure } from './memory-utils';

describe('memory-utils', () => {
Expand All @@ -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<typeof v8.getHeapStatistics>);
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);
});
Expand Down
26 changes: 20 additions & 6 deletions relay/src/utils/memory-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @module utils/memory-utils
*/

import v8 from "v8";
import { loggers } from "./logger";

const log = loggers.server;
Expand All @@ -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
Expand All @@ -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),
};
}

Expand Down
Loading