Skip to content

Commit 7b7ecd4

Browse files
authored
Merge pull request #759 from constructive-io/devin/1772308114-debug-memory-endpoint
feat(graphql-server): add dev-only /debug/memory endpoint for OOM debugging
2 parents c6015c6 + fbbc524 commit 7b7ecd4

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { getNodeEnv } from '@constructive-io/graphql-env';
2+
import { Logger } from '@pgpmjs/logger';
3+
import { svcCache } from '@pgpmjs/server-utils';
4+
import type { RequestHandler } from 'express';
5+
import { getCacheStats } from 'graphile-cache';
6+
import { getInFlightCount, getInFlightKeys } from './graphile';
7+
8+
const log = new Logger('debug-memory');
9+
10+
const toMB = (bytes: number): string => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
11+
12+
/**
13+
* Development-only debug endpoint for monitoring memory usage and cache state.
14+
*
15+
* Mounts GET /debug/memory which returns:
16+
* - Node.js process memory (heap, RSS, external, array buffers)
17+
* - Graphile cache stats (size, max, TTL, keys with ages)
18+
* - Service cache size
19+
* - In-flight handler creation count
20+
* - Process uptime
21+
*
22+
* This endpoint is only available when NODE_ENV=development.
23+
* In production, it returns 404.
24+
*/
25+
export const debugMemory: RequestHandler = (_req, res) => {
26+
if (getNodeEnv() !== 'development') {
27+
res.status(404).send('Not found');
28+
return;
29+
}
30+
31+
const mem = process.memoryUsage();
32+
const cacheStats = getCacheStats();
33+
34+
const response = {
35+
memory: {
36+
heapUsed: toMB(mem.heapUsed),
37+
heapTotal: toMB(mem.heapTotal),
38+
rss: toMB(mem.rss),
39+
external: toMB(mem.external),
40+
arrayBuffers: toMB(mem.arrayBuffers),
41+
},
42+
graphileCache: {
43+
size: cacheStats.size,
44+
max: cacheStats.max,
45+
ttl: `${(cacheStats.ttl / 1000 / 60).toFixed(0)} min`,
46+
keys: cacheStats.keys,
47+
},
48+
svcCache: {
49+
size: svcCache.size,
50+
},
51+
inFlight: {
52+
count: getInFlightCount(),
53+
keys: getInFlightKeys(),
54+
},
55+
uptime: `${(process.uptime() / 60).toFixed(1)} min`,
56+
timestamp: new Date().toISOString(),
57+
};
58+
59+
log.debug('Memory snapshot:', response);
60+
res.json(response);
61+
};

graphql/server/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { flush, flushService } from './middleware/flush';
2222
import { graphile } from './middleware/graphile';
2323
import { multipartBridge } from './middleware/multipart-bridge';
2424
import { createUploadAuthenticateMiddleware, uploadRoute } from './middleware/upload';
25+
import { debugMemory } from './middleware/debug-memory';
2526
import { normalizeServerOptions } from './options';
2627

2728
const log = new Logger('server');
@@ -127,6 +128,7 @@ class Server {
127128
});
128129

129130
healthz(app);
131+
app.get('/debug/memory', debugMemory);
130132
app.use(favicon);
131133
trustProxy(app, effectiveOpts.server.trustProxy);
132134
// Warn if a global CORS override is set in production

0 commit comments

Comments
 (0)