tywrap 0.1.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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +263 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +234 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/analyzer.d.ts +63 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +676 -0
- package/dist/core/analyzer.js.map +1 -0
- package/dist/core/discovery.d.ts +85 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +344 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/generator.d.ts +37 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +368 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/mapper.d.ts +40 -0
- package/dist/core/mapper.d.ts.map +1 -0
- package/dist/core/mapper.js +401 -0
- package/dist/core/mapper.js.map +1 -0
- package/dist/core/validation.d.ts +79 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +486 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/base.d.ts +12 -0
- package/dist/runtime/base.d.ts.map +1 -0
- package/dist/runtime/base.js +6 -0
- package/dist/runtime/base.js.map +1 -0
- package/dist/runtime/errors.d.ts +17 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +21 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/http.d.ts +22 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +79 -0
- package/dist/runtime/http.js.map +1 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/node.d.ts +48 -0
- package/dist/runtime/node.d.ts.map +1 -0
- package/dist/runtime/node.js +328 -0
- package/dist/runtime/node.js.map +1 -0
- package/dist/runtime/optimized-node.d.ts +131 -0
- package/dist/runtime/optimized-node.d.ts.map +1 -0
- package/dist/runtime/optimized-node.js +622 -0
- package/dist/runtime/optimized-node.js.map +1 -0
- package/dist/runtime/protocol.d.ts +3 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +3 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +27 -0
- package/dist/runtime/pyodide.d.ts.map +1 -0
- package/dist/runtime/pyodide.js +191 -0
- package/dist/runtime/pyodide.js.map +1 -0
- package/dist/tools/python_suite.js +173 -0
- package/dist/tools/python_suite.js.map +1 -0
- package/dist/types/index.d.ts +299 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/tywrap.d.ts +29 -0
- package/dist/tywrap.d.ts.map +1 -0
- package/dist/tywrap.js +589 -0
- package/dist/tywrap.js.map +1 -0
- package/dist/utils/bundle-optimizer.d.ts +182 -0
- package/dist/utils/bundle-optimizer.d.ts.map +1 -0
- package/dist/utils/bundle-optimizer.js +680 -0
- package/dist/utils/bundle-optimizer.js.map +1 -0
- package/dist/utils/cache.d.ts +149 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +492 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/codec.d.ts +94 -0
- package/dist/utils/codec.d.ts.map +1 -0
- package/dist/utils/codec.js +150 -0
- package/dist/utils/codec.js.map +1 -0
- package/dist/utils/logger.d.ts +42 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/memory-profiler.d.ts +123 -0
- package/dist/utils/memory-profiler.d.ts.map +1 -0
- package/dist/utils/memory-profiler.js +426 -0
- package/dist/utils/memory-profiler.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +146 -0
- package/dist/utils/parallel-processor.d.ts.map +1 -0
- package/dist/utils/parallel-processor.js +636 -0
- package/dist/utils/parallel-processor.js.map +1 -0
- package/dist/utils/python.d.ts +8 -0
- package/dist/utils/python.d.ts.map +1 -0
- package/dist/utils/python.js +56 -0
- package/dist/utils/python.js.map +1 -0
- package/dist/utils/runtime.d.ts +153 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +519 -0
- package/dist/utils/runtime.js.map +1 -0
- package/package.json +136 -0
- package/runtime/python_bridge.py +476 -0
- package/src/cli.ts +331 -0
- package/src/config/index.ts +289 -0
- package/src/core/analyzer.ts +788 -0
- package/src/core/discovery.ts +415 -0
- package/src/core/generator.ts +447 -0
- package/src/core/mapper.ts +504 -0
- package/src/core/validation.ts +693 -0
- package/src/index.ts +83 -0
- package/src/runtime/base.ts +32 -0
- package/src/runtime/errors.ts +19 -0
- package/src/runtime/http.ts +132 -0
- package/src/runtime/index.ts +24 -0
- package/src/runtime/node.ts +431 -0
- package/src/runtime/optimized-node.ts +838 -0
- package/src/runtime/protocol.ts +2 -0
- package/src/runtime/pyodide.ts +228 -0
- package/src/types/global.d.ts +60 -0
- package/src/types/index.ts +410 -0
- package/src/tywrap.ts +669 -0
- package/src/utils/bundle-optimizer.ts +876 -0
- package/src/utils/cache.ts +634 -0
- package/src/utils/codec.ts +275 -0
- package/src/utils/logger.ts +201 -0
- package/src/utils/memory-profiler.ts +582 -0
- package/src/utils/parallel-processor.ts +879 -0
- package/src/utils/python.ts +79 -0
- package/src/utils/runtime.ts +616 -0
|
@@ -0,0 +1,582 @@
|
|
|
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
|
+
});
|