UNPKG

@allan1361/iota-big3-sdk-middleware

Version:

🏆 A+ Grade Certified Enterprise Middleware Framework - Phase 3 Certified (90/100) with advanced resilience patterns, comprehensive type safety, and production-ready observability

443 lines 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerformanceMonitor = void 0; exports.createPerformanceMonitor = createPerformanceMonitor; const tslib_1 = require("tslib"); const events_1 = require("events"); const crypto = tslib_1.__importStar(require("crypto")); class PerformanceMonitor extends events_1.EventEmitter { constructor(config = {}) { super(); this.metrics = new Map(); this.activeRequests = new Map(); this.dataPoints = []; this.config = { enabled: config.enabled ?? true, sampleRate: config.sampleRate ?? 1.0, metrics: { latency: config.metrics?.latency ?? true, cpu: config.metrics?.cpu ?? true, memory: config.metrics?.memory ?? true, throughput: config.metrics?.throughput ?? true, errors: config.metrics?.errors ?? true }, thresholds: { latencyWarn: config.thresholds?.latencyWarn ?? 100, latencyCritical: config.thresholds?.latencyCritical ?? 500, cpuWarn: config.thresholds?.cpuWarn ?? 80, memoryWarn: config.thresholds?.memoryWarn ?? 500 }, reporting: { interval: config.reporting?.interval ?? 60000, destination: config.reporting?.destination ?? 'console', format: config.reporting?.format ?? 'json' }, storage: { maxDataPoints: config.storage?.maxDataPoints ?? 1000, aggregationInterval: config.storage?.aggregationInterval ?? 10000 } }; if (this.config.enabled) { this.startMonitoring(); } } wrap(name, middleware) { if (!this.config.enabled) { return middleware; } if (!this.metrics.has(name)) { this.initializeMetrics(name); } return async (req, res, next) => { if (crypto.randomInt(0, Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER > this.config.sampleRate) { return middleware(req, res, next); } const requestId = this.getRequestId(req); const startTime = process.hrtime.bigint(); const startCpu = process.cpuUsage(); const startMem = process.memoryUsage(); let trackedRequest = this.activeRequests.get(requestId); if (!trackedRequest) { trackedRequest = { id: requestId, startTime: Date.now(), middlewareTimes: new Map(), bytesRead: 0, bytesWritten: 0 }; this.activeRequests.set(requestId, trackedRequest); } trackedRequest.middlewareTimes.set(name, { start: Date.now() }); const wrappedNext = (error) => { const endTime = process.hrtime.bigint(); const duration = Number(endTime - startTime) / 1e6; const middlewareTime = trackedRequest.middlewareTimes.get(name); if (middlewareTime) { middlewareTime.end = Date.now(); } this.updateMetrics(name, { duration, error, cpu: process.cpuUsage(startCpu), memory: process.memoryUsage() }); this.checkThresholds(name, duration); next(error); }; try { const result = middleware(req, res, wrappedNext); if (result && typeof result.then === 'function') { result .then(() => { if (!res.headersSent) { wrappedNext(); } }) .catch((error) => { wrappedNext(error); }); } } catch (error) { wrappedNext(error); } }; } monitorApp(app) { if (!this.config.enabled) return; const originalUse = app.use.bind(app); app.use = (...args) => { const [path, ...handlers] = args; if (typeof path === 'function') { handlers.unshift(path); const wrappedHandlers = handlers.map((handler, index) => this.wrap(`middleware_${index}`, handler)); return originalUse(...wrappedHandlers); } else { const wrappedHandlers = handlers.map((handler, index) => this.wrap(`${path}_${index}`, handler)); return originalUse(path, ...wrappedHandlers); } }; app.use((req, res, next) => { const requestId = this.generateRequestId(); req.__requestId = requestId; this.emit('request:start', { requestId, method: req.method, path: req.path }); const originalSend = res.send; res.send = function (data) { const request = this.activeRequests.get(requestId); if (request) { request.totalTime = Date.now() - request.startTime; request.statusCode = res.statusCode; request.bytesWritten = Buffer.byteLength(data); } this.emit('request:end', { requestId, duration: request?.totalTime, statusCode: res.statusCode }); this.activeRequests.delete(requestId); return originalSend.call(res, data); }.bind(this); next(); }); } initializeMetrics(name) { this.metrics.set(name, { name, latency: { count: 0, min: Infinity, max: 0, mean: 0, median: 0, p75: 0, p95: 0, p99: 0, histogram: new Map() }, throughput: { requestsPerSecond: 0, bytesPerSecond: 0, totalRequests: 0, totalBytes: 0 }, errors: { totalErrors: 0, errorRate: 0, errorsByType: new Map(), errorsByStatusCode: new Map() }, resources: { cpu: { usage: 0, user: 0, system: 0 }, memory: { heapUsed: 0, heapTotal: 0, external: 0, rss: 0 } } }); } updateMetrics(name, data) { const metrics = this.metrics.get(name); if (!metrics) return; if (this.config.metrics.latency) { const latency = metrics.latency; latency.count++; latency.min = Math.min(latency.min, data.duration); latency.max = Math.max(latency.max, data.duration); latency.mean = ((latency.mean * (latency.count - 1)) + data.duration) / latency.count; const bucket = Math.floor(data.duration / 10) * 10; latency.histogram.set(bucket, (latency.histogram.get(bucket) || 0) + 1); } if (this.config.metrics.throughput) { metrics.throughput.totalRequests++; } if (this.config.metrics.errors && data.error) { metrics.errors.totalErrors++; const errorType = data.error.constructor.name; metrics.errors.errorsByType.set(errorType, (metrics.errors.errorsByType.get(errorType) || 0) + 1); } if (this.config.metrics.cpu && data.cpu) { metrics.resources.cpu = { usage: (data.cpu.user + data.cpu.system) / 1000000, user: data.cpu.user / 1000000, system: data.cpu.system / 1000000 }; } if (this.config.metrics.memory && data.memory) { metrics.resources.memory = { heapUsed: data.memory.heapUsed / 1024 / 1024, heapTotal: data.memory.heapTotal / 1024 / 1024, external: data.memory.external / 1024 / 1024, rss: data.memory.rss / 1024 / 1024 }; } } checkThresholds(name, duration) { if (duration > this.config.thresholds.latencyCritical) { this.emit('threshold:critical', { middleware: name, metric: 'latency', value: duration, threshold: this.config.thresholds.latencyCritical }); } else if (duration > this.config.thresholds.latencyWarn) { this.emit('threshold:warning', { middleware: name, metric: 'latency', value: duration, threshold: this.config.thresholds.latencyWarn }); } } startMonitoring() { if (this.config.reporting.interval > 0) { this.reportingInterval = setInterval(() => { this.generateReport(); }, this.config.reporting.interval); } if (this.config.storage.aggregationInterval > 0) { this.aggregationInterval = setInterval(() => { this.aggregateMetrics(); }, this.config.storage.aggregationInterval); } } generateReport() { const report = this.getReport(); switch (this.config.reporting.destination) { case 'console': console.log(this.formatReport(report)); break; case 'file': break; case 'metrics-service': this.emit('report', report); break; } } formatReport(report) { switch (this.config.reporting.format) { case 'json': return JSON.stringify(report, null, 2); case 'prometheus': return this.formatPrometheus(report); case 'statsd': return this.formatStatsd(report); default: return JSON.stringify(report); } } formatPrometheus(report) { let output = ''; for (const [name, metrics] of report.middlewares) { const safeName = name.replace(/[^a-zA-Z0-9_]/g, '_'); output += `# HELP middleware_latency_seconds Middleware execution time\n`; output += `# TYPE middleware_latency_seconds histogram\n`; output += `middleware_latency_seconds_sum{middleware="${safeName}"} ${metrics.latency.mean * metrics.latency.count / 1000}\n`; output += `middleware_latency_seconds_count{middleware="${safeName}"} ${metrics.latency.count}\n`; output += `middleware_latency_seconds{middleware="${safeName}",quantile="0.5"} ${metrics.latency.median / 1000}\n`; output += `middleware_latency_seconds{middleware="${safeName}",quantile="0.75"} ${metrics.latency.p75 / 1000}\n`; output += `middleware_latency_seconds{middleware="${safeName}",quantile="0.95"} ${metrics.latency.p95 / 1000}\n`; output += `middleware_latency_seconds{middleware="${safeName}",quantile="0.99"} ${metrics.latency.p99 / 1000}\n`; output += `# HELP middleware_errors_total Total middleware errors\n`; output += `# TYPE middleware_errors_total counter\n`; output += `middleware_errors_total{middleware="${safeName}"} ${metrics.errors.totalErrors}\n`; output += `# HELP middleware_requests_total Total requests processed\n`; output += `# TYPE middleware_requests_total counter\n`; output += `middleware_requests_total{middleware="${safeName}"} ${metrics.throughput.totalRequests}\n`; } return output; } formatStatsd(report) { const lines = []; for (const [name, metrics] of report.middlewares) { const safeName = name.replace(/[^a-zA-Z0-9_]/g, '_'); lines.push(`middleware.${safeName}.latency.mean:${metrics.latency.mean}|ms`); lines.push(`middleware.${safeName}.latency.p95:${metrics.latency.p95}|ms`); lines.push(`middleware.${safeName}.latency.p99:${metrics.latency.p99}|ms`); lines.push(`middleware.${safeName}.errors:${metrics.errors.totalErrors}|c`); lines.push(`middleware.${safeName}.requests:${metrics.throughput.totalRequests}|c`); } return lines.join('\n'); } aggregateMetrics() { for (const metrics of this.metrics.values()) { this.calculatePercentiles(metrics.latency); this.calculateThroughput(metrics.throughput); this.calculateErrorRate(metrics.errors, metrics.throughput.totalRequests); } this.dataPoints.push({ timestamp: new Date(), metrics: new Map(this.metrics) }); if (this.dataPoints.length > this.config.storage.maxDataPoints) { this.dataPoints.shift(); } } calculatePercentiles(latency) { const values = []; for (const [bucket, count] of latency.histogram) { for (let i = 0; i < count; i++) { values.push(bucket); } } if (values.length === 0) return; values.sort((a, b) => a - b); latency.median = this.percentile(values, 50); latency.p75 = this.percentile(values, 75); latency.p95 = this.percentile(values, 95); latency.p99 = this.percentile(values, 99); } percentile(values, p) { const index = Math.ceil((p / 100) * values.length) - 1; return values[Math.max(0, index)]; } calculateThroughput(throughput) { const intervalSeconds = this.config.storage.aggregationInterval / 1000; throughput.requestsPerSecond = throughput.totalRequests / intervalSeconds; throughput.bytesPerSecond = throughput.totalBytes / intervalSeconds; } calculateErrorRate(errors, totalRequests) { errors.errorRate = totalRequests > 0 ? errors.totalErrors / totalRequests : 0; } getRequestId(req) { return req.__requestId || this.generateRequestId(); } generateRequestId() { return `req_${Date.now()}_${crypto.randomInt(0, Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER.toString(36).substr(2, 9)}`; } getMetrics(middlewareName) { if (middlewareName) { return this.metrics.get(middlewareName); } return new Map(this.metrics); } getReport() { const report = { timestamp: new Date(), middlewares: new Map(), summary: { totalMiddlewares: this.metrics.size, totalRequests: 0, totalErrors: 0, averageLatency: 0, slowestMiddleware: '', highestErrorRate: '' } }; let totalLatency = 0; let totalCount = 0; let slowestLatency = 0; let highestErrorRate = 0; for (const [name, metrics] of this.metrics) { report.middlewares.set(name, { name, latency: { ...metrics.latency }, throughput: { ...metrics.throughput }, errors: { ...metrics.errors, errorsByType: Array.from(metrics.errors.errorsByType), errorsByStatusCode: Array.from(metrics.errors.errorsByStatusCode) }, resources: metrics.resources }); report.summary.totalRequests += metrics.throughput.totalRequests; report.summary.totalErrors += metrics.errors.totalErrors; totalLatency += metrics.latency.mean * metrics.latency.count; totalCount += metrics.latency.count; if (metrics.latency.mean > slowestLatency) { slowestLatency = metrics.latency.mean; report.summary.slowestMiddleware = name; } if (metrics.errors.errorRate > highestErrorRate) { highestErrorRate = metrics.errors.errorRate; report.summary.highestErrorRate = name; } } report.summary.averageLatency = totalCount > 0 ? totalLatency / totalCount : 0; return report; } getHistory() { return [...this.dataPoints]; } findBottlenecks(threshold = 100) { const bottlenecks = []; for (const [name, metrics] of this.metrics) { if (metrics.latency.p95 > threshold) { bottlenecks.push(name); } } return bottlenecks.sort((a, b) => { const metricsA = this.metrics.get(a); const metricsB = this.metrics.get(b); return metricsB.latency.p95 - metricsA.latency.p95; }); } reset(middlewareName) { if (middlewareName) { const metrics = this.metrics.get(middlewareName); if (metrics) { this.initializeMetrics(middlewareName); } } else { for (const name of this.metrics.keys()) { this.initializeMetrics(name); } } } stop() { if (this.reportingInterval) { clearInterval(this.reportingInterval); } if (this.aggregationInterval) { clearInterval(this.aggregationInterval); } this.removeAllListeners(); } } exports.PerformanceMonitor = PerformanceMonitor; function createPerformanceMonitor(config) { return new PerformanceMonitor(config); } //# sourceMappingURL=performance-monitor.js.map