immortal-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +577 -0
  3. package/docs/CHANGELOG.md +21 -0
  4. package/docs/CONTRIBUTING.md +41 -0
  5. package/docs/api/chaos.md +179 -0
  6. package/docs/api/dashboard.md +109 -0
  7. package/docs/api/isolation.md +191 -0
  8. package/docs/api/lifecycle.md +187 -0
  9. package/docs/api/monitoring.md +313 -0
  10. package/docs/api/plugins.md +217 -0
  11. package/docs/api/recovery.md +267 -0
  12. package/docs/api/safe-zone.md +236 -0
  13. package/docs/api/supervision.md +285 -0
  14. package/docs/guides/configuration.md +168 -0
  15. package/docs/guides/express.md +171 -0
  16. package/docs/guides/fastify.md +188 -0
  17. package/docs/guides/koa.md +102 -0
  18. package/docs/guides/nestjs.md +182 -0
  19. package/docs/guides/testing.md +91 -0
  20. package/examples/express-basic/index.ts +462 -0
  21. package/examples/express-basic/package.json +21 -0
  22. package/examples/express-basic/tsconfig.json +24 -0
  23. package/examples/fastify-microservice/index.ts +342 -0
  24. package/examples/fastify-microservice/package.json +22 -0
  25. package/examples/fastify-microservice/tsconfig.json +24 -0
  26. package/examples/invoice-service/data/invoices.db +0 -0
  27. package/examples/invoice-service/data/invoices.db-shm +0 -0
  28. package/examples/invoice-service/data/invoices.db-wal +0 -0
  29. package/examples/invoice-service/package.json +25 -0
  30. package/examples/invoice-service/public/index.html +5025 -0
  31. package/examples/invoice-service/src/db.ts +608 -0
  32. package/examples/invoice-service/src/pdf.ts +358 -0
  33. package/examples/invoice-service/src/server.ts +527 -0
  34. package/examples/invoice-service/src/store.ts +159 -0
  35. package/examples/invoice-service/src/types.ts +193 -0
  36. package/examples/invoice-service/tsconfig.json +23 -0
  37. package/examples/nestjs-enterprise/app.module.ts +561 -0
  38. package/examples/nestjs-enterprise/main.ts +67 -0
  39. package/examples/nestjs-enterprise/package.json +26 -0
  40. package/examples/nestjs-enterprise/tsconfig.json +27 -0
  41. package/immortal-js-1.0.0.tgz +0 -0
  42. package/package.json +33 -0
  43. package/packages/adapter-express/package.json +34 -0
  44. package/packages/adapter-express/src/index.ts +349 -0
  45. package/packages/adapter-express/tsconfig.json +14 -0
  46. package/packages/adapter-fastify/package.json +56 -0
  47. package/packages/adapter-fastify/src/plugin.ts +226 -0
  48. package/packages/adapter-fastify/tsconfig.json +14 -0
  49. package/packages/adapter-koa/package.json +55 -0
  50. package/packages/adapter-koa/src/index.ts +207 -0
  51. package/packages/adapter-koa/tsconfig.json +14 -0
  52. package/packages/adapter-nestjs/package.json +61 -0
  53. package/packages/adapter-nestjs/src/immortal.module.ts +313 -0
  54. package/packages/adapter-nestjs/src/index.ts +14 -0
  55. package/packages/adapter-nestjs/tsconfig.json +16 -0
  56. package/packages/core/package.json +56 -0
  57. package/packages/core/src/chaos/ChaosEngine.ts +249 -0
  58. package/packages/core/src/config/defaults.ts +200 -0
  59. package/packages/core/src/config/schema.ts +199 -0
  60. package/packages/core/src/event-bus.ts +168 -0
  61. package/packages/core/src/index.ts +164 -0
  62. package/packages/core/src/isolation/BulkheadPool.ts +279 -0
  63. package/packages/core/src/isolation/WorkerSandbox.ts +306 -0
  64. package/packages/core/src/isolation/index.ts +8 -0
  65. package/packages/core/src/lifecycle/GracefulShutdown.ts +161 -0
  66. package/packages/core/src/logger.ts +104 -0
  67. package/packages/core/src/monitoring/DiagnosticsChannel.ts +248 -0
  68. package/packages/core/src/monitoring/HealthMonitor.ts +191 -0
  69. package/packages/core/src/monitoring/MemoryLeakGuard.ts +340 -0
  70. package/packages/core/src/monitoring/MetricsCollector.ts +219 -0
  71. package/packages/core/src/monitoring/index.ts +10 -0
  72. package/packages/core/src/plugins/BuiltinPlugins.ts +269 -0
  73. package/packages/core/src/recovery/CircuitBreaker.ts +334 -0
  74. package/packages/core/src/recovery/FallbackCache.ts +328 -0
  75. package/packages/core/src/recovery/RetryEngine.ts +225 -0
  76. package/packages/core/src/recovery/Timeout.ts +97 -0
  77. package/packages/core/src/recovery/index.ts +11 -0
  78. package/packages/core/src/runtime.ts +242 -0
  79. package/packages/core/src/safe-zone/AsyncBoundary.ts +114 -0
  80. package/packages/core/src/safe-zone/ErrorTrap.ts +347 -0
  81. package/packages/core/src/safe-zone/SafeWrapper.ts +317 -0
  82. package/packages/core/src/safe-zone/index.ts +23 -0
  83. package/packages/core/src/supervision/ClusterManager.ts +243 -0
  84. package/packages/core/src/supervision/RestartStrategy.ts +68 -0
  85. package/packages/core/src/supervision/Supervisor.ts +311 -0
  86. package/packages/core/src/supervision/index.ts +11 -0
  87. package/packages/core/src/types.ts +470 -0
  88. package/packages/core/test/bulkhead.test.ts +310 -0
  89. package/packages/core/test/circuit-breaker.test.ts +153 -0
  90. package/packages/core/test/memory-guard.test.ts +213 -0
  91. package/packages/core/test/retry.test.ts +110 -0
  92. package/packages/core/test/safe-zone.test.ts +271 -0
  93. package/packages/core/test/supervisor.test.ts +310 -0
  94. package/packages/core/tsconfig.json +13 -0
  95. package/packages/dashboard/package.json +56 -0
  96. package/packages/dashboard/server/DashboardServer.ts +454 -0
  97. package/packages/dashboard/tsconfig.json +14 -0
  98. package/tsconfig.json +25 -0
@@ -0,0 +1,340 @@
1
+ /**
2
+ * @file MemoryLeakGuard.ts
3
+ * @description Memory leak detection and proactive restart
4
+ *
5
+ * Detection strategy (multi-signal, not just heap size):
6
+ * 1. Linear regression on heap growth across multiple windows
7
+ * 2. GC frequency trends (GC running more = struggling to reclaim)
8
+ * 3. Old generation growth rate (new gen is expected to be volatile)
9
+ * 4. RSS growth (overall process memory, includes native allocations)
10
+ * 5. WeakRef tracking for specific objects (optional, developer-controlled)
11
+ *
12
+ * Avoids false positives by:
13
+ * - Requiring N consecutive windows of growth (not just one spike)
14
+ * - Ignoring normal GC-driven sawtooth patterns
15
+ * - Using absolute thresholds as final override (when memory is genuinely critical)
16
+ *
17
+ * Proactive restart:
18
+ * Restarting before OOM is far safer than letting it crash:
19
+ * - Controlled: Supervisor orchestrates restart with zero-downtime
20
+ * - Uncontrolled: OOM kill leaves requests in-flight with no response
21
+ */
22
+
23
+ import * as v8 from "node:v8";
24
+ import type { MemoryGuardConfig } from "../types.js";
25
+ import { DEFAULT_MEMORY_GUARD } from "../config/defaults.js";
26
+ import { getLogger } from "../logger.js";
27
+ import type { ImmortalEventBus } from "../event-bus.js";
28
+ import * as os from "node:os";
29
+
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+ // TYPES
32
+ // ─────────────────────────────────────────────────────────────────────────────
33
+
34
+ interface HeapWindow {
35
+ timestamp: number;
36
+ heapUsedMb: number;
37
+ heapTotalMb: number;
38
+ rssMb: number;
39
+ externalMb: number;
40
+ }
41
+
42
+ interface LeakSignal {
43
+ type: "heap-growth" | "rss-growth" | "gc-pressure" | "absolute-threshold";
44
+ severity: "warning" | "critical";
45
+ value: number;
46
+ threshold: number;
47
+ }
48
+
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ // MEMORY LEAK GUARD CLASS
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+
53
+ export class MemoryLeakGuard {
54
+ private readonly config: Required<MemoryGuardConfig>;
55
+ private readonly bus: ImmortalEventBus;
56
+ private interval?: ReturnType<typeof setInterval>;
57
+ private running = false;
58
+ private windows: HeapWindow[] = [];
59
+ private consecutiveGrowthWindows = 0;
60
+ private lastRestartTime = 0;
61
+ private readonly minRestartIntervalMs = 5 * 60 * 1000; // Don't restart more than once per 5 minutes
62
+
63
+ // Adaptive thresholds based on available system RAM
64
+ private readonly adaptedWarningMb: number;
65
+ private readonly adaptedRestartMb: number;
66
+
67
+ constructor(config: MemoryGuardConfig = {}, bus: ImmortalEventBus) {
68
+ this.config = { ...DEFAULT_MEMORY_GUARD, ...config };
69
+ this.bus = bus;
70
+
71
+ // Adaptive thresholds: use configured values or 40%/60% of system RAM
72
+ if (this.config.adaptiveThresholds) {
73
+ const systemRamMb = os.totalmem() / 1024 / 1024;
74
+ this.adaptedWarningMb = Math.min(this.config.warningThresholdMb, systemRamMb * 0.40);
75
+ this.adaptedRestartMb = Math.min(this.config.restartThresholdMb, systemRamMb * 0.60);
76
+ } else {
77
+ this.adaptedWarningMb = this.config.warningThresholdMb;
78
+ this.adaptedRestartMb = this.config.restartThresholdMb;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Start memory monitoring
84
+ */
85
+ start(): void {
86
+ if (this.running) return;
87
+ this.running = true;
88
+
89
+ this.interval = setInterval(() => {
90
+ this.sample();
91
+ }, this.config.checkIntervalMs).unref();
92
+
93
+ getLogger().info("MemoryLeakGuard started", {
94
+ checkIntervalMs: this.config.checkIntervalMs,
95
+ warningThresholdMb: `${this.adaptedWarningMb.toFixed(0)}MB`,
96
+ restartThresholdMb: `${this.adaptedRestartMb.toFixed(0)}MB`,
97
+ adaptive: this.config.adaptiveThresholds,
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Stop monitoring
103
+ */
104
+ stop(): void {
105
+ if (!this.running) return;
106
+ this.running = false;
107
+
108
+ if (this.interval) {
109
+ clearInterval(this.interval);
110
+ delete this.interval;
111
+ }
112
+
113
+ getLogger().info("MemoryLeakGuard stopped");
114
+ }
115
+
116
+ /**
117
+ * Track a specific object for GC verification (optional)
118
+ * If the object is NOT garbage collected within timeoutMs, logs a warning
119
+ */
120
+ trackObject(
121
+ obj: object,
122
+ name: string,
123
+ expectedGcMs = 30_000
124
+ ): void {
125
+ if (!this.config.enableObjectTracking) return;
126
+
127
+ const ref = new WeakRef(obj);
128
+ const registry = new FinalizationRegistry((objectName: string) => {
129
+ getLogger().debug(`MemoryLeakGuard: Object '${objectName}' was garbage collected ✓`);
130
+ });
131
+
132
+ registry.register(obj, name);
133
+
134
+ setTimeout(() => {
135
+ if (ref.deref() !== undefined) {
136
+ getLogger().warn(`MemoryLeakGuard: Object '${name}' was NOT garbage collected after ${expectedGcMs}ms`, {
137
+ hint: "This may indicate a memory leak (event listener not removed, connection not closed, etc.)",
138
+ });
139
+
140
+ this.bus.emit("memory:leak-detected", {
141
+ data: { objectName: name, timeoutMs: expectedGcMs },
142
+ });
143
+ }
144
+ }, expectedGcMs).unref();
145
+ }
146
+
147
+ // ─────────────────────────────────────────────────────────────────────────
148
+ // SAMPLING LOGIC
149
+ // ─────────────────────────────────────────────────────────────────────────
150
+
151
+ private sample(): void {
152
+ const heapStats = v8.getHeapStatistics();
153
+ const memUsage = process.memoryUsage();
154
+
155
+ const window: HeapWindow = {
156
+ timestamp: Date.now(),
157
+ heapUsedMb: heapStats.used_heap_size / 1024 / 1024,
158
+ heapTotalMb: heapStats.heap_size_limit / 1024 / 1024,
159
+ rssMb: memUsage.rss / 1024 / 1024,
160
+ externalMb: memUsage.external / 1024 / 1024,
161
+ };
162
+
163
+ this.windows.push(window);
164
+
165
+ // Keep only last 30 windows
166
+ if (this.windows.length > 30) {
167
+ this.windows.shift();
168
+ }
169
+
170
+ const signals = this.detectLeakSignals(window);
171
+ this.processSignals(signals, window);
172
+ }
173
+
174
+ private detectLeakSignals(current: HeapWindow): LeakSignal[] {
175
+ const signals: LeakSignal[] = [];
176
+
177
+ // ── Signal 1: Absolute threshold (immediate response) ──────────────
178
+ if (current.heapUsedMb > this.adaptedRestartMb) {
179
+ signals.push({
180
+ type: "absolute-threshold",
181
+ severity: "critical",
182
+ value: current.heapUsedMb,
183
+ threshold: this.adaptedRestartMb,
184
+ });
185
+ } else if (current.heapUsedMb > this.adaptedWarningMb) {
186
+ signals.push({
187
+ type: "absolute-threshold",
188
+ severity: "warning",
189
+ value: current.heapUsedMb,
190
+ threshold: this.adaptedWarningMb,
191
+ });
192
+ }
193
+
194
+ // Need at least 5 windows for trend analysis
195
+ if (this.windows.length < 5) return signals;
196
+
197
+ // ── Signal 2: Heap growth trend (linear regression) ──────────────
198
+ const heapGrowthRate = this.computeGrowthRate(this.windows.map((w) => w.heapUsedMb));
199
+ if (heapGrowthRate > 50) {
200
+ // Growing > 50MB/hour
201
+ signals.push({
202
+ type: "heap-growth",
203
+ severity: "critical",
204
+ value: heapGrowthRate,
205
+ threshold: 50,
206
+ });
207
+ } else if (heapGrowthRate > 20) {
208
+ // Growing > 20MB/hour
209
+ signals.push({
210
+ type: "heap-growth",
211
+ severity: "warning",
212
+ value: heapGrowthRate,
213
+ threshold: 20,
214
+ });
215
+ }
216
+
217
+ // ── Signal 3: RSS growth ───────────────────────────────────────────
218
+ const rssGrowthRate = this.computeGrowthRate(this.windows.map((w) => w.rssMb));
219
+ if (rssGrowthRate > 100) {
220
+ signals.push({
221
+ type: "rss-growth",
222
+ severity: "critical",
223
+ value: rssGrowthRate,
224
+ threshold: 100,
225
+ });
226
+ }
227
+
228
+ return signals;
229
+ }
230
+
231
+ private processSignals(signals: LeakSignal[], current: HeapWindow): void {
232
+ const logger = getLogger();
233
+ const hasCritical = signals.some((s) => s.severity === "critical");
234
+ const hasWarning = signals.some((s) => s.severity === "warning");
235
+
236
+ if (hasCritical) {
237
+ this.consecutiveGrowthWindows++;
238
+ } else if (hasWarning) {
239
+ this.consecutiveGrowthWindows = Math.max(0, this.consecutiveGrowthWindows - 1);
240
+ } else {
241
+ this.consecutiveGrowthWindows = 0;
242
+ }
243
+
244
+ // Emit appropriate events
245
+ if (hasCritical) {
246
+ this.bus.emit("memory:critical", {
247
+ data: {
248
+ heapUsedMb: current.heapUsedMb,
249
+ rssMb: current.rssMb,
250
+ signals: signals.filter((s) => s.severity === "critical"),
251
+ consecutiveWindows: this.consecutiveGrowthWindows,
252
+ },
253
+ });
254
+
255
+ logger.error("Memory CRITICAL threshold reached", {
256
+ heapMb: `${current.heapUsedMb.toFixed(1)}MB`,
257
+ rssMb: `${current.rssMb.toFixed(1)}MB`,
258
+ signals: signals.map((s) => s.type),
259
+ });
260
+ } else if (hasWarning) {
261
+ this.bus.emit("memory:warning", {
262
+ data: {
263
+ heapUsedMb: current.heapUsedMb,
264
+ signals: signals.filter((s) => s.severity === "warning"),
265
+ },
266
+ });
267
+
268
+ logger.warn("Memory warning threshold reached", {
269
+ heapMb: `${current.heapUsedMb.toFixed(1)}MB`,
270
+ });
271
+ }
272
+
273
+ // Trigger proactive restart after N consecutive critical windows
274
+ const shouldRestart =
275
+ hasCritical &&
276
+ this.consecutiveGrowthWindows >= this.config.growthWindowCount &&
277
+ (Date.now() - this.lastRestartTime) > this.minRestartIntervalMs;
278
+
279
+ if (shouldRestart) {
280
+ this.triggerProactiveRestart(current, signals);
281
+ }
282
+ }
283
+
284
+ private triggerProactiveRestart(current: HeapWindow, signals: LeakSignal[]): void {
285
+ const logger = getLogger();
286
+ this.lastRestartTime = Date.now();
287
+
288
+ logger.error("MemoryLeakGuard: Triggering proactive restart before OOM", {
289
+ heapMb: `${current.heapUsedMb.toFixed(1)}MB`,
290
+ rssMb: `${current.rssMb.toFixed(1)}MB`,
291
+ consecutiveWindows: this.consecutiveGrowthWindows,
292
+ signals: signals.map((s) => `${s.type}:${s.severity}`),
293
+ });
294
+
295
+ this.bus.emit("memory:proactive-restart", {
296
+ data: {
297
+ heapUsedMb: current.heapUsedMb,
298
+ rssMb: current.rssMb,
299
+ signals,
300
+ reason: "memory-leak-guard",
301
+ },
302
+ });
303
+
304
+ // If no supervisor is listening to the restart event, force exit
305
+ // so the external process manager (PM2, K8s) handles the restart
306
+ setTimeout(() => {
307
+ logger.error("MemoryLeakGuard: No restart handler responded — forcing process exit");
308
+ process.exit(1);
309
+ }, 10_000).unref();
310
+ }
311
+
312
+ /**
313
+ * Simple linear regression to compute growth rate across time series
314
+ * Returns MB per hour
315
+ */
316
+ private computeGrowthRate(values: number[]): number {
317
+ const n = values.length;
318
+ if (n < 2) return 0;
319
+
320
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
321
+ for (let i = 0; i < n; i++) {
322
+ sumX += i;
323
+ sumY += values[i]!;
324
+ sumXY += i * values[i]!;
325
+ sumX2 += i * i;
326
+ }
327
+
328
+ const denominator = n * sumX2 - sumX * sumX;
329
+ if (denominator === 0) return 0;
330
+
331
+ // slope = MB per interval
332
+ const slope = (n * sumXY - sumX * sumY) / denominator;
333
+
334
+ // Convert to MB per hour based on check interval
335
+ const intervalsPerHour = 3_600_000 / this.config.checkIntervalMs;
336
+ const ratePerHour = slope * intervalsPerHour;
337
+
338
+ return isFinite(ratePerHour) ? ratePerHour : 0;
339
+ }
340
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * @file MetricsCollector.ts
3
+ * @description Central metrics aggregation hub
4
+ * Collects, stores, and exposes system-wide metrics as a coherent snapshot
5
+ */
6
+
7
+ import * as v8 from "node:v8";
8
+ import * as process from "node:process";
9
+ import type {
10
+ SystemMetrics,
11
+ CircuitBreakerStatus,
12
+ BulkheadStatus,
13
+ WorkerStatus,
14
+ } from "../types.js";
15
+
16
+ export interface RequestMetrics {
17
+ total: number;
18
+ active: number;
19
+ errors: number;
20
+ timestamps: number[]; // for per-second calculation
21
+ }
22
+
23
+ export interface EventLoopMetrics {
24
+ p50: number;
25
+ p95: number;
26
+ p99: number;
27
+ max: number;
28
+ }
29
+
30
+ export interface CpuMetrics {
31
+ percentUser: number;
32
+ percentSystem: number;
33
+ }
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // METRICS COLLECTOR
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ export class MetricsCollector {
40
+ private eventLoopMetrics: EventLoopMetrics = { p50: 0, p95: 0, p99: 0, max: 0 };
41
+ private cpuMetrics: CpuMetrics = { percentUser: 0, percentSystem: 0 };
42
+ private requestMetrics: RequestMetrics = {
43
+ total: 0,
44
+ active: 0,
45
+ errors: 0,
46
+ timestamps: [],
47
+ };
48
+ private circuits: Record<string, CircuitBreakerStatus> = {};
49
+ private bulkheads: Record<string, BulkheadStatus> = {};
50
+ private workers: WorkerStatus[] = [];
51
+ private lastCpuUsage = process.cpuUsage();
52
+ private lastCpuTime = Date.now();
53
+ private heapGrowthHistory: number[] = [];
54
+ private lastHeapMb = 0;
55
+ private gcCount = 0;
56
+ private lastGcCountTime = Date.now();
57
+
58
+ constructor() {
59
+ this.initGcTracking();
60
+ }
61
+
62
+ /** Update event loop lag metrics (called by HealthMonitor) */
63
+ updateEventLoopMetrics(metrics: EventLoopMetrics): void {
64
+ this.eventLoopMetrics = metrics;
65
+ }
66
+
67
+ /** Update CPU metrics */
68
+ updateCpuMetrics(): void {
69
+ const now = Date.now();
70
+ const elapsed = (now - this.lastCpuTime) / 1000;
71
+ const usage = process.cpuUsage(this.lastCpuUsage);
72
+
73
+ this.cpuMetrics = {
74
+ percentUser: (usage.user / 1000 / elapsed) / 100,
75
+ percentSystem: (usage.system / 1000 / elapsed) / 100,
76
+ };
77
+
78
+ this.lastCpuUsage = process.cpuUsage();
79
+ this.lastCpuTime = now;
80
+ }
81
+
82
+ /** Record a request started */
83
+ recordRequestStart(): void {
84
+ this.requestMetrics.total++;
85
+ this.requestMetrics.active++;
86
+ this.requestMetrics.timestamps.push(Date.now());
87
+
88
+ // Keep only last 60 seconds of timestamps
89
+ const cutoff = Date.now() - 60_000;
90
+ this.requestMetrics.timestamps = this.requestMetrics.timestamps.filter((t) => t > cutoff);
91
+ }
92
+
93
+ /** Record a request finished */
94
+ recordRequestEnd(wasError = false): void {
95
+ this.requestMetrics.active = Math.max(0, this.requestMetrics.active - 1);
96
+ if (wasError) this.requestMetrics.errors++;
97
+ }
98
+
99
+ /** Update circuit breaker statuses */
100
+ updateCircuits(circuits: Record<string, CircuitBreakerStatus>): void {
101
+ this.circuits = circuits;
102
+ }
103
+
104
+ /** Update bulkhead statuses */
105
+ updateBulkheads(bulkheads: Record<string, BulkheadStatus>): void {
106
+ this.bulkheads = bulkheads;
107
+ }
108
+
109
+ /** Update worker statuses */
110
+ updateWorkers(workers: WorkerStatus[]): void {
111
+ this.workers = workers;
112
+ }
113
+
114
+ /** Track heap growth for MemoryLeakGuard */
115
+ recordHeapSample(heapMb: number): number {
116
+ const growthMb = heapMb - this.lastHeapMb;
117
+ this.lastHeapMb = heapMb;
118
+
119
+ this.heapGrowthHistory.push(growthMb);
120
+ if (this.heapGrowthHistory.length > 20) {
121
+ this.heapGrowthHistory.shift();
122
+ }
123
+
124
+ return growthMb;
125
+ }
126
+
127
+ /** Get heap growth rate (MB/minute) using linear regression */
128
+ getHeapGrowthRate(): number {
129
+ if (this.heapGrowthHistory.length < 3) return 0;
130
+
131
+ const n = this.heapGrowthHistory.length;
132
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
133
+
134
+ for (let i = 0; i < n; i++) {
135
+ sumX += i;
136
+ sumY += this.heapGrowthHistory[i]!;
137
+ sumXY += i * this.heapGrowthHistory[i]!;
138
+ sumX2 += i * i;
139
+ }
140
+
141
+ const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
142
+ return isFinite(slope) ? slope * 60 : 0; // Convert to per-minute
143
+ }
144
+
145
+ /** Get GC frequency (GCs per minute) */
146
+ getGcFrequency(): number {
147
+ const elapsed = (Date.now() - this.lastGcCountTime) / 60_000;
148
+ return elapsed > 0 ? this.gcCount / elapsed : 0;
149
+ }
150
+
151
+ /**
152
+ * Get a complete system metrics snapshot
153
+ */
154
+ getSnapshot(): SystemMetrics {
155
+ const heapStats = v8.getHeapStatistics();
156
+ const heapUsedMb = heapStats.used_heap_size / 1024 / 1024;
157
+ const heapTotalMb = heapStats.heap_size_limit / 1024 / 1024;
158
+ const memUsage = process.memoryUsage();
159
+
160
+ this.updateCpuMetrics();
161
+
162
+ // Per-second request rate (last 10 seconds)
163
+ const tenSecondsAgo = Date.now() - 10_000;
164
+ const recentRequests = this.requestMetrics.timestamps.filter((t) => t > tenSecondsAgo);
165
+ const perSecond = recentRequests.length / 10;
166
+
167
+ // Error rate
168
+ const errorRate = this.requestMetrics.total > 0
169
+ ? this.requestMetrics.errors / this.requestMetrics.total
170
+ : 0;
171
+
172
+ return {
173
+ timestamp: Date.now(),
174
+ eventLoopLag: this.eventLoopMetrics,
175
+ memory: {
176
+ heapUsedMb: Math.round(heapUsedMb * 100) / 100,
177
+ heapTotalMb: Math.round(heapTotalMb * 100) / 100,
178
+ externalMb: Math.round(memUsage.external / 1024 / 1024 * 100) / 100,
179
+ rssMb: Math.round(memUsage.rss / 1024 / 1024 * 100) / 100,
180
+ heapGrowthRate: Math.round(this.getHeapGrowthRate() * 100) / 100,
181
+ gcFrequency: Math.round(this.getGcFrequency() * 10) / 10,
182
+ },
183
+ cpu: {
184
+ percentUser: Math.round(this.cpuMetrics.percentUser * 100) / 100,
185
+ percentSystem: Math.round(this.cpuMetrics.percentSystem * 100) / 100,
186
+ },
187
+ handles: {
188
+ active: (process as NodeJS.Process & { _getActiveHandles?: () => unknown[] })._getActiveHandles?.().length ?? 0,
189
+ },
190
+ requests: {
191
+ total: this.requestMetrics.total,
192
+ active: this.requestMetrics.active,
193
+ perSecond: Math.round(perSecond * 10) / 10,
194
+ errorRate: Math.round(errorRate * 1000) / 1000,
195
+ },
196
+ circuits: this.circuits,
197
+ bulkheads: this.bulkheads,
198
+ workers: this.workers,
199
+ };
200
+ }
201
+
202
+ private initGcTracking(): void {
203
+ try {
204
+ // Use PerformanceObserver for GC events (Node 18+)
205
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
206
+ const { PerformanceObserver } = require("node:perf_hooks") as typeof import("perf_hooks");
207
+ const observer = new PerformanceObserver((list) => {
208
+ for (const entry of list.getEntries()) {
209
+ if (entry.entryType === "gc") {
210
+ this.gcCount++;
211
+ }
212
+ }
213
+ });
214
+ observer.observe({ entryTypes: ["gc"] });
215
+ } catch {
216
+ // GC tracking not available in this environment
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file monitoring/index.ts
3
+ * @description Monitoring layer public exports
4
+ */
5
+
6
+ export { HealthMonitor } from "./HealthMonitor.js";
7
+ export { MemoryLeakGuard } from "./MemoryLeakGuard.js";
8
+ export { MetricsCollector } from "./MetricsCollector.js";
9
+ export type { RequestMetrics, EventLoopMetrics, CpuMetrics } from "./MetricsCollector.js";
10
+ export { DiagnosticsChannel, IMMORTAL_CHANNELS } from "./DiagnosticsChannel.js";