s3db.js 13.3.1 → 13.4.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/README.md +34 -10
- package/dist/s3db.cjs.js +102 -23
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +102 -23
- package/dist/s3db.es.js.map +1 -1
- package/package.json +15 -2
- package/src/plugins/audit.plugin.js +427 -0
- package/src/plugins/costs.plugin.js +524 -0
- package/src/plugins/fulltext.plugin.js +484 -0
- package/src/plugins/metrics.plugin.js +575 -0
- package/src/plugins/queue-consumer.plugin.js +607 -19
- package/src/plugins/state-machine.plugin.js +132 -26
|
@@ -1,3 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # MetricsPlugin - Performance & Error Monitoring for s3db.js
|
|
3
|
+
*
|
|
4
|
+
* ## Overview
|
|
5
|
+
*
|
|
6
|
+
* The MetricsPlugin provides comprehensive performance monitoring, error tracking, and
|
|
7
|
+
* Prometheus integration for s3db.js applications. Track operation counts, durations,
|
|
8
|
+
* errors, and export metrics to Prometheus for visualization.
|
|
9
|
+
*
|
|
10
|
+
* ## Features
|
|
11
|
+
*
|
|
12
|
+
* 1. **Operation Tracking** - Monitor insert, update, delete, get, list, count operations
|
|
13
|
+
* 2. **Performance Metrics** - Track operation duration and throughput
|
|
14
|
+
* 3. **Error Logging** - Capture and store error details
|
|
15
|
+
* 4. **Resource-Level Metrics** - Per-resource and global metrics
|
|
16
|
+
* 5. **Prometheus Integration** - Export metrics in Prometheus format
|
|
17
|
+
* 6. **Flexible Modes** - Standalone, integrated (with API Plugin), or auto mode
|
|
18
|
+
* 7. **Automatic Cleanup** - Retention-based cleanup of old metrics
|
|
19
|
+
* 8. **Periodic Flushing** - Configurable flush interval for metric persistence
|
|
20
|
+
*
|
|
21
|
+
* ## Configuration
|
|
22
|
+
*
|
|
23
|
+
* ```javascript
|
|
24
|
+
* import { Database } from 's3db.js';
|
|
25
|
+
* import { MetricsPlugin } from 's3db.js/plugins/metrics';
|
|
26
|
+
*
|
|
27
|
+
* // Basic configuration
|
|
28
|
+
* const db = new Database({
|
|
29
|
+
* connectionString: 's3://bucket/db'
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* await db.use(new MetricsPlugin({
|
|
33
|
+
* collectPerformance: true, // Track performance data (default: true)
|
|
34
|
+
* collectErrors: true, // Track errors (default: true)
|
|
35
|
+
* collectUsage: true, // Track usage metrics (default: true)
|
|
36
|
+
* retentionDays: 30, // Keep metrics for 30 days (default: 30)
|
|
37
|
+
* flushInterval: 60000 // Flush every 60 seconds (default: 60000)
|
|
38
|
+
* }));
|
|
39
|
+
*
|
|
40
|
+
* // With Prometheus integration
|
|
41
|
+
* await db.use(new MetricsPlugin({
|
|
42
|
+
* prometheus: {
|
|
43
|
+
* enabled: true, // Enable Prometheus export (default: true)
|
|
44
|
+
* mode: 'auto', // auto | integrated | standalone (default: 'auto')
|
|
45
|
+
* port: 9090, // Standalone server port (default: 9090)
|
|
46
|
+
* path: '/metrics', // Metrics endpoint path (default: '/metrics')
|
|
47
|
+
* includeResourceLabels: true // Include resource names in labels (default: true)
|
|
48
|
+
* }
|
|
49
|
+
* }));
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* ## Usage Examples
|
|
53
|
+
*
|
|
54
|
+
* ### Basic Metrics Collection
|
|
55
|
+
*
|
|
56
|
+
* ```javascript
|
|
57
|
+
* const db = new Database({ connectionString: 's3://bucket/db' });
|
|
58
|
+
* await db.use(new MetricsPlugin());
|
|
59
|
+
* await db.start();
|
|
60
|
+
*
|
|
61
|
+
* const users = await db.createResource({
|
|
62
|
+
* name: 'users',
|
|
63
|
+
* attributes: { name: 'string', email: 'string' }
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // Perform operations (automatically tracked)
|
|
67
|
+
* await users.insert({ id: 'u1', name: 'John', email: 'john@example.com' });
|
|
68
|
+
* await users.get('u1');
|
|
69
|
+
* await users.update('u1', { name: 'Jane' });
|
|
70
|
+
*
|
|
71
|
+
* // Get metrics
|
|
72
|
+
* const metricsPlugin = db.plugins.MetricsPlugin;
|
|
73
|
+
* const stats = await metricsPlugin.getStats();
|
|
74
|
+
*
|
|
75
|
+
* console.log(stats);
|
|
76
|
+
* // {
|
|
77
|
+
* // period: '24h',
|
|
78
|
+
* // totalOperations: 3,
|
|
79
|
+
* // totalErrors: 0,
|
|
80
|
+
* // avgResponseTime: 45.2,
|
|
81
|
+
* // operationsByType: {
|
|
82
|
+
* // insert: { count: 1, errors: 0, avgTime: 52 },
|
|
83
|
+
* // get: { count: 1, errors: 0, avgTime: 38 },
|
|
84
|
+
* // update: { count: 1, errors: 0, avgTime: 46 }
|
|
85
|
+
* // },
|
|
86
|
+
* // uptime: { startTime: '2025-01-15T...', duration: 3600000 }
|
|
87
|
+
* // }
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* ### Query Metrics
|
|
91
|
+
*
|
|
92
|
+
* ```javascript
|
|
93
|
+
* const metricsPlugin = db.plugins.MetricsPlugin;
|
|
94
|
+
*
|
|
95
|
+
* // Get all metrics for last 24 hours
|
|
96
|
+
* const allMetrics = await metricsPlugin.getMetrics({
|
|
97
|
+
* startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* // Get metrics for specific resource
|
|
101
|
+
* const userMetrics = await metricsPlugin.getMetrics({
|
|
102
|
+
* resourceName: 'users',
|
|
103
|
+
* limit: 100
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* // Get metrics for specific operation
|
|
107
|
+
* const insertMetrics = await metricsPlugin.getMetrics({
|
|
108
|
+
* operation: 'insert',
|
|
109
|
+
* startDate: '2025-01-15',
|
|
110
|
+
* endDate: '2025-01-16'
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* ### Error Tracking
|
|
115
|
+
*
|
|
116
|
+
* ```javascript
|
|
117
|
+
* const metricsPlugin = db.plugins.MetricsPlugin;
|
|
118
|
+
*
|
|
119
|
+
* // Get recent errors
|
|
120
|
+
* const errors = await metricsPlugin.getErrorLogs({
|
|
121
|
+
* limit: 50
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* console.log(errors);
|
|
125
|
+
* // [
|
|
126
|
+
* // {
|
|
127
|
+
* // id: 'error-123...',
|
|
128
|
+
* // resourceName: 'users',
|
|
129
|
+
* // operation: 'insert',
|
|
130
|
+
* // error: 'Validation failed: email is required',
|
|
131
|
+
* // timestamp: '2025-01-15T10:30:00Z',
|
|
132
|
+
* // createdAt: '2025-01-15'
|
|
133
|
+
* // }
|
|
134
|
+
* // ]
|
|
135
|
+
*
|
|
136
|
+
* // Get errors for specific resource
|
|
137
|
+
* const userErrors = await metricsPlugin.getErrorLogs({
|
|
138
|
+
* resourceName: 'users',
|
|
139
|
+
* operation: 'insert',
|
|
140
|
+
* startDate: '2025-01-15'
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* ### Performance Monitoring
|
|
145
|
+
*
|
|
146
|
+
* ```javascript
|
|
147
|
+
* const metricsPlugin = db.plugins.MetricsPlugin;
|
|
148
|
+
*
|
|
149
|
+
* // Get performance logs
|
|
150
|
+
* const perfLogs = await metricsPlugin.getPerformanceLogs({
|
|
151
|
+
* resourceName: 'users',
|
|
152
|
+
* operation: 'insert',
|
|
153
|
+
* limit: 100
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* console.log(perfLogs);
|
|
157
|
+
* // [
|
|
158
|
+
* // {
|
|
159
|
+
* // id: 'perf-123...',
|
|
160
|
+
* // resourceName: 'users',
|
|
161
|
+
* // operation: 'insert',
|
|
162
|
+
* // duration: 52,
|
|
163
|
+
* // timestamp: '2025-01-15T10:30:00Z'
|
|
164
|
+
* // }
|
|
165
|
+
* // ]
|
|
166
|
+
*
|
|
167
|
+
* // Identify slow operations
|
|
168
|
+
* const slowOps = perfLogs.filter(log => log.duration > 100);
|
|
169
|
+
* console.log(`Found ${slowOps.length} slow operations`);
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* ### Prometheus Integration
|
|
173
|
+
*
|
|
174
|
+
* ```javascript
|
|
175
|
+
* // AUTO mode: detects API Plugin and chooses mode automatically
|
|
176
|
+
* await db.use(new MetricsPlugin({
|
|
177
|
+
* prometheus: { mode: 'auto' }
|
|
178
|
+
* }));
|
|
179
|
+
*
|
|
180
|
+
* // INTEGRATED mode: uses API Plugin's server
|
|
181
|
+
* await db.use(new MetricsPlugin({
|
|
182
|
+
* prometheus: {
|
|
183
|
+
* mode: 'integrated',
|
|
184
|
+
* path: '/metrics'
|
|
185
|
+
* }
|
|
186
|
+
* }));
|
|
187
|
+
* // Metrics available at http://localhost:3000/metrics (API Plugin's port)
|
|
188
|
+
*
|
|
189
|
+
* // STANDALONE mode: separate HTTP server
|
|
190
|
+
* await db.use(new MetricsPlugin({
|
|
191
|
+
* prometheus: {
|
|
192
|
+
* mode: 'standalone',
|
|
193
|
+
* port: 9090,
|
|
194
|
+
* path: '/metrics'
|
|
195
|
+
* }
|
|
196
|
+
* }));
|
|
197
|
+
* // Metrics available at http://localhost:9090/metrics
|
|
198
|
+
*
|
|
199
|
+
* // Get Prometheus metrics manually
|
|
200
|
+
* const prometheusMetrics = await metricsPlugin.getPrometheusMetrics();
|
|
201
|
+
* console.log(prometheusMetrics);
|
|
202
|
+
* // # HELP s3db_operations_total Total number of operations
|
|
203
|
+
* // # TYPE s3db_operations_total counter
|
|
204
|
+
* // s3db_operations_total{operation="insert",resource="users"} 15
|
|
205
|
+
* // s3db_operations_total{operation="get",resource="users"} 42
|
|
206
|
+
* // ...
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* ### Cleanup Old Metrics
|
|
210
|
+
*
|
|
211
|
+
* ```javascript
|
|
212
|
+
* const metricsPlugin = db.plugins.MetricsPlugin;
|
|
213
|
+
*
|
|
214
|
+
* // Clean up metrics older than retention period
|
|
215
|
+
* await metricsPlugin.cleanupOldData();
|
|
216
|
+
*
|
|
217
|
+
* // Schedule regular cleanup (e.g., daily)
|
|
218
|
+
* setInterval(async () => {
|
|
219
|
+
* await metricsPlugin.cleanupOldData();
|
|
220
|
+
* console.log('Metrics cleanup completed');
|
|
221
|
+
* }, 24 * 60 * 60 * 1000);
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* ## Best Practices
|
|
225
|
+
*
|
|
226
|
+
* ### 1. Configure Appropriate Retention
|
|
227
|
+
*
|
|
228
|
+
* ```javascript
|
|
229
|
+
* // For production: 30-90 days
|
|
230
|
+
* await db.use(new MetricsPlugin({
|
|
231
|
+
* retentionDays: 90
|
|
232
|
+
* }));
|
|
233
|
+
*
|
|
234
|
+
* // For development: 7 days
|
|
235
|
+
* await db.use(new MetricsPlugin({
|
|
236
|
+
* retentionDays: 7
|
|
237
|
+
* }));
|
|
238
|
+
*
|
|
239
|
+
* // For high-volume: shorter retention
|
|
240
|
+
* await db.use(new MetricsPlugin({
|
|
241
|
+
* retentionDays: 14,
|
|
242
|
+
* flushInterval: 300000 // Flush every 5 minutes
|
|
243
|
+
* }));
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* ### 2. Use Prometheus for Visualization
|
|
247
|
+
*
|
|
248
|
+
* ```javascript
|
|
249
|
+
* // Enable Prometheus export
|
|
250
|
+
* await db.use(new MetricsPlugin({
|
|
251
|
+
* prometheus: { enabled: true, mode: 'standalone', port: 9090 }
|
|
252
|
+
* }));
|
|
253
|
+
*
|
|
254
|
+
* // Configure Prometheus to scrape metrics
|
|
255
|
+
* // In prometheus.yml:
|
|
256
|
+
* // scrape_configs:
|
|
257
|
+
* // - job_name: 's3db'
|
|
258
|
+
* // static_configs:
|
|
259
|
+
* // - targets: ['localhost:9090']
|
|
260
|
+
*
|
|
261
|
+
* // Use Grafana for dashboards
|
|
262
|
+
* // - Import Prometheus as data source
|
|
263
|
+
* // - Create dashboards with PromQL queries
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* ### 3. Monitor Error Rates
|
|
267
|
+
*
|
|
268
|
+
* ```javascript
|
|
269
|
+
* // Set up alerts for high error rates
|
|
270
|
+
* setInterval(async () => {
|
|
271
|
+
* const stats = await metricsPlugin.getStats();
|
|
272
|
+
* const errorRate = stats.totalErrors / stats.totalOperations;
|
|
273
|
+
*
|
|
274
|
+
* if (errorRate > 0.05) { // 5% error rate
|
|
275
|
+
* console.error(`High error rate detected: ${(errorRate * 100).toFixed(2)}%`);
|
|
276
|
+
* sendAlert({
|
|
277
|
+
* message: 'S3DB error rate exceeded threshold',
|
|
278
|
+
* errorRate,
|
|
279
|
+
* totalErrors: stats.totalErrors,
|
|
280
|
+
* totalOperations: stats.totalOperations
|
|
281
|
+
* });
|
|
282
|
+
* }
|
|
283
|
+
* }, 60000); // Check every minute
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* ### 4. Track Performance Baselines
|
|
287
|
+
*
|
|
288
|
+
* ```javascript
|
|
289
|
+
* // Establish performance baselines
|
|
290
|
+
* const baseline = {
|
|
291
|
+
* insert: 50, // ms
|
|
292
|
+
* update: 60,
|
|
293
|
+
* get: 30,
|
|
294
|
+
* list: 100
|
|
295
|
+
* };
|
|
296
|
+
*
|
|
297
|
+
* // Alert on performance degradation
|
|
298
|
+
* setInterval(async () => {
|
|
299
|
+
* const stats = await metricsPlugin.getStats();
|
|
300
|
+
*
|
|
301
|
+
* for (const [op, opStats] of Object.entries(stats.operationsByType)) {
|
|
302
|
+
* if (opStats.avgTime > baseline[op] * 1.5) { // 50% slower
|
|
303
|
+
* console.warn(`Performance degradation: ${op} is ${opStats.avgTime}ms (baseline: ${baseline[op]}ms)`);
|
|
304
|
+
* }
|
|
305
|
+
* }
|
|
306
|
+
* }, 300000); // Check every 5 minutes
|
|
307
|
+
* ```
|
|
308
|
+
*
|
|
309
|
+
* ## Performance Considerations
|
|
310
|
+
*
|
|
311
|
+
* ### Overhead
|
|
312
|
+
*
|
|
313
|
+
* - **CPU**: 1-3% overhead (timing + metric recording)
|
|
314
|
+
* - **Memory**: ~5-10KB per 1000 operations (in-memory buffer)
|
|
315
|
+
* - **Storage**: ~300-500 bytes per operation metric
|
|
316
|
+
* - **Latency**: <1ms per operation
|
|
317
|
+
*
|
|
318
|
+
* ### Optimization Tips
|
|
319
|
+
*
|
|
320
|
+
* ```javascript
|
|
321
|
+
* // 1. Disable unnecessary collection
|
|
322
|
+
* await db.use(new MetricsPlugin({
|
|
323
|
+
* collectPerformance: false, // Disable if not needed
|
|
324
|
+
* collectErrors: true // Keep error tracking
|
|
325
|
+
* }));
|
|
326
|
+
*
|
|
327
|
+
* // 2. Increase flush interval
|
|
328
|
+
* await db.use(new MetricsPlugin({
|
|
329
|
+
* flushInterval: 300000 // Flush every 5 minutes (less frequent writes)
|
|
330
|
+
* }));
|
|
331
|
+
*
|
|
332
|
+
* // 3. Shorter retention period
|
|
333
|
+
* await db.use(new MetricsPlugin({
|
|
334
|
+
* retentionDays: 14 // Less storage, faster cleanup
|
|
335
|
+
* }));
|
|
336
|
+
*
|
|
337
|
+
* // 4. Manual flush control
|
|
338
|
+
* await db.use(new MetricsPlugin({
|
|
339
|
+
* flushInterval: 0 // Disable auto-flush, flush manually
|
|
340
|
+
* }));
|
|
341
|
+
* await metricsPlugin.flushMetrics(); // Flush when needed
|
|
342
|
+
* ```
|
|
343
|
+
*
|
|
344
|
+
* ## Troubleshooting
|
|
345
|
+
*
|
|
346
|
+
* ### Metrics Not Being Collected
|
|
347
|
+
*
|
|
348
|
+
* ```javascript
|
|
349
|
+
* // Check if plugin is installed and started
|
|
350
|
+
* console.log(db.plugins.MetricsPlugin); // Should exist
|
|
351
|
+
* await db.start(); // Must call start() to activate plugin
|
|
352
|
+
*
|
|
353
|
+
* // Check if metrics resources exist
|
|
354
|
+
* console.log(db.resources.plg_metrics); // Should exist
|
|
355
|
+
* console.log(db.resources.plg_error_logs);
|
|
356
|
+
* console.log(db.resources.plg_performance_logs);
|
|
357
|
+
* ```
|
|
358
|
+
*
|
|
359
|
+
* ### Prometheus Endpoint Not Available
|
|
360
|
+
*
|
|
361
|
+
* ```javascript
|
|
362
|
+
* // Check Prometheus configuration
|
|
363
|
+
* const plugin = db.plugins.MetricsPlugin;
|
|
364
|
+
* console.log(plugin.config.prometheus);
|
|
365
|
+
*
|
|
366
|
+
* // Ensure plugin is started
|
|
367
|
+
* await db.start();
|
|
368
|
+
*
|
|
369
|
+
* // For integrated mode, ensure API Plugin is active
|
|
370
|
+
* console.log(db.plugins.api); // Should exist for integrated mode
|
|
371
|
+
*
|
|
372
|
+
* // For standalone mode, check if port is available
|
|
373
|
+
* // Try accessing: http://localhost:9090/metrics
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* ### High Storage Usage
|
|
377
|
+
*
|
|
378
|
+
* ```javascript
|
|
379
|
+
* // Check metrics count
|
|
380
|
+
* const allMetrics = await metricsPlugin.getMetrics();
|
|
381
|
+
* console.log(`Total metrics: ${allMetrics.length}`);
|
|
382
|
+
*
|
|
383
|
+
* // Solution 1: Reduce retention
|
|
384
|
+
* await db.use(new MetricsPlugin({
|
|
385
|
+
* retentionDays: 14 // Down from 30
|
|
386
|
+
* }));
|
|
387
|
+
*
|
|
388
|
+
* // Solution 2: Manual cleanup
|
|
389
|
+
* await metricsPlugin.cleanupOldData();
|
|
390
|
+
*
|
|
391
|
+
* // Solution 3: Disable performance logging
|
|
392
|
+
* await db.use(new MetricsPlugin({
|
|
393
|
+
* collectPerformance: false
|
|
394
|
+
* }));
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* ### Metrics Causing Performance Issues
|
|
398
|
+
*
|
|
399
|
+
* ```javascript
|
|
400
|
+
* // Solution 1: Increase flush interval
|
|
401
|
+
* await db.use(new MetricsPlugin({
|
|
402
|
+
* flushInterval: 600000 // Flush every 10 minutes
|
|
403
|
+
* }));
|
|
404
|
+
*
|
|
405
|
+
* // Solution 2: Disable in tests
|
|
406
|
+
* const shouldEnableMetrics = process.env.NODE_ENV !== 'test';
|
|
407
|
+
* if (shouldEnableMetrics) {
|
|
408
|
+
* await db.use(new MetricsPlugin());
|
|
409
|
+
* }
|
|
410
|
+
*
|
|
411
|
+
* // Solution 3: Selective collection
|
|
412
|
+
* await db.use(new MetricsPlugin({
|
|
413
|
+
* collectPerformance: false, // Disable performance logging
|
|
414
|
+
* collectErrors: true // Keep error tracking
|
|
415
|
+
* }));
|
|
416
|
+
* ```
|
|
417
|
+
*
|
|
418
|
+
* ## Real-World Use Cases
|
|
419
|
+
*
|
|
420
|
+
* ### 1. Production Monitoring Dashboard
|
|
421
|
+
*
|
|
422
|
+
* ```javascript
|
|
423
|
+
* // Set up comprehensive monitoring
|
|
424
|
+
* await db.use(new MetricsPlugin({
|
|
425
|
+
* retentionDays: 90,
|
|
426
|
+
* prometheus: {
|
|
427
|
+
* enabled: true,
|
|
428
|
+
* mode: 'standalone',
|
|
429
|
+
* port: 9090
|
|
430
|
+
* }
|
|
431
|
+
* }));
|
|
432
|
+
*
|
|
433
|
+
* // Generate daily reports
|
|
434
|
+
* setInterval(async () => {
|
|
435
|
+
* const stats = await metricsPlugin.getStats();
|
|
436
|
+
* const errors = await metricsPlugin.getErrorLogs({ limit: 10 });
|
|
437
|
+
*
|
|
438
|
+
* const report = {
|
|
439
|
+
* date: new Date().toISOString(),
|
|
440
|
+
* totalOps: stats.totalOperations,
|
|
441
|
+
* avgResponseTime: stats.avgResponseTime,
|
|
442
|
+
* errorCount: stats.totalErrors,
|
|
443
|
+
* topErrors: errors.slice(0, 5),
|
|
444
|
+
* operationBreakdown: stats.operationsByType
|
|
445
|
+
* };
|
|
446
|
+
*
|
|
447
|
+
* sendDailyReport(report);
|
|
448
|
+
* }, 24 * 60 * 60 * 1000);
|
|
449
|
+
* ```
|
|
450
|
+
*
|
|
451
|
+
* ### 2. Performance Regression Detection
|
|
452
|
+
*
|
|
453
|
+
* ```javascript
|
|
454
|
+
* // Track performance over time
|
|
455
|
+
* const performanceBaseline = {};
|
|
456
|
+
*
|
|
457
|
+
* setInterval(async () => {
|
|
458
|
+
* const stats = await metricsPlugin.getStats();
|
|
459
|
+
*
|
|
460
|
+
* for (const [op, opStats] of Object.entries(stats.operationsByType)) {
|
|
461
|
+
* if (!performanceBaseline[op]) {
|
|
462
|
+
* performanceBaseline[op] = opStats.avgTime;
|
|
463
|
+
* }
|
|
464
|
+
*
|
|
465
|
+
* const degradation = ((opStats.avgTime / performanceBaseline[op]) - 1) * 100;
|
|
466
|
+
*
|
|
467
|
+
* if (degradation > 50) { // 50% slower
|
|
468
|
+
* console.error(`Performance regression: ${op} is ${degradation.toFixed(1)}% slower`);
|
|
469
|
+
* createIncident({
|
|
470
|
+
* title: `S3DB Performance Regression: ${op}`,
|
|
471
|
+
* description: `${op} operation is ${degradation.toFixed(1)}% slower than baseline`,
|
|
472
|
+
* baseline: performanceBaseline[op],
|
|
473
|
+
* current: opStats.avgTime
|
|
474
|
+
* });
|
|
475
|
+
* }
|
|
476
|
+
* }
|
|
477
|
+
* }, 300000); // Check every 5 minutes
|
|
478
|
+
* ```
|
|
479
|
+
*
|
|
480
|
+
* ### 3. SLA Monitoring
|
|
481
|
+
*
|
|
482
|
+
* ```javascript
|
|
483
|
+
* // Monitor SLA compliance (99.9% uptime, <100ms avg response time)
|
|
484
|
+
* setInterval(async () => {
|
|
485
|
+
* const stats = await metricsPlugin.getStats();
|
|
486
|
+
*
|
|
487
|
+
* const errorRate = stats.totalErrors / stats.totalOperations;
|
|
488
|
+
* const slaCompliance = {
|
|
489
|
+
* uptime: (1 - errorRate) * 100,
|
|
490
|
+
* avgResponseTime: stats.avgResponseTime,
|
|
491
|
+
* meetsUptime: errorRate < 0.001, // 99.9%
|
|
492
|
+
* meetsPerformance: stats.avgResponseTime < 100
|
|
493
|
+
* };
|
|
494
|
+
*
|
|
495
|
+
* if (!slaCompliance.meetsUptime || !slaCompliance.meetsPerformance) {
|
|
496
|
+
* sendSLAAlert(slaCompliance);
|
|
497
|
+
* }
|
|
498
|
+
*
|
|
499
|
+
* logSLACompliance(slaCompliance);
|
|
500
|
+
* }, 60000); // Check every minute
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* ### 4. Cost Optimization Analysis
|
|
504
|
+
*
|
|
505
|
+
* ```javascript
|
|
506
|
+
* // Analyze operation patterns to optimize costs
|
|
507
|
+
* setInterval(async () => {
|
|
508
|
+
* const stats = await metricsPlugin.getStats();
|
|
509
|
+
*
|
|
510
|
+
* const report = {
|
|
511
|
+
* totalOps: stats.totalOperations,
|
|
512
|
+
* breakdown: {
|
|
513
|
+
* expensive: stats.operationsByType.insert?.count || 0 +
|
|
514
|
+
* stats.operationsByType.update?.count || 0,
|
|
515
|
+
* cheap: stats.operationsByType.get?.count || 0
|
|
516
|
+
* }
|
|
517
|
+
* };
|
|
518
|
+
*
|
|
519
|
+
* // Suggest optimizations
|
|
520
|
+
* if (report.breakdown.expensive > report.breakdown.cheap * 2) {
|
|
521
|
+
* console.warn('High write-to-read ratio detected. Consider caching to reduce costs.');
|
|
522
|
+
* }
|
|
523
|
+
* }, 24 * 60 * 60 * 1000); // Daily analysis
|
|
524
|
+
* ```
|
|
525
|
+
*
|
|
526
|
+
* ## API Reference
|
|
527
|
+
*
|
|
528
|
+
* ### Constructor Options
|
|
529
|
+
*
|
|
530
|
+
* - `collectPerformance` (boolean, default: true) - Track performance metrics
|
|
531
|
+
* - `collectErrors` (boolean, default: true) - Track errors
|
|
532
|
+
* - `collectUsage` (boolean, default: true) - Track usage metrics
|
|
533
|
+
* - `retentionDays` (number, default: 30) - Retention period for metrics
|
|
534
|
+
* - `flushInterval` (number, default: 60000) - Flush interval in milliseconds
|
|
535
|
+
* - `prometheus` (object) - Prometheus configuration
|
|
536
|
+
* - `enabled` (boolean, default: true) - Enable Prometheus export
|
|
537
|
+
* - `mode` (string, default: 'auto') - 'auto' | 'integrated' | 'standalone'
|
|
538
|
+
* - `port` (number, default: 9090) - Standalone server port
|
|
539
|
+
* - `path` (string, default: '/metrics') - Metrics endpoint path
|
|
540
|
+
* - `includeResourceLabels` (boolean, default: true) - Include resource names
|
|
541
|
+
*
|
|
542
|
+
* ### Methods
|
|
543
|
+
*
|
|
544
|
+
* - `getMetrics(options)` - Query metrics with filters
|
|
545
|
+
* - `getErrorLogs(options)` - Get error logs
|
|
546
|
+
* - `getPerformanceLogs(options)` - Get performance logs
|
|
547
|
+
* - `getStats()` - Get aggregated statistics (last 24h)
|
|
548
|
+
* - `getPrometheusMetrics()` - Get Prometheus-formatted metrics
|
|
549
|
+
* - `cleanupOldData()` - Delete old metrics based on retention period
|
|
550
|
+
* - `flushMetrics()` - Manually flush metrics to storage
|
|
551
|
+
*
|
|
552
|
+
* ### Query Options
|
|
553
|
+
*
|
|
554
|
+
* ```typescript
|
|
555
|
+
* interface MetricsQueryOptions {
|
|
556
|
+
* type?: string; // 'operation' | 'error' | 'performance'
|
|
557
|
+
* resourceName?: string; // Filter by resource
|
|
558
|
+
* operation?: string; // Filter by operation
|
|
559
|
+
* startDate?: string; // Filter by start date (ISO format)
|
|
560
|
+
* endDate?: string; // Filter by end date (ISO format)
|
|
561
|
+
* limit?: number; // Max results (default: 100)
|
|
562
|
+
* offset?: number; // Pagination offset (default: 0)
|
|
563
|
+
* }
|
|
564
|
+
* ```
|
|
565
|
+
*
|
|
566
|
+
* ## Notes
|
|
567
|
+
*
|
|
568
|
+
* - Plugin creates 3 resources: plg_metrics, plg_error_logs, plg_performance_logs
|
|
569
|
+
* - All resources use date partitioning for efficient queries
|
|
570
|
+
* - Metrics flush automatically on plugin stop
|
|
571
|
+
* - Flush timer is disabled during tests (NODE_ENV=test)
|
|
572
|
+
* - Prometheus mode 'auto' detects API Plugin and chooses best mode
|
|
573
|
+
* - Standalone Prometheus server listens on 0.0.0.0 (all interfaces)
|
|
574
|
+
*/
|
|
575
|
+
|
|
1
576
|
import { Plugin } from "./plugin.class.js";
|
|
2
577
|
import tryFn from "../concerns/try-fn.js";
|
|
3
578
|
|