@devilsdev/rag-pipeline-utils 2.1.12 ā 2.2.1
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 +178 -550
- package/bin/cli.js +8 -14
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +18 -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 +620 -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 +294 -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 +1030 -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 +111 -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/rate-limiter.js +244 -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/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +388 -0
- package/src/utils/validate-plugin-contract.js +42 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark tooling for measuring RAG pipeline performance
|
|
3
|
+
* Provides detailed timing and performance metrics for each stage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { logger } = require('../../utils/structured-logger.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Performance timer utility
|
|
11
|
+
*/
|
|
12
|
+
class PerformanceTimer {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.timers = new Map();
|
|
15
|
+
this.results = new Map();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Start timing an operation
|
|
20
|
+
* @param {string} name - Timer name
|
|
21
|
+
* @param {object} metadata - Additional metadata
|
|
22
|
+
*/
|
|
23
|
+
start(name, metadata = {}) {
|
|
24
|
+
this.timers.set(name, {
|
|
25
|
+
startTime: performance.now(),
|
|
26
|
+
startMemory: process.memoryUsage(),
|
|
27
|
+
metadata,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* End timing an operation
|
|
33
|
+
* @param {string} name - Timer name
|
|
34
|
+
* @param {object} result - Operation result metadata
|
|
35
|
+
*/
|
|
36
|
+
end(name, result = {}) {
|
|
37
|
+
const timer = this.timers.get(name);
|
|
38
|
+
if (!timer) {
|
|
39
|
+
throw new Error(`Timer '${name}' not found. Did you call start() first?`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const endTime = performance.now();
|
|
43
|
+
const endMemory = process.memoryUsage();
|
|
44
|
+
|
|
45
|
+
const timing = {
|
|
46
|
+
duration: endTime - timer.startTime,
|
|
47
|
+
startTime: timer.startTime,
|
|
48
|
+
endTime,
|
|
49
|
+
memoryDelta: {
|
|
50
|
+
heapUsed: endMemory.heapUsed - timer.startMemory.heapUsed,
|
|
51
|
+
heapTotal: endMemory.heapTotal - timer.startMemory.heapTotal,
|
|
52
|
+
external: endMemory.external - timer.startMemory.external,
|
|
53
|
+
},
|
|
54
|
+
metadata: timer.metadata,
|
|
55
|
+
result,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.results.set(name, timing);
|
|
59
|
+
this.timers.delete(name);
|
|
60
|
+
|
|
61
|
+
return timing;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get timing result
|
|
66
|
+
* @param {string} name - Timer name
|
|
67
|
+
* @returns {object} Timing result
|
|
68
|
+
*/
|
|
69
|
+
getResult(name) {
|
|
70
|
+
return this.results.get(name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all results
|
|
75
|
+
* @returns {Map} All timing results
|
|
76
|
+
*/
|
|
77
|
+
getAllResults() {
|
|
78
|
+
return new Map(this.results);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Clear all results
|
|
83
|
+
*/
|
|
84
|
+
clear() {
|
|
85
|
+
this.timers.clear();
|
|
86
|
+
this.results.clear();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pipeline benchmark runner
|
|
92
|
+
*/
|
|
93
|
+
class PipelineBenchmark {
|
|
94
|
+
constructor(pipeline, options = {}) {
|
|
95
|
+
this.pipeline = pipeline;
|
|
96
|
+
this.timer = new PerformanceTimer();
|
|
97
|
+
this.options = {
|
|
98
|
+
includeMemory: options.includeMemory !== false,
|
|
99
|
+
includeGC: options.includeGC !== false,
|
|
100
|
+
warmupRuns: options.warmupRuns || 0,
|
|
101
|
+
iterations: options.iterations || 1,
|
|
102
|
+
concurrency: options.concurrency || 1,
|
|
103
|
+
timeout: options.timeout || 30000,
|
|
104
|
+
...options,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Benchmark the ingest operation
|
|
110
|
+
* @param {string} docPath - Document path
|
|
111
|
+
* @returns {Promise<object>} Benchmark results
|
|
112
|
+
*/
|
|
113
|
+
async benchmarkIngest(docPath) {
|
|
114
|
+
logger.info('Benchmarking ingest operation', { docPath });
|
|
115
|
+
|
|
116
|
+
// Warmup runs
|
|
117
|
+
for (let i = 0; i < this.options.warmupRuns; i++) {
|
|
118
|
+
logger.info('Warmup run', { run: i + 1, total: this.options.warmupRuns });
|
|
119
|
+
await this.runIngestBenchmark(docPath, true);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Actual benchmark runs
|
|
123
|
+
const results = [];
|
|
124
|
+
for (let i = 0; i < this.options.iterations; i++) {
|
|
125
|
+
logger.info('Benchmark run', {
|
|
126
|
+
run: i + 1,
|
|
127
|
+
total: this.options.iterations,
|
|
128
|
+
});
|
|
129
|
+
const result = await this.runIngestBenchmark(docPath, false);
|
|
130
|
+
results.push(result);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return this.aggregateResults('ingest', results);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Run a single ingest benchmark
|
|
138
|
+
* @param {string} docPath - Document path
|
|
139
|
+
* @param {boolean} isWarmup - Whether this is a warmup run
|
|
140
|
+
* @returns {Promise<object>} Single run results
|
|
141
|
+
*/
|
|
142
|
+
async runIngestBenchmark(docPath, isWarmup = false) {
|
|
143
|
+
this.timer.clear();
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Overall ingest timing
|
|
147
|
+
this.timer.start('ingest_total', { docPath, isWarmup });
|
|
148
|
+
|
|
149
|
+
// Stage 1: Document loading
|
|
150
|
+
this.timer.start('loader', { stage: 'load', docPath });
|
|
151
|
+
const documents = await this.pipeline.loaderInstance.load(docPath);
|
|
152
|
+
const loadResult = this.timer.end('loader', {
|
|
153
|
+
documentCount: documents.length,
|
|
154
|
+
totalSize: documents.reduce(
|
|
155
|
+
(sum, doc) => sum + (doc.content?.length || 0),
|
|
156
|
+
0,
|
|
157
|
+
),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Stage 2: Chunking
|
|
161
|
+
this.timer.start('chunker', {
|
|
162
|
+
stage: 'chunk',
|
|
163
|
+
documentCount: documents.length,
|
|
164
|
+
});
|
|
165
|
+
const chunks = documents.flatMap((doc) => doc.chunk());
|
|
166
|
+
const chunkResult = this.timer.end('chunker', {
|
|
167
|
+
chunkCount: chunks.length,
|
|
168
|
+
avgChunkSize:
|
|
169
|
+
chunks.reduce((sum, chunk) => sum + chunk.length, 0) / chunks.length,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Stage 3: Embedding
|
|
173
|
+
this.timer.start('embedder', {
|
|
174
|
+
stage: 'embed',
|
|
175
|
+
chunkCount: chunks.length,
|
|
176
|
+
});
|
|
177
|
+
const vectors = await this.pipeline.embedderInstance.embed(chunks);
|
|
178
|
+
const embedResult = this.timer.end('embedder', {
|
|
179
|
+
vectorCount: vectors.length,
|
|
180
|
+
vectorDimension: vectors[0]?.length || 0,
|
|
181
|
+
throughput: chunks.length / (loadResult.duration / 1000), // chunks per second
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Stage 4: Storage
|
|
185
|
+
this.timer.start('retriever_store', {
|
|
186
|
+
stage: 'store',
|
|
187
|
+
vectorCount: vectors.length,
|
|
188
|
+
});
|
|
189
|
+
await this.pipeline.retrieverInstance.store(vectors);
|
|
190
|
+
const storeResult = this.timer.end('retriever_store', {
|
|
191
|
+
vectorCount: vectors.length,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// End overall timing
|
|
195
|
+
const totalResult = this.timer.end('ingest_total', {
|
|
196
|
+
success: true,
|
|
197
|
+
documentCount: documents.length,
|
|
198
|
+
chunkCount: chunks.length,
|
|
199
|
+
vectorCount: vectors.length,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
stages: {
|
|
205
|
+
load: loadResult,
|
|
206
|
+
chunk: chunkResult,
|
|
207
|
+
embed: embedResult,
|
|
208
|
+
store: storeResult,
|
|
209
|
+
},
|
|
210
|
+
total: totalResult,
|
|
211
|
+
metadata: {
|
|
212
|
+
docPath,
|
|
213
|
+
isWarmup,
|
|
214
|
+
timestamp: new Date().toISOString(),
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
const totalResult = this.timer.end('ingest_total', {
|
|
219
|
+
success: false,
|
|
220
|
+
error: error.message,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: error.message,
|
|
226
|
+
total: totalResult,
|
|
227
|
+
stages: Object.fromEntries(this.timer.getAllResults()),
|
|
228
|
+
metadata: {
|
|
229
|
+
docPath,
|
|
230
|
+
isWarmup,
|
|
231
|
+
timestamp: new Date().toISOString(),
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Benchmark the query operation
|
|
239
|
+
* @param {string} prompt - Query prompt
|
|
240
|
+
* @returns {Promise<object>} Benchmark results
|
|
241
|
+
*/
|
|
242
|
+
async benchmarkQuery(prompt) {
|
|
243
|
+
logger.info('Benchmarking query operation', {
|
|
244
|
+
promptPreview: prompt.substring(0, 50),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Warmup runs
|
|
248
|
+
for (let i = 0; i < this.options.warmupRuns; i++) {
|
|
249
|
+
logger.info('Warmup run', { run: i + 1, total: this.options.warmupRuns });
|
|
250
|
+
await this.runQueryBenchmark(prompt, true);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Actual benchmark runs
|
|
254
|
+
const results = [];
|
|
255
|
+
for (let i = 0; i < this.options.iterations; i++) {
|
|
256
|
+
logger.info('Benchmark run', {
|
|
257
|
+
run: i + 1,
|
|
258
|
+
total: this.options.iterations,
|
|
259
|
+
});
|
|
260
|
+
const result = await this.runQueryBenchmark(prompt, false);
|
|
261
|
+
results.push(result);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return this.aggregateResults('query', results);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Run a single query benchmark
|
|
269
|
+
* @param {string} prompt - Query prompt
|
|
270
|
+
* @param {boolean} isWarmup - Whether this is a warmup run
|
|
271
|
+
* @returns {Promise<object>} Single run results
|
|
272
|
+
*/
|
|
273
|
+
async runQueryBenchmark(prompt, isWarmup = false) {
|
|
274
|
+
this.timer.clear();
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Overall query timing
|
|
278
|
+
this.timer.start('query_total', {
|
|
279
|
+
prompt: prompt.substring(0, 100),
|
|
280
|
+
isWarmup,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Stage 1: Query embedding
|
|
284
|
+
this.timer.start('embedder_query', {
|
|
285
|
+
stage: 'embed_query',
|
|
286
|
+
promptLength: prompt.length,
|
|
287
|
+
});
|
|
288
|
+
const queryVector =
|
|
289
|
+
await this.pipeline.embedderInstance.embedQuery(prompt);
|
|
290
|
+
const embedResult = this.timer.end('embedder_query', {
|
|
291
|
+
vectorDimension: queryVector.length,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Stage 2: Retrieval
|
|
295
|
+
this.timer.start('retriever_search', {
|
|
296
|
+
stage: 'retrieve',
|
|
297
|
+
vectorDimension: queryVector.length,
|
|
298
|
+
});
|
|
299
|
+
let retrieved =
|
|
300
|
+
await this.pipeline.retrieverInstance.retrieve(queryVector);
|
|
301
|
+
const retrieveResult = this.timer.end('retriever_search', {
|
|
302
|
+
retrievedCount: retrieved.length,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Stage 3: Reranking (if enabled)
|
|
306
|
+
let rerankResult = null;
|
|
307
|
+
if (this.pipeline.rerankerInstance) {
|
|
308
|
+
this.timer.start('reranker', {
|
|
309
|
+
stage: 'rerank',
|
|
310
|
+
documentCount: retrieved.length,
|
|
311
|
+
});
|
|
312
|
+
retrieved = await this.pipeline.rerankerInstance.rerank(
|
|
313
|
+
prompt,
|
|
314
|
+
retrieved,
|
|
315
|
+
);
|
|
316
|
+
rerankResult = this.timer.end('reranker', {
|
|
317
|
+
rerankedCount: retrieved.length,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Stage 4: LLM Generation
|
|
322
|
+
this.timer.start('llm_generate', {
|
|
323
|
+
stage: 'generate',
|
|
324
|
+
promptLength: prompt.length,
|
|
325
|
+
contextCount: retrieved.length,
|
|
326
|
+
});
|
|
327
|
+
const result = await this.pipeline.llmInstance.generate(
|
|
328
|
+
prompt,
|
|
329
|
+
retrieved,
|
|
330
|
+
);
|
|
331
|
+
const generateResult = this.timer.end('llm_generate', {
|
|
332
|
+
responseLength: result.length,
|
|
333
|
+
estimatedTokens: Math.ceil(result.length / 4), // Rough token estimate
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// End overall timing
|
|
337
|
+
const totalResult = this.timer.end('query_total', {
|
|
338
|
+
success: true,
|
|
339
|
+
promptLength: prompt.length,
|
|
340
|
+
responseLength: result.length,
|
|
341
|
+
retrievedCount: retrieved.length,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const stages = {
|
|
345
|
+
embed: embedResult,
|
|
346
|
+
retrieve: retrieveResult,
|
|
347
|
+
generate: generateResult,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
if (rerankResult) {
|
|
351
|
+
stages.rerank = rerankResult;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
success: true,
|
|
356
|
+
stages,
|
|
357
|
+
total: totalResult,
|
|
358
|
+
result,
|
|
359
|
+
metadata: {
|
|
360
|
+
prompt: prompt.substring(0, 100),
|
|
361
|
+
isWarmup,
|
|
362
|
+
timestamp: new Date().toISOString(),
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const totalResult = this.timer.end('query_total', {
|
|
367
|
+
success: false,
|
|
368
|
+
error: error.message,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: error.message,
|
|
374
|
+
total: totalResult,
|
|
375
|
+
stages: Object.fromEntries(this.timer.getAllResults()),
|
|
376
|
+
metadata: {
|
|
377
|
+
prompt: prompt.substring(0, 100),
|
|
378
|
+
isWarmup,
|
|
379
|
+
timestamp: new Date().toISOString(),
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Aggregate results from multiple runs
|
|
387
|
+
* @param {string} operation - Operation name
|
|
388
|
+
* @param {object[]} results - Array of run results
|
|
389
|
+
* @returns {object} Aggregated results
|
|
390
|
+
*/
|
|
391
|
+
aggregateResults(operation, results) {
|
|
392
|
+
const successful = results.filter((r) => r.success);
|
|
393
|
+
const failed = results.filter((r) => !r.success);
|
|
394
|
+
|
|
395
|
+
if (successful.length === 0) {
|
|
396
|
+
return {
|
|
397
|
+
operation,
|
|
398
|
+
success: false,
|
|
399
|
+
error: 'All benchmark runs failed',
|
|
400
|
+
runs: {
|
|
401
|
+
failed: failed.length,
|
|
402
|
+
total: results.length,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Aggregate stage timings
|
|
408
|
+
const stageStats = {};
|
|
409
|
+
const stageNames = Object.keys(successful[0].stages);
|
|
410
|
+
|
|
411
|
+
for (const stageName of stageNames) {
|
|
412
|
+
const stageDurations = successful
|
|
413
|
+
.map((r) => r.stages[stageName]?.duration)
|
|
414
|
+
.filter((d) => d !== undefined);
|
|
415
|
+
|
|
416
|
+
if (stageDurations.length > 0) {
|
|
417
|
+
stageStats[stageName] = this.calculateStats(stageDurations);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Aggregate total timings
|
|
422
|
+
const totalDurations = successful.map((r) => r.total.duration);
|
|
423
|
+
const totalStats = this.calculateStats(totalDurations);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
operation,
|
|
427
|
+
success: true,
|
|
428
|
+
runs: {
|
|
429
|
+
total: results.length,
|
|
430
|
+
successful: successful.length,
|
|
431
|
+
failed: failed.length,
|
|
432
|
+
},
|
|
433
|
+
timing: {
|
|
434
|
+
stages: stageStats,
|
|
435
|
+
total: totalStats,
|
|
436
|
+
},
|
|
437
|
+
metadata: {
|
|
438
|
+
iterations: this.options.iterations,
|
|
439
|
+
warmupRuns: this.options.warmupRuns,
|
|
440
|
+
timestamp: new Date().toISOString(),
|
|
441
|
+
},
|
|
442
|
+
rawResults: successful,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Calculate statistical measures for an array of values
|
|
448
|
+
* @param {number[]} values - Array of numeric values
|
|
449
|
+
* @returns {object} Statistical measures
|
|
450
|
+
*/
|
|
451
|
+
calculateStats(values) {
|
|
452
|
+
if (values.length === 0) return null;
|
|
453
|
+
|
|
454
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
455
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
456
|
+
const mean = sum / values.length;
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
min: Math.min(...values),
|
|
460
|
+
max: Math.max(...values),
|
|
461
|
+
mean: mean,
|
|
462
|
+
median: sorted[Math.floor(sorted.length / 2)],
|
|
463
|
+
p95: sorted[Math.floor(sorted.length * 0.95)],
|
|
464
|
+
p99: sorted[Math.floor(sorted.length * 0.99)],
|
|
465
|
+
stdDev: Math.sqrt(
|
|
466
|
+
values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) /
|
|
467
|
+
values.length,
|
|
468
|
+
),
|
|
469
|
+
count: values.length,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Format benchmark results for display
|
|
475
|
+
* @param {object} results - Benchmark results
|
|
476
|
+
* @returns {string} Formatted output
|
|
477
|
+
*/
|
|
478
|
+
formatResults(results) {
|
|
479
|
+
if (!results.success) {
|
|
480
|
+
return `ā Benchmark failed: ${results.error}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let output = `\nš ${results.operation.toUpperCase()} BENCHMARK RESULTS\n`;
|
|
484
|
+
output += `${'='.repeat(50)}\n`;
|
|
485
|
+
output += `Runs: ${results.runs.successful}/${results.runs.total} successful\n`;
|
|
486
|
+
|
|
487
|
+
if (results.runs.failed > 0) {
|
|
488
|
+
output += `ā ļø ${results.runs.failed} runs failed\n`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
output += '\nā±ļø STAGE TIMINGS (ms)\n';
|
|
492
|
+
output += `${'-'.repeat(30)}\n`;
|
|
493
|
+
|
|
494
|
+
for (const [stage, stats] of Object.entries(results.timing.stages)) {
|
|
495
|
+
output += `${stage.padEnd(15)} | `;
|
|
496
|
+
output += `avg: ${stats.mean.toFixed(1).padStart(6)} | `;
|
|
497
|
+
output += `min: ${stats.min.toFixed(1).padStart(6)} | `;
|
|
498
|
+
output += `max: ${stats.max.toFixed(1).padStart(6)} | `;
|
|
499
|
+
output += `p95: ${stats.p95.toFixed(1).padStart(6)}\n`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
output += `${'-'.repeat(30)}\n`;
|
|
503
|
+
output += 'TOTAL | ';
|
|
504
|
+
output += `avg: ${results.timing.total.mean.toFixed(1).padStart(6)} | `;
|
|
505
|
+
output += `min: ${results.timing.total.min.toFixed(1).padStart(6)} | `;
|
|
506
|
+
output += `max: ${results.timing.total.max.toFixed(1).padStart(6)} | `;
|
|
507
|
+
output += `p95: ${results.timing.total.p95.toFixed(1).padStart(6)}\n`;
|
|
508
|
+
|
|
509
|
+
return output;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
module.exports = {
|
|
514
|
+
PipelineBenchmark,
|
|
515
|
+
PerformanceTimer,
|
|
516
|
+
};
|