@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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/.ragrc.schema.json +218 -0
- package/README.md +292 -32
- package/bin/cli.js +8 -14
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +17 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +611 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +265 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +903 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +85 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/structured-logger.js +361 -0
- package/src/utils/validate-plugin-contract.js +42 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Profiler
|
|
3
|
+
*
|
|
4
|
+
* Detailed bottleneck analysis and performance monitoring for RAG pipelines.
|
|
5
|
+
* Provides comprehensive metrics, flame graphs, and optimization recommendations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require("events");
|
|
9
|
+
// eslint-disable-line global-require
|
|
10
|
+
const fs = require("fs").promises;
|
|
11
|
+
// eslint-disable-line global-require
|
|
12
|
+
const path = require("path");
|
|
13
|
+
// eslint-disable-line global-require
|
|
14
|
+
|
|
15
|
+
class PerformanceProfiler extends EventEmitter {
|
|
16
|
+
constructor(_options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this._options = {
|
|
20
|
+
enableCPUProfiling: _options.enableCPUProfiling !== false,
|
|
21
|
+
enableMemoryProfiling: _options.enableMemoryProfiling !== false,
|
|
22
|
+
enableNetworkProfiling: _options.enableNetworkProfiling !== false,
|
|
23
|
+
sampleInterval: _options.sampleInterval || 100, // ms
|
|
24
|
+
maxSamples: _options.maxSamples || 10000,
|
|
25
|
+
outputDir: _options.outputDir || "./profiling-reports",
|
|
26
|
+
..._options,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
this.profiles = new Map();
|
|
30
|
+
this.currentProfile = null;
|
|
31
|
+
this.samples = [];
|
|
32
|
+
this.metrics = new Map();
|
|
33
|
+
this.isProfileing = false;
|
|
34
|
+
|
|
35
|
+
this.initializeProfiler();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize profiler components
|
|
40
|
+
*/
|
|
41
|
+
initializeProfiler() {
|
|
42
|
+
this.baselineMetrics = {
|
|
43
|
+
memory: process.memoryUsage(),
|
|
44
|
+
cpu: process.cpuUsage(),
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.performanceCounters = {
|
|
49
|
+
totalExecutions: 0,
|
|
50
|
+
totalDuration: 0,
|
|
51
|
+
averageDuration: 0,
|
|
52
|
+
minDuration: Infinity,
|
|
53
|
+
maxDuration: 0,
|
|
54
|
+
errorCount: 0,
|
|
55
|
+
successCount: 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Start profiling session
|
|
61
|
+
*/
|
|
62
|
+
startProfiling(sessionId, _config = {}) {
|
|
63
|
+
if (this.isProfileing) {
|
|
64
|
+
throw new Error("Profiling session already active");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const profile = {
|
|
68
|
+
id: sessionId,
|
|
69
|
+
_config,
|
|
70
|
+
startTime: Date.now(),
|
|
71
|
+
endTime: null,
|
|
72
|
+
samples: [],
|
|
73
|
+
metrics: new Map(),
|
|
74
|
+
components: new Map(),
|
|
75
|
+
flamegraph: null,
|
|
76
|
+
recommendations: [],
|
|
77
|
+
status: "active",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this.profiles.set(sessionId, profile);
|
|
81
|
+
this.currentProfile = profile;
|
|
82
|
+
this.isProfileing = true;
|
|
83
|
+
this.samples = [];
|
|
84
|
+
|
|
85
|
+
// Start sampling if enabled
|
|
86
|
+
if (
|
|
87
|
+
this._options.enableCPUProfiling ||
|
|
88
|
+
this._options.enableMemoryProfiling
|
|
89
|
+
) {
|
|
90
|
+
this.startSampling();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.emit("profilingStarted", { sessionId, profile });
|
|
94
|
+
|
|
95
|
+
return sessionId;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Start performance sampling
|
|
100
|
+
*/
|
|
101
|
+
startSampling() {
|
|
102
|
+
this.samplingInterval = setInterval(() => {
|
|
103
|
+
if (
|
|
104
|
+
!this.isProfileing ||
|
|
105
|
+
this.samples.length >= this._options.maxSamples
|
|
106
|
+
) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sample = this.collectSample();
|
|
111
|
+
this.samples.push(sample);
|
|
112
|
+
this.currentProfile.samples.push(sample);
|
|
113
|
+
}, this._options.sampleInterval);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Collect performance sample
|
|
118
|
+
*/
|
|
119
|
+
collectSample() {
|
|
120
|
+
const timestamp = Date.now();
|
|
121
|
+
const sample = {
|
|
122
|
+
timestamp,
|
|
123
|
+
relativeTime: timestamp - this.currentProfile.startTime,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// CPU metrics
|
|
127
|
+
if (this._options.enableCPUProfiling) {
|
|
128
|
+
const cpuUsage = process.cpuUsage();
|
|
129
|
+
sample.cpu = {
|
|
130
|
+
user: cpuUsage.user,
|
|
131
|
+
system: cpuUsage.system,
|
|
132
|
+
userDelta: cpuUsage.user - this.baselineMetrics.cpu.user,
|
|
133
|
+
systemDelta: cpuUsage.system - this.baselineMetrics.cpu.system,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Memory metrics
|
|
138
|
+
if (this._options.enableMemoryProfiling) {
|
|
139
|
+
const memoryUsage = process.memoryUsage();
|
|
140
|
+
sample.memory = {
|
|
141
|
+
rss: memoryUsage.rss,
|
|
142
|
+
heapTotal: memoryUsage.heapTotal,
|
|
143
|
+
heapUsed: memoryUsage.heapUsed,
|
|
144
|
+
external: memoryUsage.external,
|
|
145
|
+
arrayBuffers: memoryUsage.arrayBuffers,
|
|
146
|
+
heapUsedDelta:
|
|
147
|
+
memoryUsage.heapUsed - this.baselineMetrics.memory.heapUsed,
|
|
148
|
+
rssDelta: memoryUsage.rss - this.baselineMetrics.memory.rss,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Event loop lag
|
|
153
|
+
const start = process.hrtime.bigint();
|
|
154
|
+
setImmediate(() => {
|
|
155
|
+
const lag = Number(process.hrtime.bigint() - start) / 1000000; // Convert to ms
|
|
156
|
+
sample.eventLoopLag = lag;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return sample;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Profile component execution
|
|
164
|
+
*/
|
|
165
|
+
async profileComponent(
|
|
166
|
+
componentId,
|
|
167
|
+
componentType,
|
|
168
|
+
executionFunc,
|
|
169
|
+
input = null,
|
|
170
|
+
) {
|
|
171
|
+
if (!this.isProfileing) {
|
|
172
|
+
throw new Error("No active profiling session");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const componentProfile = this._createComponentProfile(
|
|
176
|
+
componentId,
|
|
177
|
+
componentType,
|
|
178
|
+
);
|
|
179
|
+
const execution = this._createExecution(input);
|
|
180
|
+
|
|
181
|
+
componentProfile.executions.push(execution);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const result = await this._executeWithTiming(executionFunc, execution);
|
|
185
|
+
this._updateSuccessMetrics(componentProfile, execution);
|
|
186
|
+
this._emitProfiledEvent(componentId, execution.id, execution, null, true);
|
|
187
|
+
return result;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
this._handleExecutionError(componentProfile, execution, error);
|
|
190
|
+
this._emitProfiledEvent(
|
|
191
|
+
componentId,
|
|
192
|
+
execution.id,
|
|
193
|
+
execution,
|
|
194
|
+
error.message,
|
|
195
|
+
false,
|
|
196
|
+
);
|
|
197
|
+
throw error;
|
|
198
|
+
} finally {
|
|
199
|
+
this.currentProfile.components.set(componentId, componentProfile);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_createComponentProfile(componentId, componentType) {
|
|
204
|
+
return {
|
|
205
|
+
id: componentId,
|
|
206
|
+
_type: componentType,
|
|
207
|
+
executions: [],
|
|
208
|
+
totalDuration: 0,
|
|
209
|
+
averageDuration: 0,
|
|
210
|
+
minDuration: Infinity,
|
|
211
|
+
maxDuration: 0,
|
|
212
|
+
errorCount: 0,
|
|
213
|
+
successCount: 0,
|
|
214
|
+
memoryPeak: 0,
|
|
215
|
+
cpuUsage: { user: 0, system: 0 },
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_createExecution(input) {
|
|
220
|
+
return {
|
|
221
|
+
id: `exec_${Date.now()}`,
|
|
222
|
+
startTime: Date.now(),
|
|
223
|
+
startMemory: process.memoryUsage(),
|
|
224
|
+
startCpu: process.cpuUsage(),
|
|
225
|
+
input: this.serializeInput(input),
|
|
226
|
+
output: null,
|
|
227
|
+
error: null,
|
|
228
|
+
duration: 0,
|
|
229
|
+
memoryUsage: null,
|
|
230
|
+
cpuUsage: null,
|
|
231
|
+
networkCalls: [],
|
|
232
|
+
customMetrics: new Map(),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async _executeWithTiming(executionFunc, execution) {
|
|
237
|
+
const startHrTime = process.hrtime.bigint();
|
|
238
|
+
const result = await executionFunc();
|
|
239
|
+
const endHrTime = process.hrtime.bigint();
|
|
240
|
+
|
|
241
|
+
execution.duration = Number(endHrTime - startHrTime) / 1000000; // Convert to ms
|
|
242
|
+
execution.endTime = Date.now();
|
|
243
|
+
execution.endMemory = process.memoryUsage();
|
|
244
|
+
execution.endCpu = process.cpuUsage(execution.startCpu);
|
|
245
|
+
execution.output = this.serializeOutput(result);
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_updateSuccessMetrics(componentProfile, execution) {
|
|
251
|
+
componentProfile.totalDuration += execution.duration;
|
|
252
|
+
componentProfile.successCount++;
|
|
253
|
+
componentProfile.minDuration = Math.min(
|
|
254
|
+
componentProfile.minDuration,
|
|
255
|
+
execution.duration,
|
|
256
|
+
);
|
|
257
|
+
componentProfile.maxDuration = Math.max(
|
|
258
|
+
componentProfile.maxDuration,
|
|
259
|
+
execution.duration,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
this._updateAverageDuration(componentProfile);
|
|
263
|
+
this._updateMemoryMetrics(componentProfile, execution);
|
|
264
|
+
this._updateCpuMetrics(componentProfile, execution);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_updateAverageDuration(componentProfile) {
|
|
268
|
+
const totalExecutions =
|
|
269
|
+
componentProfile.successCount + componentProfile.errorCount;
|
|
270
|
+
componentProfile.averageDuration =
|
|
271
|
+
componentProfile.totalDuration / totalExecutions;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_updateMemoryMetrics(componentProfile, execution) {
|
|
275
|
+
componentProfile.memoryPeak = Math.max(
|
|
276
|
+
componentProfile.memoryPeak,
|
|
277
|
+
execution.endMemory.heapUsed,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_updateCpuMetrics(componentProfile, execution) {
|
|
282
|
+
componentProfile.cpuUsage.user += execution.endCpu.user;
|
|
283
|
+
componentProfile.cpuUsage.system += execution.endCpu.system;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
_handleExecutionError(componentProfile, execution, error) {
|
|
287
|
+
execution.error = {
|
|
288
|
+
message: error.message,
|
|
289
|
+
stack: error.stack,
|
|
290
|
+
name: error.name,
|
|
291
|
+
};
|
|
292
|
+
execution.endTime = Date.now();
|
|
293
|
+
execution.duration = execution.endTime - execution.startTime;
|
|
294
|
+
|
|
295
|
+
componentProfile.errorCount++;
|
|
296
|
+
this._updateAverageDuration(componentProfile);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_emitProfiledEvent(
|
|
300
|
+
componentId,
|
|
301
|
+
executionId,
|
|
302
|
+
execution,
|
|
303
|
+
errorMessage,
|
|
304
|
+
success,
|
|
305
|
+
) {
|
|
306
|
+
const eventData = {
|
|
307
|
+
componentId,
|
|
308
|
+
executionId,
|
|
309
|
+
duration: execution.duration,
|
|
310
|
+
success,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (success) {
|
|
314
|
+
const memoryDelta =
|
|
315
|
+
execution.endMemory.heapUsed - execution.startMemory.heapUsed;
|
|
316
|
+
eventData.memoryDelta = memoryDelta;
|
|
317
|
+
} else {
|
|
318
|
+
eventData.error = errorMessage;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.emit("componentProfiled", eventData);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Add custom metric
|
|
326
|
+
*/
|
|
327
|
+
addMetric(name, value, tags = {}) {
|
|
328
|
+
if (!this.isProfileing) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const metric = {
|
|
333
|
+
name,
|
|
334
|
+
value,
|
|
335
|
+
tags,
|
|
336
|
+
timestamp: Date.now(),
|
|
337
|
+
relativeTime: Date.now() - this.currentProfile.startTime,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (!this.currentProfile.metrics.has(name)) {
|
|
341
|
+
this.currentProfile.metrics.set(name, []);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.currentProfile.metrics.get(name).push(metric);
|
|
345
|
+
this.emit("metricAdded", { name, value, tags });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Profile network call
|
|
350
|
+
*/
|
|
351
|
+
profileNetworkCall(
|
|
352
|
+
url,
|
|
353
|
+
method,
|
|
354
|
+
duration,
|
|
355
|
+
statusCode,
|
|
356
|
+
requestSize = 0,
|
|
357
|
+
responseSize = 0,
|
|
358
|
+
) {
|
|
359
|
+
if (!this.isProfileing || !this._options.enableNetworkProfiling) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const networkCall = {
|
|
364
|
+
url,
|
|
365
|
+
method,
|
|
366
|
+
duration,
|
|
367
|
+
statusCode,
|
|
368
|
+
requestSize,
|
|
369
|
+
responseSize,
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
relativeTime: Date.now() - this.currentProfile.startTime,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Add to current execution if available
|
|
375
|
+
const currentComponent = Array.from(
|
|
376
|
+
this.currentProfile.components.values(),
|
|
377
|
+
).pop();
|
|
378
|
+
if (currentComponent && currentComponent.executions.length > 0) {
|
|
379
|
+
const currentExecution =
|
|
380
|
+
currentComponent.executions[currentComponent.executions.length - 1];
|
|
381
|
+
currentExecution.networkCalls.push(networkCall);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.emit("networkCallProfiled", networkCall);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Stop profiling session
|
|
389
|
+
*/
|
|
390
|
+
stopProfiling() {
|
|
391
|
+
if (!this.isProfileing) {
|
|
392
|
+
throw new Error("No active profiling session");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this.isProfileing = false;
|
|
396
|
+
|
|
397
|
+
if (this.samplingInterval) {
|
|
398
|
+
clearInterval(this.samplingInterval);
|
|
399
|
+
this.samplingInterval = null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.currentProfile.endTime = Date.now();
|
|
403
|
+
this.currentProfile.status = "completed";
|
|
404
|
+
|
|
405
|
+
// Generate analysis
|
|
406
|
+
this.analyzeProfile(this.currentProfile);
|
|
407
|
+
|
|
408
|
+
const sessionId = this.currentProfile.id;
|
|
409
|
+
this.emit("profilingStopped", { sessionId, profile: this.currentProfile });
|
|
410
|
+
|
|
411
|
+
this.currentProfile = null;
|
|
412
|
+
|
|
413
|
+
return sessionId;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Analyze profile data
|
|
418
|
+
*/
|
|
419
|
+
analyzeProfile(profile) {
|
|
420
|
+
const analysis = {
|
|
421
|
+
summary: this.generateSummary(profile),
|
|
422
|
+
bottlenecks: this.identifyBottlenecks(profile),
|
|
423
|
+
recommendations: this.generateRecommendations(profile),
|
|
424
|
+
flamegraph: this.generateFlamegraph(profile),
|
|
425
|
+
trends: this.analyzeTrends(profile),
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
profile.analysis = analysis;
|
|
429
|
+
return analysis;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Generate profile summary
|
|
434
|
+
*/
|
|
435
|
+
generateSummary(profile) {
|
|
436
|
+
const totalDuration = profile.endTime - profile.startTime;
|
|
437
|
+
const components = Array.from(profile.components.values());
|
|
438
|
+
|
|
439
|
+
const totalExecutions = components.reduce(
|
|
440
|
+
(sum, comp) => sum + comp.executions.length,
|
|
441
|
+
0,
|
|
442
|
+
);
|
|
443
|
+
const totalErrors = components.reduce(
|
|
444
|
+
(sum, comp) => sum + comp.errorCount,
|
|
445
|
+
0,
|
|
446
|
+
);
|
|
447
|
+
const avgComponentDuration =
|
|
448
|
+
components.reduce((sum, comp) => sum + comp.averageDuration, 0) /
|
|
449
|
+
components.length;
|
|
450
|
+
|
|
451
|
+
const memoryPeak = Math.max(
|
|
452
|
+
...profile.samples.map((s) => s.memory?.heapUsed || 0),
|
|
453
|
+
);
|
|
454
|
+
const memoryGrowth =
|
|
455
|
+
profile.samples.length > 0
|
|
456
|
+
? profile.samples[profile.samples.length - 1].memory?.heapUsed -
|
|
457
|
+
profile.samples[0].memory?.heapUsed
|
|
458
|
+
: 0;
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
totalDuration,
|
|
462
|
+
totalExecutions,
|
|
463
|
+
totalErrors,
|
|
464
|
+
errorRate:
|
|
465
|
+
totalExecutions > 0 ? (totalErrors / totalExecutions) * 100 : 0,
|
|
466
|
+
avgComponentDuration,
|
|
467
|
+
memoryPeak,
|
|
468
|
+
memoryGrowth,
|
|
469
|
+
samplesCollected: profile.samples.length,
|
|
470
|
+
componentsProfiled: components.length,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Identify performance bottlenecks
|
|
476
|
+
*/
|
|
477
|
+
identifyBottlenecks(profile) {
|
|
478
|
+
const components = Array.from(profile.components.values());
|
|
479
|
+
const bottlenecks = [];
|
|
480
|
+
|
|
481
|
+
// Slow components
|
|
482
|
+
const avgDuration =
|
|
483
|
+
components.reduce((sum, comp) => sum + comp.averageDuration, 0) /
|
|
484
|
+
components.length;
|
|
485
|
+
const slowComponents = components.filter(
|
|
486
|
+
(comp) => comp.averageDuration > avgDuration * 2,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
slowComponents.forEach((comp) => {
|
|
490
|
+
bottlenecks.push({
|
|
491
|
+
type: "slow_component",
|
|
492
|
+
severity: "high",
|
|
493
|
+
component: comp.id,
|
|
494
|
+
description: `Component ${comp.id} is ${(comp.averageDuration / avgDuration).toFixed(1)}x slower than average`,
|
|
495
|
+
metric: "duration",
|
|
496
|
+
value: comp.averageDuration,
|
|
497
|
+
threshold: avgDuration * 2,
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Memory leaks
|
|
502
|
+
if (profile.samples.length > 10) {
|
|
503
|
+
const memoryTrend = this.calculateTrend(
|
|
504
|
+
profile.samples.map((s) => s.memory?.heapUsed || 0),
|
|
505
|
+
);
|
|
506
|
+
if (memoryTrend > 0.1) {
|
|
507
|
+
// Growing trend
|
|
508
|
+
bottlenecks.push({
|
|
509
|
+
type: "memory_leak",
|
|
510
|
+
severity: "medium",
|
|
511
|
+
description:
|
|
512
|
+
"Potential memory leak detected - heap usage is consistently growing",
|
|
513
|
+
metric: "memory_growth_rate",
|
|
514
|
+
value: memoryTrend,
|
|
515
|
+
threshold: 0.1,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// High error rates
|
|
521
|
+
const highErrorComponents = components.filter((comp) => {
|
|
522
|
+
const totalExecs = comp.successCount + comp.errorCount;
|
|
523
|
+
return totalExecs > 0 && comp.errorCount / totalExecs > 0.1;
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
highErrorComponents.forEach((comp) => {
|
|
527
|
+
const errorRate =
|
|
528
|
+
(comp.errorCount / (comp.successCount + comp.errorCount)) * 100;
|
|
529
|
+
bottlenecks.push({
|
|
530
|
+
type: "high_error_rate",
|
|
531
|
+
severity: "high",
|
|
532
|
+
component: comp.id,
|
|
533
|
+
description: `Component ${comp.id} has high error rate: ${errorRate.toFixed(1)}%`,
|
|
534
|
+
metric: "error_rate",
|
|
535
|
+
value: errorRate,
|
|
536
|
+
threshold: 10,
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Event loop lag
|
|
541
|
+
const avgEventLoopLag =
|
|
542
|
+
profile.samples
|
|
543
|
+
.filter((s) => s.eventLoopLag !== undefined)
|
|
544
|
+
.reduce((sum, s) => sum + s.eventLoopLag, 0) / profile.samples.length;
|
|
545
|
+
|
|
546
|
+
if (avgEventLoopLag > 10) {
|
|
547
|
+
// More than 10ms average lag
|
|
548
|
+
bottlenecks.push({
|
|
549
|
+
type: "event_loop_lag",
|
|
550
|
+
severity: "medium",
|
|
551
|
+
description: `High event loop lag detected: ${avgEventLoopLag.toFixed(1)}ms average`,
|
|
552
|
+
metric: "event_loop_lag",
|
|
553
|
+
value: avgEventLoopLag,
|
|
554
|
+
threshold: 10,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return bottlenecks.sort((a, b) => {
|
|
559
|
+
const severityOrder = { high: 3, medium: 2, low: 1 };
|
|
560
|
+
return severityOrder[b.severity] - severityOrder[a.severity];
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Generate optimization recommendations
|
|
566
|
+
*/
|
|
567
|
+
generateRecommendations(profile) {
|
|
568
|
+
const recommendations = [];
|
|
569
|
+
const bottlenecks = this.identifyBottlenecks(profile);
|
|
570
|
+
|
|
571
|
+
bottlenecks.forEach((bottleneck) => {
|
|
572
|
+
switch (bottleneck.type) {
|
|
573
|
+
case "slow_component":
|
|
574
|
+
recommendations.push({
|
|
575
|
+
priority: "high",
|
|
576
|
+
category: "performance",
|
|
577
|
+
title: `Optimize ${bottleneck.component} component`,
|
|
578
|
+
description: `Consider caching, parallel processing, or algorithm optimization for ${bottleneck.component}`,
|
|
579
|
+
impact: "high",
|
|
580
|
+
effort: "medium",
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
|
|
584
|
+
case "memory_leak":
|
|
585
|
+
recommendations.push({
|
|
586
|
+
priority: "high",
|
|
587
|
+
category: "memory",
|
|
588
|
+
title: "Investigate memory leak",
|
|
589
|
+
description:
|
|
590
|
+
"Review object lifecycle management and ensure proper cleanup of resources",
|
|
591
|
+
impact: "high",
|
|
592
|
+
effort: "high",
|
|
593
|
+
});
|
|
594
|
+
break;
|
|
595
|
+
|
|
596
|
+
case "high_error_rate":
|
|
597
|
+
recommendations.push({
|
|
598
|
+
priority: "critical",
|
|
599
|
+
category: "reliability",
|
|
600
|
+
title: `Fix error handling in ${bottleneck.component}`,
|
|
601
|
+
description: "Improve error handling and add retry mechanisms",
|
|
602
|
+
impact: "high",
|
|
603
|
+
effort: "medium",
|
|
604
|
+
});
|
|
605
|
+
break;
|
|
606
|
+
|
|
607
|
+
case "event_loop_lag":
|
|
608
|
+
recommendations.push({
|
|
609
|
+
priority: "medium",
|
|
610
|
+
category: "concurrency",
|
|
611
|
+
title: "Reduce event loop blocking",
|
|
612
|
+
description:
|
|
613
|
+
"Move CPU-intensive operations to worker threads or break into smaller chunks",
|
|
614
|
+
impact: "medium",
|
|
615
|
+
effort: "medium",
|
|
616
|
+
});
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// General recommendations
|
|
622
|
+
const components = Array.from(profile.components.values());
|
|
623
|
+
const totalNetworkCalls = components.reduce(
|
|
624
|
+
(sum, comp) =>
|
|
625
|
+
sum +
|
|
626
|
+
comp.executions.reduce(
|
|
627
|
+
(execSum, exec) => execSum + exec.networkCalls.length,
|
|
628
|
+
0,
|
|
629
|
+
),
|
|
630
|
+
0,
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
if (totalNetworkCalls > 10) {
|
|
634
|
+
recommendations.push({
|
|
635
|
+
priority: "medium",
|
|
636
|
+
category: "network",
|
|
637
|
+
title: "Optimize network calls",
|
|
638
|
+
description:
|
|
639
|
+
"Consider request batching, connection pooling, or caching for frequent API calls",
|
|
640
|
+
impact: "medium",
|
|
641
|
+
effort: "low",
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return recommendations.sort((a, b) => {
|
|
646
|
+
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
647
|
+
return priorityOrder[b.priority] - priorityOrder[a.priority];
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Generate flamegraph data
|
|
653
|
+
*/
|
|
654
|
+
generateFlamegraph(profile) {
|
|
655
|
+
const components = Array.from(profile.components.values());
|
|
656
|
+
const flamegraphData = {
|
|
657
|
+
name: "Pipeline",
|
|
658
|
+
value: profile.endTime - profile.startTime,
|
|
659
|
+
children: [],
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
components.forEach((component) => {
|
|
663
|
+
const componentNode = {
|
|
664
|
+
name: component.id,
|
|
665
|
+
value: component.totalDuration,
|
|
666
|
+
children: [],
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// Group executions by similar duration
|
|
670
|
+
const executionGroups = this.groupExecutionsByDuration(
|
|
671
|
+
component.executions,
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
executionGroups.forEach((group) => {
|
|
675
|
+
componentNode.children.push({
|
|
676
|
+
name: `${group.executions.length} executions`,
|
|
677
|
+
value: group.totalDuration,
|
|
678
|
+
avgDuration: group.avgDuration,
|
|
679
|
+
count: group.executions.length,
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
flamegraphData.children.push(componentNode);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
return flamegraphData;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Group executions by similar duration for flamegraph
|
|
691
|
+
*/
|
|
692
|
+
groupExecutionsByDuration(executions) {
|
|
693
|
+
const groups = [];
|
|
694
|
+
const sortedExecutions = executions.sort((a, b) => a.duration - b.duration);
|
|
695
|
+
|
|
696
|
+
let currentGroup = null;
|
|
697
|
+
|
|
698
|
+
sortedExecutions.forEach((execution) => {
|
|
699
|
+
if (
|
|
700
|
+
!currentGroup ||
|
|
701
|
+
Math.abs(execution.duration - currentGroup.avgDuration) >
|
|
702
|
+
currentGroup.avgDuration * 0.5
|
|
703
|
+
) {
|
|
704
|
+
currentGroup = {
|
|
705
|
+
executions: [execution],
|
|
706
|
+
totalDuration: execution.duration,
|
|
707
|
+
avgDuration: execution.duration,
|
|
708
|
+
};
|
|
709
|
+
groups.push(currentGroup);
|
|
710
|
+
} else {
|
|
711
|
+
currentGroup.executions.push(execution);
|
|
712
|
+
currentGroup.totalDuration += execution.duration;
|
|
713
|
+
currentGroup.avgDuration =
|
|
714
|
+
currentGroup.totalDuration / currentGroup.executions.length;
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
return groups;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Analyze trends in performance data
|
|
723
|
+
*/
|
|
724
|
+
analyzeTrends(profile) {
|
|
725
|
+
const trends = {};
|
|
726
|
+
|
|
727
|
+
if (profile.samples.length > 5) {
|
|
728
|
+
// Memory trend
|
|
729
|
+
const memoryValues = profile.samples.map((s) => s.memory?.heapUsed || 0);
|
|
730
|
+
trends.memory = {
|
|
731
|
+
trend: this.calculateTrend(memoryValues),
|
|
732
|
+
direction: this.getTrendDirection(memoryValues),
|
|
733
|
+
volatility: this.calculateVolatility(memoryValues),
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// CPU trend
|
|
737
|
+
const cpuValues = profile.samples.map(
|
|
738
|
+
(s) => (s.cpu?.userDelta || 0) + (s.cpu?.systemDelta || 0),
|
|
739
|
+
);
|
|
740
|
+
trends.cpu = {
|
|
741
|
+
trend: this.calculateTrend(cpuValues),
|
|
742
|
+
direction: this.getTrendDirection(cpuValues),
|
|
743
|
+
volatility: this.calculateVolatility(cpuValues),
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
// Event loop lag trend
|
|
747
|
+
const lagValues = profile.samples
|
|
748
|
+
.filter((s) => s.eventLoopLag !== undefined)
|
|
749
|
+
.map((s) => s.eventLoopLag);
|
|
750
|
+
if (lagValues.length > 0) {
|
|
751
|
+
trends.eventLoopLag = {
|
|
752
|
+
trend: this.calculateTrend(lagValues),
|
|
753
|
+
direction: this.getTrendDirection(lagValues),
|
|
754
|
+
volatility: this.calculateVolatility(lagValues),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return trends;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Calculate trend (linear regression slope)
|
|
764
|
+
*/
|
|
765
|
+
calculateTrend(values) {
|
|
766
|
+
if (values.length < 2) return 0;
|
|
767
|
+
|
|
768
|
+
const n = values.length;
|
|
769
|
+
const sumX = (n * (n - 1)) / 2;
|
|
770
|
+
const sumY = values.reduce((sum, val) => sum + val, 0);
|
|
771
|
+
const sumXY = values.reduce((sum, val, i) => sum + i * val, 0);
|
|
772
|
+
const sumXX = (n * (n - 1) * (2 * n - 1)) / 6;
|
|
773
|
+
|
|
774
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
|
775
|
+
return slope;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Get trend direction
|
|
780
|
+
*/
|
|
781
|
+
getTrendDirection(values) {
|
|
782
|
+
const trend = this.calculateTrend(values);
|
|
783
|
+
if (Math.abs(trend) < 0.01) return "stable";
|
|
784
|
+
return trend > 0 ? "increasing" : "decreasing";
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Calculate volatility (standard deviation)
|
|
789
|
+
*/
|
|
790
|
+
calculateVolatility(values) {
|
|
791
|
+
if (values.length < 2) return 0;
|
|
792
|
+
|
|
793
|
+
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
794
|
+
const variance =
|
|
795
|
+
values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) /
|
|
796
|
+
values.length;
|
|
797
|
+
return Math.sqrt(variance);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Export profile report
|
|
802
|
+
*/
|
|
803
|
+
async exportReport(sessionId, format = "json") {
|
|
804
|
+
const profile = this.profiles.get(sessionId);
|
|
805
|
+
if (!profile) {
|
|
806
|
+
throw new Error(`Profile ${sessionId} not found`);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
await fs.mkdir(this._options.outputDir, { recursive: true });
|
|
810
|
+
|
|
811
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
812
|
+
const filename = `profile-${sessionId}-${timestamp}`;
|
|
813
|
+
|
|
814
|
+
if (format === "json") {
|
|
815
|
+
const _filePath = path.join(this._options.outputDir, `${filename}.json`);
|
|
816
|
+
await fs.writeFile(_filePath, JSON.stringify(profile, null, 2));
|
|
817
|
+
return _filePath;
|
|
818
|
+
} else if (format === "html") {
|
|
819
|
+
const htmlReport = this.generateHTMLReport(profile);
|
|
820
|
+
const _filePath = path.join(this._options.outputDir, `${filename}.html`);
|
|
821
|
+
await fs.writeFile(_filePath, htmlReport);
|
|
822
|
+
return _filePath;
|
|
823
|
+
} else {
|
|
824
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Generate HTML report
|
|
830
|
+
*/
|
|
831
|
+
generateHTMLReport(profile) {
|
|
832
|
+
const summary = profile.analysis?.summary || {};
|
|
833
|
+
const bottlenecks = profile.analysis?.bottlenecks || [];
|
|
834
|
+
const recommendations = profile.analysis?.recommendations || [];
|
|
835
|
+
|
|
836
|
+
return `<!DOCTYPE html>
|
|
837
|
+
<html>
|
|
838
|
+
<head>
|
|
839
|
+
<title>Performance Profile Report - ${profile.id}</title>
|
|
840
|
+
<style>
|
|
841
|
+
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
842
|
+
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; }
|
|
843
|
+
.section { margin: 20px 0; }
|
|
844
|
+
.metric { display: inline-block; margin: 10px; padding: 10px; background: #e9ecef; border-radius: 3px; }
|
|
845
|
+
.bottleneck { padding: 10px; margin: 5px 0; border-left: 4px solid #dc3545; background: #f8d7da; }
|
|
846
|
+
.recommendation { padding: 10px; margin: 5px 0; border-left: 4px solid #28a745; background: #d4edda; }
|
|
847
|
+
.high { border-color: #dc3545; }
|
|
848
|
+
.medium { border-color: #ffc107; }
|
|
849
|
+
.low { border-color: #28a745; }
|
|
850
|
+
</style>
|
|
851
|
+
</head>
|
|
852
|
+
<body>
|
|
853
|
+
<div class="header">
|
|
854
|
+
<h1>Performance Profile Report</h1>
|
|
855
|
+
<p><strong>Session:</strong> ${profile.id}</p>
|
|
856
|
+
<p><strong>Duration:</strong> ${summary.totalDuration || 0}ms</p>
|
|
857
|
+
<p><strong>Generated:</strong> ${new Date().toISOString()}</p>
|
|
858
|
+
</div>
|
|
859
|
+
|
|
860
|
+
<div class="section">
|
|
861
|
+
<h2>Summary</h2>
|
|
862
|
+
<div class="metric">Total Executions: ${summary.totalExecutions || 0}</div>
|
|
863
|
+
<div class="metric">Error Rate: ${(summary.errorRate || 0).toFixed(1)}%</div>
|
|
864
|
+
<div class="metric">Avg Duration: ${(summary.avgComponentDuration || 0).toFixed(1)}ms</div>
|
|
865
|
+
<div class="metric">Memory Peak: ${Math.round((summary.memoryPeak || 0) / 1024 / 1024)}MB</div>
|
|
866
|
+
</div>
|
|
867
|
+
|
|
868
|
+
<div class="section">
|
|
869
|
+
<h2>Bottlenecks</h2>
|
|
870
|
+
${bottlenecks
|
|
871
|
+
.map(
|
|
872
|
+
(b) => `
|
|
873
|
+
<div class="bottleneck ${b.severity}">
|
|
874
|
+
<strong>${b.type.replace(/_/g, " ").toUpperCase()}</strong>: ${b.description}
|
|
875
|
+
</div>
|
|
876
|
+
`,
|
|
877
|
+
)
|
|
878
|
+
.join("")}
|
|
879
|
+
</div>
|
|
880
|
+
|
|
881
|
+
<div class="section">
|
|
882
|
+
<h2>Recommendations</h2>
|
|
883
|
+
${recommendations
|
|
884
|
+
.map(
|
|
885
|
+
(r) => `
|
|
886
|
+
<div class="recommendation ${r.priority}">
|
|
887
|
+
<strong>${r.title}</strong>: ${r.description}
|
|
888
|
+
<br><small>Impact: ${r.impact}, Effort: ${r.effort}</small>
|
|
889
|
+
</div>
|
|
890
|
+
`,
|
|
891
|
+
)
|
|
892
|
+
.join("")}
|
|
893
|
+
</div>
|
|
894
|
+
</body>
|
|
895
|
+
</html>`;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Serialize input for storage
|
|
900
|
+
*/
|
|
901
|
+
serializeInput(input) {
|
|
902
|
+
try {
|
|
903
|
+
return JSON.parse(JSON.stringify(input));
|
|
904
|
+
} catch (error) {
|
|
905
|
+
return {
|
|
906
|
+
_serializationError: "Unable to serialize input",
|
|
907
|
+
_type: typeof input,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Serialize output for storage
|
|
914
|
+
*/
|
|
915
|
+
serializeOutput(output) {
|
|
916
|
+
try {
|
|
917
|
+
return JSON.parse(JSON.stringify(output));
|
|
918
|
+
} catch (error) {
|
|
919
|
+
return {
|
|
920
|
+
_serializationError: "Unable to serialize output",
|
|
921
|
+
_type: typeof output,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Get all profiles
|
|
928
|
+
*/
|
|
929
|
+
getAllProfiles() {
|
|
930
|
+
return Array.from(this.profiles.values());
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Get profile by ID
|
|
935
|
+
*/
|
|
936
|
+
getProfile(sessionId) {
|
|
937
|
+
return this.profiles.get(sessionId);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Clear old profiles
|
|
942
|
+
*/
|
|
943
|
+
clearProfiles(olderThanMs = 24 * 60 * 60 * 1000) {
|
|
944
|
+
// 24 hours default
|
|
945
|
+
const cutoffTime = Date.now() - olderThanMs;
|
|
946
|
+
|
|
947
|
+
for (const [sessionId, profile] of this.profiles) {
|
|
948
|
+
if (profile.startTime < cutoffTime) {
|
|
949
|
+
this.profiles.delete(sessionId);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get profiler status
|
|
956
|
+
*/
|
|
957
|
+
getStatus() {
|
|
958
|
+
return {
|
|
959
|
+
isProfileing: this.isProfileing,
|
|
960
|
+
currentProfile: this.currentProfile?.id || null,
|
|
961
|
+
totalProfiles: this.profiles.size,
|
|
962
|
+
samplesCollected: this.samples.length,
|
|
963
|
+
_options: this._options,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
module.exports = PerformanceProfiler;
|