tywrap 0.5.1 → 0.6.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 (155) hide show
  1. package/dist/cli.js +8 -6
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/index.js +236 -147
  5. package/dist/config/index.js.map +1 -1
  6. package/dist/core/annotation-parser.d.ts.map +1 -1
  7. package/dist/core/annotation-parser.js +93 -83
  8. package/dist/core/annotation-parser.js.map +1 -1
  9. package/dist/core/discovery.d.ts.map +1 -1
  10. package/dist/core/discovery.js +26 -0
  11. package/dist/core/discovery.js.map +1 -1
  12. package/dist/core/emit-call.d.ts +58 -0
  13. package/dist/core/emit-call.d.ts.map +1 -0
  14. package/dist/core/emit-call.js +103 -0
  15. package/dist/core/emit-call.js.map +1 -0
  16. package/dist/core/generator.d.ts +2 -0
  17. package/dist/core/generator.d.ts.map +1 -1
  18. package/dist/core/generator.js +54 -211
  19. package/dist/core/generator.js.map +1 -1
  20. package/dist/core/mapper.d.ts.map +1 -1
  21. package/dist/core/mapper.js +118 -104
  22. package/dist/core/mapper.js.map +1 -1
  23. package/dist/index.d.ts +6 -25
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +9 -31
  26. package/dist/index.js.map +1 -1
  27. package/dist/runtime/bounded-context.d.ts +2 -2
  28. package/dist/runtime/bounded-context.js +2 -2
  29. package/dist/runtime/{safe-codec.d.ts → bridge-codec.d.ts} +11 -11
  30. package/dist/runtime/bridge-codec.d.ts.map +1 -0
  31. package/dist/runtime/{safe-codec.js → bridge-codec.js} +26 -26
  32. package/dist/runtime/bridge-codec.js.map +1 -0
  33. package/dist/runtime/{http-io.d.ts → http-transport.d.ts} +6 -6
  34. package/dist/runtime/http-transport.d.ts.map +1 -0
  35. package/dist/runtime/{http-io.js → http-transport.js} +3 -3
  36. package/dist/runtime/http-transport.js.map +1 -0
  37. package/dist/runtime/http.d.ts +3 -3
  38. package/dist/runtime/http.d.ts.map +1 -1
  39. package/dist/runtime/http.js +4 -4
  40. package/dist/runtime/http.js.map +1 -1
  41. package/dist/runtime/index.d.ts +9 -1
  42. package/dist/runtime/index.d.ts.map +1 -1
  43. package/dist/runtime/index.js +9 -1
  44. package/dist/runtime/index.js.map +1 -1
  45. package/dist/runtime/node.d.ts +6 -6
  46. package/dist/runtime/node.d.ts.map +1 -1
  47. package/dist/runtime/node.js +7 -7
  48. package/dist/runtime/node.js.map +1 -1
  49. package/dist/runtime/pooled-transport.d.ts +11 -11
  50. package/dist/runtime/pooled-transport.d.ts.map +1 -1
  51. package/dist/runtime/pooled-transport.js +10 -10
  52. package/dist/runtime/pooled-transport.js.map +1 -1
  53. package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
  54. package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
  55. package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
  56. package/dist/runtime/{pyodide-io.d.ts → pyodide-transport.d.ts} +8 -8
  57. package/dist/runtime/pyodide-transport.d.ts.map +1 -0
  58. package/dist/runtime/{pyodide-io.js → pyodide-transport.js} +5 -5
  59. package/dist/runtime/pyodide-transport.js.map +1 -0
  60. package/dist/runtime/pyodide.d.ts +3 -3
  61. package/dist/runtime/pyodide.d.ts.map +1 -1
  62. package/dist/runtime/pyodide.js +5 -5
  63. package/dist/runtime/pyodide.js.map +1 -1
  64. package/dist/runtime/rpc-client.d.ts +5 -5
  65. package/dist/runtime/rpc-client.d.ts.map +1 -1
  66. package/dist/runtime/rpc-client.js +6 -7
  67. package/dist/runtime/rpc-client.js.map +1 -1
  68. package/dist/runtime/{process-io.d.ts → subprocess-transport.d.ts} +9 -9
  69. package/dist/runtime/subprocess-transport.d.ts.map +1 -0
  70. package/dist/runtime/{process-io.js → subprocess-transport.js} +6 -6
  71. package/dist/runtime/subprocess-transport.js.map +1 -0
  72. package/dist/runtime/{worker-pool.d.ts → transport-pool.d.ts} +15 -15
  73. package/dist/runtime/transport-pool.d.ts.map +1 -0
  74. package/dist/runtime/{worker-pool.js → transport-pool.js} +6 -6
  75. package/dist/runtime/transport-pool.js.map +1 -0
  76. package/dist/runtime/transport.d.ts +14 -8
  77. package/dist/runtime/transport.d.ts.map +1 -1
  78. package/dist/runtime/transport.js +10 -4
  79. package/dist/runtime/transport.js.map +1 -1
  80. package/dist/types/index.d.ts +10 -1
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/utils/cache.d.ts +2 -2
  83. package/dist/utils/cache.d.ts.map +1 -1
  84. package/dist/utils/cache.js +2 -2
  85. package/dist/utils/cache.js.map +1 -1
  86. package/dist/utils/codec.d.ts +1 -1
  87. package/dist/utils/codec.d.ts.map +1 -1
  88. package/dist/utils/codec.js +181 -182
  89. package/dist/utils/codec.js.map +1 -1
  90. package/dist/version.d.ts +12 -0
  91. package/dist/version.d.ts.map +1 -0
  92. package/dist/version.js +12 -0
  93. package/dist/version.js.map +1 -0
  94. package/package.json +3 -2
  95. package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
  96. package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
  97. package/runtime/python_bridge.py +54 -6
  98. package/runtime/safe_codec.py +8 -8
  99. package/runtime/tywrap_bridge_core.py +159 -23
  100. package/src/cli.ts +8 -12
  101. package/src/config/index.ts +271 -165
  102. package/src/core/annotation-parser.ts +112 -105
  103. package/src/core/discovery.ts +32 -0
  104. package/src/core/emit-call.ts +171 -0
  105. package/src/core/generator.ts +67 -263
  106. package/src/core/mapper.ts +125 -110
  107. package/src/index.ts +11 -68
  108. package/src/runtime/bounded-context.ts +2 -2
  109. package/src/runtime/{safe-codec.ts → bridge-codec.ts} +27 -27
  110. package/src/runtime/{http-io.ts → http-transport.ts} +5 -5
  111. package/src/runtime/http.ts +5 -5
  112. package/src/runtime/index.ts +23 -1
  113. package/src/runtime/node.ts +15 -15
  114. package/src/runtime/pooled-transport.ts +15 -15
  115. package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
  116. package/src/runtime/{pyodide-io.ts → pyodide-transport.ts} +7 -7
  117. package/src/runtime/pyodide.ts +6 -6
  118. package/src/runtime/rpc-client.ts +12 -8
  119. package/src/runtime/{process-io.ts → subprocess-transport.ts} +8 -8
  120. package/src/runtime/{worker-pool.ts → transport-pool.ts} +26 -26
  121. package/src/runtime/transport.ts +15 -8
  122. package/src/types/index.ts +10 -1
  123. package/src/utils/cache.ts +2 -2
  124. package/src/utils/codec.ts +217 -196
  125. package/src/version.ts +12 -0
  126. package/dist/runtime/http-io.d.ts.map +0 -1
  127. package/dist/runtime/http-io.js.map +0 -1
  128. package/dist/runtime/optimized-node.d.ts +0 -23
  129. package/dist/runtime/optimized-node.d.ts.map +0 -1
  130. package/dist/runtime/optimized-node.js +0 -23
  131. package/dist/runtime/optimized-node.js.map +0 -1
  132. package/dist/runtime/process-io.d.ts.map +0 -1
  133. package/dist/runtime/process-io.js.map +0 -1
  134. package/dist/runtime/protocol.d.ts +0 -3
  135. package/dist/runtime/protocol.d.ts.map +0 -1
  136. package/dist/runtime/protocol.js +0 -3
  137. package/dist/runtime/protocol.js.map +0 -1
  138. package/dist/runtime/pyodide-io.d.ts.map +0 -1
  139. package/dist/runtime/pyodide-io.js.map +0 -1
  140. package/dist/runtime/safe-codec.d.ts.map +0 -1
  141. package/dist/runtime/safe-codec.js.map +0 -1
  142. package/dist/runtime/worker-pool.d.ts.map +0 -1
  143. package/dist/runtime/worker-pool.js.map +0 -1
  144. package/dist/utils/bundle-optimizer.d.ts +0 -182
  145. package/dist/utils/bundle-optimizer.d.ts.map +0 -1
  146. package/dist/utils/bundle-optimizer.js +0 -680
  147. package/dist/utils/bundle-optimizer.js.map +0 -1
  148. package/dist/utils/memory-profiler.d.ts +0 -123
  149. package/dist/utils/memory-profiler.d.ts.map +0 -1
  150. package/dist/utils/memory-profiler.js +0 -426
  151. package/dist/utils/memory-profiler.js.map +0 -1
  152. package/src/runtime/optimized-node.ts +0 -25
  153. package/src/runtime/protocol.ts +0 -2
  154. package/src/utils/bundle-optimizer.ts +0 -876
  155. package/src/utils/memory-profiler.ts +0 -582
@@ -1,582 +0,0 @@
1
- /**
2
- * Memory Profiler and Leak Detection for tywrap
3
- * Comprehensive memory monitoring and analysis tools
4
- */
5
-
6
- import { EventEmitter } from 'events';
7
- import { writeFileSync } from 'fs';
8
- import type { PerformanceEntry, PerformanceObserverEntryList } from 'node:perf_hooks';
9
- import { getComponentLogger } from './logger.js';
10
-
11
- const log = getComponentLogger('MemoryProfiler');
12
-
13
- export interface MemorySnapshot {
14
- timestamp: number;
15
- heapUsed: number;
16
- heapTotal: number;
17
- external: number;
18
- rss: number;
19
- arrayBuffers: number;
20
- gcCount: number;
21
- operation?: string;
22
- metadata?: Record<string, unknown>;
23
- }
24
-
25
- export interface LeakAnalysis {
26
- detected: boolean;
27
- severity: 'low' | 'medium' | 'high' | 'critical';
28
- growthRate: number; // bytes per second
29
- suspiciousOperations: string[];
30
- recommendations: string[];
31
- snapshots: MemorySnapshot[];
32
- }
33
-
34
- export interface MemoryReport {
35
- summary: {
36
- duration: number;
37
- totalOperations: number;
38
- peakMemory: number;
39
- averageMemory: number;
40
- memoryGrowth: number;
41
- gcEfficiency: number;
42
- };
43
- leakAnalysis: LeakAnalysis;
44
- operationMetrics: Map<
45
- string,
46
- {
47
- count: number;
48
- averageMemoryDelta: number;
49
- maxMemoryDelta: number;
50
- totalMemoryAllocated: number;
51
- }
52
- >;
53
- recommendations: string[];
54
- }
55
-
56
- export class MemoryProfiler extends EventEmitter {
57
- private snapshots: MemorySnapshot[] = [];
58
- private operationMetrics = new Map<
59
- string,
60
- {
61
- count: number;
62
- averageMemoryDelta: number;
63
- maxMemoryDelta: number;
64
- totalMemoryAllocated: number;
65
- }
66
- >();
67
- private monitoring = false;
68
- private monitoringInterval?: NodeJS.Timeout;
69
- private baselineSnapshot?: MemorySnapshot;
70
- private gcCount = 0;
71
- private options: {
72
- snapshotInterval: number;
73
- maxSnapshots: number;
74
- leakThreshold: number; // bytes per second growth rate
75
- enableGCTracking: boolean;
76
- };
77
-
78
- constructor(options: Partial<typeof MemoryProfiler.prototype.options> = {}) {
79
- super();
80
- this.options = {
81
- snapshotInterval: 5000, // 5 seconds
82
- maxSnapshots: 100,
83
- leakThreshold: 1024 * 1024, // 1MB per second
84
- enableGCTracking: true,
85
- ...options,
86
- };
87
-
88
- if (this.options.enableGCTracking) {
89
- this.setupGCTracking();
90
- }
91
- }
92
-
93
- private logInfo(message: string): void {
94
- process.stdout.write(`${message}\n`);
95
- }
96
-
97
- /**
98
- * Start memory monitoring
99
- */
100
- startMonitoring(): void {
101
- if (this.monitoring) {
102
- return;
103
- }
104
-
105
- this.monitoring = true;
106
- this.baselineSnapshot = this.takeSnapshot('monitoring_start');
107
-
108
- this.monitoringInterval = setInterval(() => {
109
- const snapshot = this.takeSnapshot('periodic');
110
- this.emit('snapshot', snapshot);
111
-
112
- // Check for immediate leak concerns
113
- const analysis = this.analyzeLeaks();
114
- if (analysis.detected && analysis.severity === 'critical') {
115
- this.emit('critical_leak', analysis);
116
- }
117
- }, this.options.snapshotInterval);
118
-
119
- this.logInfo('Memory monitoring started');
120
- }
121
-
122
- /**
123
- * Stop memory monitoring
124
- */
125
- stopMonitoring(): void {
126
- if (!this.monitoring) {
127
- return;
128
- }
129
-
130
- this.monitoring = false;
131
-
132
- if (this.monitoringInterval) {
133
- clearInterval(this.monitoringInterval);
134
- this.monitoringInterval = undefined;
135
- }
136
-
137
- this.takeSnapshot('monitoring_stop');
138
- this.emit('monitoring_stopped', this.generateReport());
139
-
140
- this.logInfo('Memory monitoring stopped');
141
- }
142
-
143
- /**
144
- * Take a memory snapshot
145
- */
146
- takeSnapshot(operation?: string, metadata?: Record<string, unknown>): MemorySnapshot {
147
- const memUsage = process.memoryUsage();
148
-
149
- const snapshot: MemorySnapshot = {
150
- timestamp: Date.now(),
151
- heapUsed: memUsage.heapUsed,
152
- heapTotal: memUsage.heapTotal,
153
- external: memUsage.external,
154
- rss: memUsage.rss,
155
- arrayBuffers: memUsage.arrayBuffers,
156
- gcCount: this.gcCount,
157
- operation,
158
- metadata,
159
- };
160
-
161
- this.snapshots.push(snapshot);
162
-
163
- // Keep only recent snapshots
164
- if (this.snapshots.length > this.options.maxSnapshots) {
165
- this.snapshots.shift();
166
- }
167
-
168
- return snapshot;
169
- }
170
-
171
- /**
172
- * Profile a specific operation
173
- */
174
- async profileOperation<T>(
175
- operationName: string,
176
- operation: () => Promise<T> | T,
177
- metadata?: Record<string, unknown>
178
- ): Promise<{ result: T; memoryDelta: number; duration: number }> {
179
- // Force garbage collection if available
180
- if (global.gc) {
181
- global.gc();
182
- await this.sleep(100); // Let GC settle
183
- }
184
-
185
- const beforeSnapshot = this.takeSnapshot(`${operationName}_start`, metadata);
186
- const startTime = performance.now();
187
-
188
- try {
189
- const result = await operation();
190
- const endTime = performance.now();
191
-
192
- // Force GC again to get accurate measurement
193
- if (global.gc) {
194
- global.gc();
195
- await this.sleep(100);
196
- }
197
-
198
- const afterSnapshot = this.takeSnapshot(`${operationName}_end`, metadata);
199
-
200
- const memoryDelta = afterSnapshot.heapUsed - beforeSnapshot.heapUsed;
201
- const duration = endTime - startTime;
202
-
203
- // Update operation metrics
204
- const existing = this.operationMetrics.get(operationName) ?? {
205
- count: 0,
206
- averageMemoryDelta: 0,
207
- maxMemoryDelta: 0,
208
- totalMemoryAllocated: 0,
209
- };
210
-
211
- existing.count++;
212
- existing.totalMemoryAllocated += Math.max(0, memoryDelta);
213
- existing.maxMemoryDelta = Math.max(existing.maxMemoryDelta, memoryDelta);
214
- existing.averageMemoryDelta = existing.totalMemoryAllocated / existing.count;
215
-
216
- this.operationMetrics.set(operationName, existing);
217
-
218
- this.logInfo(
219
- `${operationName}: ${duration.toFixed(2)}ms, ${this.formatBytes(memoryDelta)} memory delta`
220
- );
221
-
222
- return { result, memoryDelta, duration };
223
- } catch (error) {
224
- this.takeSnapshot(`${operationName}_error`, { ...metadata, error: String(error) });
225
- throw error;
226
- }
227
- }
228
-
229
- /**
230
- * Analyze potential memory leaks
231
- */
232
- analyzeLeaks(): LeakAnalysis {
233
- if (this.snapshots.length < 3) {
234
- return {
235
- detected: false,
236
- severity: 'low',
237
- growthRate: 0,
238
- suspiciousOperations: [],
239
- recommendations: [],
240
- snapshots: this.snapshots,
241
- };
242
- }
243
-
244
- const recentSnapshots = this.snapshots.slice(-10); // Last 10 snapshots
245
- const lastSnapshot = recentSnapshots[recentSnapshots.length - 1];
246
- const firstSnapshot = recentSnapshots[0];
247
-
248
- if (!lastSnapshot || !firstSnapshot) {
249
- return {
250
- detected: false,
251
- severity: 'low',
252
- growthRate: 0,
253
- suspiciousOperations: [],
254
- recommendations: [],
255
- snapshots: this.snapshots,
256
- };
257
- }
258
-
259
- const timeSpan = lastSnapshot.timestamp - firstSnapshot.timestamp;
260
-
261
- if (timeSpan === 0) {
262
- return {
263
- detected: false,
264
- severity: 'low',
265
- growthRate: 0,
266
- suspiciousOperations: [],
267
- recommendations: [],
268
- snapshots: this.snapshots,
269
- };
270
- }
271
-
272
- const memoryGrowth = lastSnapshot.heapUsed - firstSnapshot.heapUsed;
273
- const growthRate = (memoryGrowth / timeSpan) * 1000; // bytes per second
274
-
275
- // Detect leak severity
276
- let detected = false;
277
- let severity: LeakAnalysis['severity'] = 'low';
278
-
279
- if (growthRate > this.options.leakThreshold) {
280
- detected = true;
281
- if (growthRate > this.options.leakThreshold * 5) {
282
- severity = 'critical';
283
- } else if (growthRate > this.options.leakThreshold * 3) {
284
- severity = 'high';
285
- } else if (growthRate > this.options.leakThreshold * 1.5) {
286
- severity = 'medium';
287
- }
288
- }
289
-
290
- // Find suspicious operations
291
- const suspiciousOperations = this.identifySuspiciousOperations();
292
-
293
- // Generate recommendations
294
- const recommendations = this.generateRecommendations(growthRate, suspiciousOperations);
295
-
296
- return {
297
- detected,
298
- severity,
299
- growthRate,
300
- suspiciousOperations,
301
- recommendations,
302
- snapshots: recentSnapshots,
303
- };
304
- }
305
-
306
- /**
307
- * Identify operations that may be causing memory leaks
308
- */
309
- private identifySuspiciousOperations(): string[] {
310
- const suspicious: string[] = [];
311
-
312
- for (const [operation, metrics] of this.operationMetrics) {
313
- // High average memory usage
314
- if (metrics.averageMemoryDelta > 10 * 1024 * 1024) {
315
- // 10MB average
316
- suspicious.push(
317
- `${operation}: High average memory usage (${this.formatBytes(metrics.averageMemoryDelta)})`
318
- );
319
- }
320
-
321
- // Memory growth without proper cleanup
322
- if (metrics.count > 5 && metrics.averageMemoryDelta > 1024 * 1024) {
323
- // 1MB average over 5 calls
324
- suspicious.push(`${operation}: Potential memory accumulation`);
325
- }
326
-
327
- // Very large single allocation
328
- if (metrics.maxMemoryDelta > 50 * 1024 * 1024) {
329
- // 50MB single allocation
330
- suspicious.push(
331
- `${operation}: Very large memory allocation (${this.formatBytes(metrics.maxMemoryDelta)})`
332
- );
333
- }
334
- }
335
-
336
- return suspicious;
337
- }
338
-
339
- /**
340
- * Generate recommendations based on analysis
341
- */
342
- private generateRecommendations(growthRate: number, suspiciousOperations: string[]): string[] {
343
- const recommendations: string[] = [];
344
-
345
- if (growthRate > this.options.leakThreshold) {
346
- recommendations.push('Memory leak detected - investigate resource cleanup in your code');
347
-
348
- if (suspiciousOperations.length > 0) {
349
- recommendations.push(
350
- `Focus on these operations: ${suspiciousOperations.slice(0, 3).join(', ')}`
351
- );
352
- }
353
-
354
- if (growthRate > this.options.leakThreshold * 5) {
355
- recommendations.push('CRITICAL: Memory growing very rapidly - immediate action required');
356
- recommendations.push('Consider restarting the process if growth continues');
357
- }
358
-
359
- recommendations.push('Enable garbage collection with --expose-gc for better measurements');
360
- recommendations.push('Review cache expiration policies');
361
- recommendations.push('Check for retained references to large objects');
362
- recommendations.push('Monitor subprocess memory usage in Python bridge');
363
- }
364
-
365
- // General performance recommendations
366
- if (this.operationMetrics.size > 0) {
367
- const avgMemoryPerOp =
368
- Array.from(this.operationMetrics.values()).reduce(
369
- (sum, m) => sum + m.averageMemoryDelta,
370
- 0
371
- ) / this.operationMetrics.size;
372
-
373
- if (avgMemoryPerOp > 5 * 1024 * 1024) {
374
- // 5MB average
375
- recommendations.push('Consider optimizing data structures for lower memory usage');
376
- recommendations.push('Implement streaming for large data processing');
377
- }
378
- }
379
-
380
- return recommendations;
381
- }
382
-
383
- /**
384
- * Generate comprehensive memory report
385
- */
386
- generateReport(): MemoryReport {
387
- if (this.snapshots.length === 0) {
388
- throw new Error('No memory snapshots available for report');
389
- }
390
-
391
- const firstSnapshot = this.snapshots[0];
392
- const lastSnapshot = this.snapshots[this.snapshots.length - 1];
393
-
394
- if (!firstSnapshot || !lastSnapshot) {
395
- throw new Error('Invalid snapshot data for report generation');
396
- }
397
-
398
- const duration = lastSnapshot.timestamp - firstSnapshot.timestamp;
399
-
400
- const heapValues = this.snapshots.map(s => s.heapUsed);
401
- const peakMemory = Math.max(...heapValues);
402
- const averageMemory = heapValues.reduce((sum, val) => sum + val, 0) / heapValues.length;
403
- const memoryGrowth = lastSnapshot.heapUsed - firstSnapshot.heapUsed;
404
-
405
- // Calculate GC efficiency
406
- const gcEvents = this.snapshots.filter((s, i) => {
407
- const prevSnapshot = this.snapshots[i - 1];
408
- return i > 0 && prevSnapshot && s.gcCount > prevSnapshot.gcCount;
409
- });
410
- const gcEfficiency =
411
- gcEvents.length > 0
412
- ? gcEvents.reduce((sum, s) => {
413
- const prev = this.snapshots[this.snapshots.indexOf(s) - 1];
414
- const memoryReclaimed = prev ? prev.heapUsed - s.heapUsed : 0;
415
- return sum + Math.max(0, memoryReclaimed);
416
- }, 0) / gcEvents.length
417
- : 0;
418
-
419
- const summary = {
420
- duration,
421
- totalOperations: Array.from(this.operationMetrics.values()).reduce(
422
- (sum, m) => sum + m.count,
423
- 0
424
- ),
425
- peakMemory,
426
- averageMemory,
427
- memoryGrowth,
428
- gcEfficiency,
429
- };
430
-
431
- const leakAnalysis = this.analyzeLeaks();
432
-
433
- // Generate overall recommendations
434
- const recommendations: string[] = [];
435
- if (summary.peakMemory > 500 * 1024 * 1024) {
436
- // 500MB peak
437
- recommendations.push(
438
- 'High peak memory usage detected - consider memory optimization strategies'
439
- );
440
- }
441
- if (summary.memoryGrowth > 100 * 1024 * 1024) {
442
- // 100MB growth
443
- recommendations.push('Significant memory growth over session - review for potential leaks');
444
- }
445
- if (summary.gcEfficiency < 1024 * 1024) {
446
- // Less than 1MB reclaimed per GC
447
- recommendations.push(
448
- 'Low GC efficiency - may indicate memory fragmentation or retained references'
449
- );
450
- }
451
-
452
- return {
453
- summary,
454
- leakAnalysis,
455
- operationMetrics: this.operationMetrics,
456
- recommendations: [...recommendations, ...leakAnalysis.recommendations],
457
- };
458
- }
459
-
460
- /**
461
- * Save memory report to file
462
- */
463
- saveReport(filePath: string): MemoryReport {
464
- const report = this.generateReport();
465
-
466
- const reportData = {
467
- timestamp: new Date().toISOString(),
468
- report,
469
- snapshots: this.snapshots,
470
- };
471
-
472
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- caller controls report path
473
- writeFileSync(filePath, JSON.stringify(reportData, null, 2));
474
- this.logInfo(`Memory report saved to ${filePath}`);
475
-
476
- return report;
477
- }
478
-
479
- /**
480
- * Setup garbage collection tracking
481
- */
482
- private setupGCTracking(): void {
483
- if (typeof global.gc !== 'function') {
484
- log.warn('GC tracking unavailable - run with --expose-gc for better memory analysis');
485
- return;
486
- }
487
-
488
- // Hook into GC events if available
489
- try {
490
- if (process.versions.node >= '14.0.0') {
491
- const perfHooks = require('perf_hooks') as typeof import('node:perf_hooks');
492
- type GcPerformanceEntry = PerformanceEntry & { kind: number };
493
- const obs = new perfHooks.PerformanceObserver((list: PerformanceObserverEntryList) => {
494
- const entries = list.getEntries() as GcPerformanceEntry[];
495
- entries.forEach(entry => {
496
- if (entry.entryType === 'gc') {
497
- this.gcCount++;
498
- this.emit('gc', {
499
- type: entry.kind,
500
- duration: entry.duration,
501
- timestamp: Date.now(),
502
- });
503
- }
504
- });
505
- });
506
- obs.observe({ entryTypes: ['gc'] });
507
- }
508
- } catch (error) {
509
- log.warn('Advanced GC tracking unavailable', { error: String(error) });
510
- }
511
- }
512
-
513
- /**
514
- * Format bytes for human readable output
515
- */
516
- private formatBytes(bytes: number): string {
517
- if (bytes === 0) {
518
- return '0 B';
519
- }
520
- const k = 1024;
521
- const sizes = ['B', 'KB', 'MB', 'GB'];
522
- const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
523
- const size = bytes / Math.pow(k, i);
524
- const sign = bytes < 0 ? '-' : '+';
525
- const sizeLabel = i === 0 ? sizes[0] : i === 1 ? sizes[1] : i === 2 ? sizes[2] : sizes[3];
526
- return `${sign}${size.toFixed(1)} ${sizeLabel}`;
527
- }
528
-
529
- /**
530
- * Utility sleep function
531
- */
532
- private sleep(ms: number): Promise<void> {
533
- return new Promise(resolve => setTimeout(resolve, ms));
534
- }
535
-
536
- /**
537
- * Clear all collected data
538
- */
539
- clear(): void {
540
- this.snapshots.length = 0;
541
- this.operationMetrics.clear();
542
- this.gcCount = 0;
543
- this.baselineSnapshot = undefined;
544
- }
545
-
546
- /**
547
- * Get current memory usage
548
- */
549
- getCurrentMemoryUsage(): MemorySnapshot {
550
- return this.takeSnapshot('current');
551
- }
552
-
553
- /**
554
- * Check if profiler has snapshots
555
- */
556
- hasSnapshots(): boolean {
557
- return this.snapshots.length > 0;
558
- }
559
-
560
- /**
561
- * Dispose profiler
562
- */
563
- dispose(): void {
564
- this.stopMonitoring();
565
- this.clear();
566
- this.removeAllListeners();
567
- }
568
- }
569
-
570
- // Export singleton instance for global use
571
- export const globalMemoryProfiler = new MemoryProfiler();
572
-
573
- // Automatic profiling for process exit
574
- process.on('exit', () => {
575
- if (globalMemoryProfiler.hasSnapshots()) {
576
- try {
577
- globalMemoryProfiler.saveReport('memory-profile-exit.json');
578
- } catch (error) {
579
- log.error('Failed to save exit memory report', { error: String(error) });
580
- }
581
- }
582
- });