@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,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability Metrics
|
|
3
|
+
*
|
|
4
|
+
* Enterprise-grade metrics collection and monitoring for RAG pipelines.
|
|
5
|
+
* Provides Counter, Histogram, and PipelineMetrics classes for comprehensive observability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events'); // eslint-disable-line global-require
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Counter metric for tracking cumulative values
|
|
12
|
+
*/
|
|
13
|
+
class Counter extends EventEmitter {
|
|
14
|
+
constructor(name, description = '', labels = []) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this.name = name;
|
|
18
|
+
this.description = description;
|
|
19
|
+
this.labels = labels;
|
|
20
|
+
this.value = 0;
|
|
21
|
+
this.labeledValues = new Map();
|
|
22
|
+
this.created = Date.now();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Increment counter by specified amount
|
|
27
|
+
*/
|
|
28
|
+
inc(amount = 1, labelValues = {}) {
|
|
29
|
+
if (amount < 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'Counter can only be incremented by non-negative amounts',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Object.keys(labelValues).length === 0) {
|
|
36
|
+
this.value += amount;
|
|
37
|
+
} else {
|
|
38
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
39
|
+
const current = this.labeledValues.get(labelKey) || 0;
|
|
40
|
+
this.labeledValues.set(labelKey, current + amount);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.emit('increment', { amount, labelValues, timestamp: Date.now() });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get current counter value
|
|
48
|
+
*/
|
|
49
|
+
get(labelValues = {}) {
|
|
50
|
+
if (Object.keys(labelValues).length === 0) {
|
|
51
|
+
return this.value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
55
|
+
return this.labeledValues.get(labelKey) || 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reset counter to zero
|
|
60
|
+
*/
|
|
61
|
+
reset(labelValues = {}) {
|
|
62
|
+
if (Object.keys(labelValues).length === 0) {
|
|
63
|
+
this.value = 0;
|
|
64
|
+
} else {
|
|
65
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
66
|
+
this.labeledValues.set(labelKey, 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.emit('reset', { labelValues, timestamp: Date.now() });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all labeled values
|
|
74
|
+
*/
|
|
75
|
+
getAllValues() {
|
|
76
|
+
const result = { _unlabeled: this.value };
|
|
77
|
+
|
|
78
|
+
for (const [labelKey, value] of this.labeledValues) {
|
|
79
|
+
result[labelKey] = value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generate label key from label values
|
|
87
|
+
*/
|
|
88
|
+
_getLabelKey(labelValues) {
|
|
89
|
+
const sortedKeys = Object.keys(labelValues).sort();
|
|
90
|
+
return sortedKeys.map((key) => `${key}=${labelValues[key]}`).join(',');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Export metric data
|
|
95
|
+
*/
|
|
96
|
+
export() {
|
|
97
|
+
return {
|
|
98
|
+
name: this.name,
|
|
99
|
+
description: this.description,
|
|
100
|
+
type: 'counter',
|
|
101
|
+
value: this.value,
|
|
102
|
+
labeledValues: Object.fromEntries(this.labeledValues),
|
|
103
|
+
created: this.created,
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Histogram metric for tracking distributions of values
|
|
111
|
+
*/
|
|
112
|
+
class Histogram extends EventEmitter {
|
|
113
|
+
constructor(name, description = '', buckets = [0.1, 0.5, 1, 2.5, 5, 10]) {
|
|
114
|
+
super();
|
|
115
|
+
|
|
116
|
+
this.name = name;
|
|
117
|
+
this.description = description;
|
|
118
|
+
this.buckets = [...buckets].sort((a, b) => a - b);
|
|
119
|
+
this.bucketCounts = new Map();
|
|
120
|
+
this.sum = 0;
|
|
121
|
+
this.count = 0;
|
|
122
|
+
this.labeledMetrics = new Map();
|
|
123
|
+
this.created = Date.now();
|
|
124
|
+
|
|
125
|
+
// Initialize bucket counts
|
|
126
|
+
for (const bucket of this.buckets) {
|
|
127
|
+
this.bucketCounts.set(bucket, 0);
|
|
128
|
+
}
|
|
129
|
+
this.bucketCounts.set('+Inf', 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Observe a value
|
|
134
|
+
*/
|
|
135
|
+
observe(value, labelValues = {}) {
|
|
136
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
137
|
+
throw new Error('Histogram can only observe numeric values');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (Object.keys(labelValues).length === 0) {
|
|
141
|
+
this._observeUnlabeled(value);
|
|
142
|
+
} else {
|
|
143
|
+
this._observeLabeled(value, labelValues);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.emit('observe', { value, labelValues, timestamp: Date.now() });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Observe unlabeled value
|
|
151
|
+
*/
|
|
152
|
+
_observeUnlabeled(value) {
|
|
153
|
+
this.sum += value;
|
|
154
|
+
this.count++;
|
|
155
|
+
|
|
156
|
+
// Update bucket counts
|
|
157
|
+
for (const bucket of this.buckets) {
|
|
158
|
+
if (value <= bucket) {
|
|
159
|
+
this.bucketCounts.set(bucket, this.bucketCounts.get(bucket) + 1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
this.bucketCounts.set('+Inf', this.bucketCounts.get('+Inf') + 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Observe labeled value
|
|
167
|
+
*/
|
|
168
|
+
_observeLabeled(value, labelValues) {
|
|
169
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
170
|
+
|
|
171
|
+
if (!this.labeledMetrics.has(labelKey)) {
|
|
172
|
+
this.labeledMetrics.set(labelKey, {
|
|
173
|
+
sum: 0,
|
|
174
|
+
count: 0,
|
|
175
|
+
bucketCounts: new Map(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Initialize buckets for this label combination
|
|
179
|
+
const metric = this.labeledMetrics.get(labelKey);
|
|
180
|
+
for (const bucket of this.buckets) {
|
|
181
|
+
metric.bucketCounts.set(bucket, 0);
|
|
182
|
+
}
|
|
183
|
+
metric.bucketCounts.set('+Inf', 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const metric = this.labeledMetrics.get(labelKey);
|
|
187
|
+
metric.sum += value;
|
|
188
|
+
metric.count++;
|
|
189
|
+
|
|
190
|
+
// Update bucket counts
|
|
191
|
+
for (const bucket of this.buckets) {
|
|
192
|
+
if (value <= bucket) {
|
|
193
|
+
metric.bucketCounts.set(bucket, metric.bucketCounts.get(bucket) + 1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
metric.bucketCounts.set('+Inf', metric.bucketCounts.get('+Inf') + 1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get histogram statistics
|
|
201
|
+
*/
|
|
202
|
+
getStats(labelValues = {}) {
|
|
203
|
+
if (Object.keys(labelValues).length === 0) {
|
|
204
|
+
return {
|
|
205
|
+
count: this.count,
|
|
206
|
+
sum: this.sum,
|
|
207
|
+
average: this.count > 0 ? this.sum / this.count : 0,
|
|
208
|
+
buckets: Object.fromEntries(this.bucketCounts),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
213
|
+
const metric = this.labeledMetrics.get(labelKey);
|
|
214
|
+
|
|
215
|
+
if (!metric) {
|
|
216
|
+
return {
|
|
217
|
+
count: 0,
|
|
218
|
+
sum: 0,
|
|
219
|
+
average: 0,
|
|
220
|
+
buckets: {},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
count: metric.count,
|
|
226
|
+
sum: metric.sum,
|
|
227
|
+
average: metric.count > 0 ? metric.sum / metric.count : 0,
|
|
228
|
+
buckets: Object.fromEntries(metric.bucketCounts),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Calculate percentiles
|
|
234
|
+
*/
|
|
235
|
+
getPercentiles(percentiles = [50, 90, 95, 99], labelValues = {}) {
|
|
236
|
+
const stats = this.getStats(labelValues);
|
|
237
|
+
const result = {};
|
|
238
|
+
|
|
239
|
+
if (stats.count === 0) {
|
|
240
|
+
for (const p of percentiles) {
|
|
241
|
+
result[`p${p}`] = 0;
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const percentile of percentiles) {
|
|
247
|
+
const targetCount = Math.ceil((percentile / 100) * stats.count);
|
|
248
|
+
let cumulativeCount = 0;
|
|
249
|
+
|
|
250
|
+
for (const [bucket, count] of Object.entries(stats.buckets)) {
|
|
251
|
+
cumulativeCount += count;
|
|
252
|
+
if (cumulativeCount >= targetCount) {
|
|
253
|
+
result[`p${percentile}`] =
|
|
254
|
+
bucket === '+Inf' ? Infinity : parseFloat(bucket);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Generate label key from label values
|
|
265
|
+
*/
|
|
266
|
+
_getLabelKey(labelValues) {
|
|
267
|
+
const sortedKeys = Object.keys(labelValues).sort();
|
|
268
|
+
return sortedKeys.map((key) => `${key}=${labelValues[key]}`).join(',');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Reset histogram
|
|
273
|
+
*/
|
|
274
|
+
reset(labelValues = {}) {
|
|
275
|
+
if (Object.keys(labelValues).length === 0) {
|
|
276
|
+
this.sum = 0;
|
|
277
|
+
this.count = 0;
|
|
278
|
+
for (const bucket of this.buckets) {
|
|
279
|
+
this.bucketCounts.set(bucket, 0);
|
|
280
|
+
}
|
|
281
|
+
this.bucketCounts.set('+Inf', 0);
|
|
282
|
+
} else {
|
|
283
|
+
const labelKey = this._getLabelKey(labelValues);
|
|
284
|
+
this.labeledMetrics.delete(labelKey);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.emit('reset', { labelValues, timestamp: Date.now() });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Export metric data
|
|
292
|
+
*/
|
|
293
|
+
export() {
|
|
294
|
+
const labeledData = {};
|
|
295
|
+
for (const [labelKey, metric] of this.labeledMetrics) {
|
|
296
|
+
labeledData[labelKey] = {
|
|
297
|
+
count: metric.count,
|
|
298
|
+
sum: metric.sum,
|
|
299
|
+
buckets: Object.fromEntries(metric.bucketCounts),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
name: this.name,
|
|
305
|
+
description: this.description,
|
|
306
|
+
type: 'histogram',
|
|
307
|
+
buckets: this.buckets,
|
|
308
|
+
count: this.count,
|
|
309
|
+
sum: this.sum,
|
|
310
|
+
bucketCounts: Object.fromEntries(this.bucketCounts),
|
|
311
|
+
labeledMetrics: labeledData,
|
|
312
|
+
created: this.created,
|
|
313
|
+
timestamp: Date.now(),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Pipeline-specific metrics collector
|
|
320
|
+
*/
|
|
321
|
+
class PipelineMetrics extends EventEmitter {
|
|
322
|
+
constructor(pipelineId, options = {}) {
|
|
323
|
+
super();
|
|
324
|
+
|
|
325
|
+
this.pipelineId = pipelineId;
|
|
326
|
+
this.options = {
|
|
327
|
+
enableDetailedMetrics: options.enableDetailedMetrics !== false,
|
|
328
|
+
enablePerformanceMetrics: options.enablePerformanceMetrics !== false,
|
|
329
|
+
enableErrorMetrics: options.enableErrorMetrics !== false,
|
|
330
|
+
retentionPeriod: options.retentionPeriod || 24 * 60 * 60 * 1000, // 24 hours
|
|
331
|
+
...options,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
this.metrics = new Map();
|
|
335
|
+
this.startTime = Date.now();
|
|
336
|
+
this.executionHistory = [];
|
|
337
|
+
|
|
338
|
+
this.initializeMetrics();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Initialize standard pipeline metrics
|
|
343
|
+
*/
|
|
344
|
+
initializeMetrics() {
|
|
345
|
+
// Execution counters
|
|
346
|
+
this.metrics.set(
|
|
347
|
+
'executions_total',
|
|
348
|
+
new Counter(
|
|
349
|
+
'pipeline_executions_total',
|
|
350
|
+
'Total number of pipeline executions',
|
|
351
|
+
['status', 'component'],
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
this.metrics.set(
|
|
356
|
+
'documents_processed',
|
|
357
|
+
new Counter(
|
|
358
|
+
'pipeline_documents_processed_total',
|
|
359
|
+
'Total number of documents processed',
|
|
360
|
+
['component', 'source'],
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
this.metrics.set(
|
|
365
|
+
'errors_total',
|
|
366
|
+
new Counter('pipeline_errors_total', 'Total number of errors', [
|
|
367
|
+
'component',
|
|
368
|
+
'error_type',
|
|
369
|
+
]),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Duration histograms
|
|
373
|
+
this.metrics.set(
|
|
374
|
+
'execution_duration',
|
|
375
|
+
new Histogram(
|
|
376
|
+
'pipeline_execution_duration_seconds',
|
|
377
|
+
'Pipeline execution duration in seconds',
|
|
378
|
+
[0.1, 0.5, 1, 2, 5, 10, 30, 60],
|
|
379
|
+
),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
this.metrics.set(
|
|
383
|
+
'component_duration',
|
|
384
|
+
new Histogram(
|
|
385
|
+
'pipeline_component_duration_seconds',
|
|
386
|
+
'Component execution duration in seconds',
|
|
387
|
+
[0.01, 0.05, 0.1, 0.5, 1, 2, 5],
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Performance metrics
|
|
392
|
+
if (this.options.enablePerformanceMetrics) {
|
|
393
|
+
this.metrics.set(
|
|
394
|
+
'memory_usage',
|
|
395
|
+
new Histogram(
|
|
396
|
+
'pipeline_memory_usage_bytes',
|
|
397
|
+
'Memory usage during pipeline execution',
|
|
398
|
+
[1e6, 10e6, 50e6, 100e6, 500e6, 1e9], // 1MB to 1GB
|
|
399
|
+
),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
this.metrics.set(
|
|
403
|
+
'throughput',
|
|
404
|
+
new Histogram(
|
|
405
|
+
'pipeline_throughput_docs_per_second',
|
|
406
|
+
'Document processing throughput',
|
|
407
|
+
[1, 5, 10, 50, 100, 500],
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Record pipeline execution start
|
|
415
|
+
*/
|
|
416
|
+
recordExecutionStart(executionId, metadata = {}) {
|
|
417
|
+
const execution = {
|
|
418
|
+
id: executionId,
|
|
419
|
+
startTime: Date.now(),
|
|
420
|
+
endTime: null,
|
|
421
|
+
status: 'running',
|
|
422
|
+
metadata,
|
|
423
|
+
components: [],
|
|
424
|
+
errors: [],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
this.executionHistory.push(execution);
|
|
428
|
+
this.emit('executionStarted', { executionId, execution });
|
|
429
|
+
|
|
430
|
+
return execution;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Record pipeline execution completion
|
|
435
|
+
*/
|
|
436
|
+
recordExecutionEnd(executionId, status = 'success', metadata = {}) {
|
|
437
|
+
const execution = this.executionHistory.find((e) => e.id === executionId);
|
|
438
|
+
if (!execution) {
|
|
439
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
execution.endTime = Date.now();
|
|
443
|
+
execution.status = status;
|
|
444
|
+
execution.metadata = { ...execution.metadata, ...metadata };
|
|
445
|
+
|
|
446
|
+
const duration = (execution.endTime - execution.startTime) / 1000; // Convert to seconds
|
|
447
|
+
|
|
448
|
+
// Update metrics
|
|
449
|
+
this.metrics.get('executions_total').inc(1, { status });
|
|
450
|
+
this.metrics.get('execution_duration').observe(duration);
|
|
451
|
+
|
|
452
|
+
this.emit('executionCompleted', { executionId, execution, duration });
|
|
453
|
+
|
|
454
|
+
return execution;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Record component execution
|
|
459
|
+
*/
|
|
460
|
+
recordComponentExecution(
|
|
461
|
+
executionId,
|
|
462
|
+
componentId,
|
|
463
|
+
componentType,
|
|
464
|
+
duration,
|
|
465
|
+
status = 'success',
|
|
466
|
+
metadata = {},
|
|
467
|
+
) {
|
|
468
|
+
const execution = this.executionHistory.find((e) => e.id === executionId);
|
|
469
|
+
if (execution) {
|
|
470
|
+
execution.components.push({
|
|
471
|
+
id: componentId,
|
|
472
|
+
type: componentType,
|
|
473
|
+
duration,
|
|
474
|
+
status,
|
|
475
|
+
metadata,
|
|
476
|
+
timestamp: Date.now(),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Update metrics
|
|
481
|
+
this.metrics
|
|
482
|
+
.get('executions_total')
|
|
483
|
+
.inc(1, { status, component: componentType });
|
|
484
|
+
this.metrics
|
|
485
|
+
.get('component_duration')
|
|
486
|
+
.observe(duration / 1000, { component: componentType });
|
|
487
|
+
|
|
488
|
+
this.emit('componentExecuted', {
|
|
489
|
+
executionId,
|
|
490
|
+
componentId,
|
|
491
|
+
componentType,
|
|
492
|
+
duration,
|
|
493
|
+
status,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Record document processing
|
|
499
|
+
*/
|
|
500
|
+
recordDocumentProcessed(componentType, source = 'unknown', count = 1) {
|
|
501
|
+
this.metrics
|
|
502
|
+
.get('documents_processed')
|
|
503
|
+
.inc(count, { component: componentType, source });
|
|
504
|
+
this.emit('documentsProcessed', { componentType, source, count });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Record error
|
|
509
|
+
*/
|
|
510
|
+
recordError(componentType, errorType, error, executionId = null) {
|
|
511
|
+
this.metrics
|
|
512
|
+
.get('errors_total')
|
|
513
|
+
.inc(1, { component: componentType, error_type: errorType });
|
|
514
|
+
|
|
515
|
+
if (executionId) {
|
|
516
|
+
const execution = this.executionHistory.find((e) => e.id === executionId);
|
|
517
|
+
if (execution) {
|
|
518
|
+
execution.errors.push({
|
|
519
|
+
componentType,
|
|
520
|
+
errorType,
|
|
521
|
+
message: error.message,
|
|
522
|
+
timestamp: Date.now(),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
this.emit('errorRecorded', {
|
|
528
|
+
componentType,
|
|
529
|
+
errorType,
|
|
530
|
+
error,
|
|
531
|
+
executionId,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Record performance metrics
|
|
537
|
+
*/
|
|
538
|
+
recordPerformanceMetrics(memoryUsage, throughput) {
|
|
539
|
+
if (this.options.enablePerformanceMetrics) {
|
|
540
|
+
if (memoryUsage !== undefined) {
|
|
541
|
+
this.metrics.get('memory_usage').observe(memoryUsage);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (throughput !== undefined) {
|
|
545
|
+
this.metrics.get('throughput').observe(throughput);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.emit('performanceRecorded', { memoryUsage, throughput });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Get metric by name
|
|
554
|
+
*/
|
|
555
|
+
getMetric(name) {
|
|
556
|
+
return this.metrics.get(name);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get all metrics
|
|
561
|
+
*/
|
|
562
|
+
getAllMetrics() {
|
|
563
|
+
const result = {};
|
|
564
|
+
for (const [name, metric] of this.metrics) {
|
|
565
|
+
result[name] = metric.export();
|
|
566
|
+
}
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Get pipeline statistics
|
|
572
|
+
*/
|
|
573
|
+
getStatistics() {
|
|
574
|
+
const now = Date.now();
|
|
575
|
+
const uptime = now - this.startTime;
|
|
576
|
+
|
|
577
|
+
const totalExecutions = this.metrics.get('executions_total').get();
|
|
578
|
+
const totalErrors = this.metrics.get('errors_total').get();
|
|
579
|
+
const totalDocuments = this.metrics.get('documents_processed').get();
|
|
580
|
+
|
|
581
|
+
const executionStats = this.metrics.get('execution_duration').getStats();
|
|
582
|
+
const componentStats = this.metrics.get('component_duration').getStats();
|
|
583
|
+
|
|
584
|
+
return {
|
|
585
|
+
pipelineId: this.pipelineId,
|
|
586
|
+
uptime,
|
|
587
|
+
totalExecutions,
|
|
588
|
+
totalErrors,
|
|
589
|
+
totalDocuments,
|
|
590
|
+
errorRate:
|
|
591
|
+
totalExecutions > 0 ? (totalErrors / totalExecutions) * 100 : 0,
|
|
592
|
+
averageExecutionTime: executionStats.average,
|
|
593
|
+
averageComponentTime: componentStats.average,
|
|
594
|
+
throughput: uptime > 0 ? totalDocuments / (uptime / 1000) : 0, // docs per second
|
|
595
|
+
recentExecutions: this.executionHistory.slice(-10),
|
|
596
|
+
timestamp: now,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Export all metrics data
|
|
602
|
+
*/
|
|
603
|
+
exportMetrics(format = 'json') {
|
|
604
|
+
const data = {
|
|
605
|
+
pipelineId: this.pipelineId,
|
|
606
|
+
timestamp: Date.now(),
|
|
607
|
+
uptime: Date.now() - this.startTime,
|
|
608
|
+
metrics: this.getAllMetrics(),
|
|
609
|
+
statistics: this.getStatistics(),
|
|
610
|
+
executionHistory: this.executionHistory,
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
if (format === 'prometheus') {
|
|
614
|
+
return this._exportPrometheusFormat(data);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return data;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Export metrics in Prometheus format
|
|
622
|
+
*/
|
|
623
|
+
_exportPrometheusFormat(data) {
|
|
624
|
+
const lines = [];
|
|
625
|
+
|
|
626
|
+
for (const [name, metric] of Object.entries(data.metrics)) {
|
|
627
|
+
lines.push(`# HELP ${metric.name} ${metric.description}`);
|
|
628
|
+
lines.push(`# TYPE ${metric.name} ${metric.type}`);
|
|
629
|
+
|
|
630
|
+
if (metric.type === 'counter') {
|
|
631
|
+
if (
|
|
632
|
+
metric.labeledValues &&
|
|
633
|
+
Object.keys(metric.labeledValues).length > 0
|
|
634
|
+
) {
|
|
635
|
+
for (const [labels, value] of Object.entries(metric.labeledValues)) {
|
|
636
|
+
lines.push(`${metric.name}{${labels}} ${value}`);
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
lines.push(`${metric.name} ${metric.value}`);
|
|
640
|
+
}
|
|
641
|
+
} else if (metric.type === 'histogram') {
|
|
642
|
+
// Histogram buckets
|
|
643
|
+
for (const [bucket, count] of Object.entries(metric.bucketCounts)) {
|
|
644
|
+
const le = bucket === '+Inf' ? '+Inf' : bucket;
|
|
645
|
+
lines.push(`${metric.name}_bucket{le="${le}"} ${count}`);
|
|
646
|
+
}
|
|
647
|
+
lines.push(`${metric.name}_sum ${metric.sum}`);
|
|
648
|
+
lines.push(`${metric.name}_count ${metric.count}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
lines.push('');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return lines.join('\n');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Clean up old execution history
|
|
659
|
+
*/
|
|
660
|
+
cleanup() {
|
|
661
|
+
const cutoffTime = Date.now() - this.options.retentionPeriod;
|
|
662
|
+
this.executionHistory = this.executionHistory.filter(
|
|
663
|
+
(e) => e.startTime > cutoffTime,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
this.emit('cleanup', {
|
|
667
|
+
removedCount: this.executionHistory.length,
|
|
668
|
+
retainedCount: this.executionHistory.length,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Reset all metrics
|
|
674
|
+
*/
|
|
675
|
+
reset() {
|
|
676
|
+
for (const metric of this.metrics.values()) {
|
|
677
|
+
metric.reset();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
this.executionHistory = [];
|
|
681
|
+
this.startTime = Date.now();
|
|
682
|
+
|
|
683
|
+
this.emit('reset', { timestamp: Date.now() });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
module.exports = {
|
|
688
|
+
Counter,
|
|
689
|
+
Histogram,
|
|
690
|
+
PipelineMetrics,
|
|
691
|
+
};
|