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.
@@ -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