@iota-big3/sdk-gateway
Version:
Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching
345 lines • 14.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformanceMonitor = void 0;
const events_1 = require("events");
const minimal_analytics_1 = require("../utils/minimal-analytics");
class PerformanceMonitor extends events_1.EventEmitter {
constructor(thresholds) {
super();
this.requests = new Map(); // Request ID -> start time
this.latencies = [];
this.windowSize = 60000; // 1 minute window for calculations
this.lastReset = Date.now();
this.anomalies = [];
this.metrics = new minimal_analytics_1.SimpleTimeSeriesStore();
this.thresholds = {
latency: {
p50: thresholds?.latency?.p50 || 50,
p95: thresholds?.latency?.p95 || 150,
p99: thresholds?.latency?.p99 || 300
},
errorRate: thresholds?.errorRate || 0.01, // 1%
throughput: {
min: thresholds?.throughput?.min || 100,
max: thresholds?.throughput?.max || 10000
},
memory: {
max: thresholds?.memory?.max || 512, // MB
growthRate: thresholds?.memory?.growthRate || 10 // MB/hour
}
};
// Start periodic metric collection
this.startMetricCollection();
}
startRequest(request) {
this.requests.set(request.id, Date.now());
this.metrics.record('requests.active', this.requests.size);
}
endRequest(request, response) {
const startTime = this.requests.get(request.id);
if (!startTime)
return;
const latency = Date.now() - startTime;
this.requests.delete(request.id);
// Record metrics
this.collectMetrics(request, response, latency);
// Check for anomalies
this.checkAnomalies();
}
collectMetrics(request, response, latency) {
// Request metrics
this.metrics.record('requests.total', 1);
this.metrics.record('requests.active', this.requests.size);
this.latencies.push(latency);
// Latency metrics
this.metrics.record('latency.current', latency, {
path: request.path,
method: request.method
});
// Error tracking
if (response.statusCode >= 400) {
this.metrics.record('errors.total', 1, {
status: String(response.statusCode),
path: request.path
});
}
// Throughput (approximate based on response size)
const responseSize = JSON.stringify(response.body).length;
this.metrics.record('throughput.bytes', responseSize);
// Resource metrics
this.recordResourceMetrics();
// SLA compliance
const slaCompliant = this.checkSLACompliance(request.path, latency, response.statusCode);
this.metrics.record('sla.compliance', slaCompliant ? 1 : 0, {
path: request.path
});
}
recordResourceMetrics() {
const memUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
// Memory metrics in MB
this.metrics.record('memory.rss', memUsage.rss / 1024 / 1024);
this.metrics.record('memory.heapUsed', memUsage.heapUsed / 1024 / 1024);
this.metrics.record('memory.heapTotal', memUsage.heapTotal / 1024 / 1024);
this.metrics.record('memory.external', memUsage.external / 1024 / 1024);
// CPU metrics
this.metrics.record('cpu.user', cpuUsage.user / 1000000); // Convert to seconds
this.metrics.record('cpu.system', cpuUsage.system / 1000000);
}
checkSLACompliance(path, latency, statusCode) {
// Basic SLA rules
if (latency > this.thresholds.latency.p95)
return false;
if (statusCode >= 500)
return false;
// Path-specific SLAs could be added here
if (path.startsWith('/api/critical/') && latency > 50)
return false;
return true;
}
detectAnomalies() {
this.anomalies = [
...this.detectLatencySpikes(),
...this.detectMemoryLeaks(),
...this.detectErrorRateIncrease(),
...this.detectThroughputDegradation()
];
// Emit critical anomalies
this.anomalies
.filter(a => a.severity === 'critical')
.forEach(anomaly => this.emit('anomaly:critical', anomaly));
return this.anomalies;
}
detectLatencySpikes() {
const anomalies = [];
const recentLatencies = this.latencies.slice(-100); // Last 100 requests
if (recentLatencies.length === 0)
return anomalies;
const p95 = this.calculatePercentile(recentLatencies, 95);
const p99 = this.calculatePercentile(recentLatencies, 99);
if (p95 > this.thresholds.latency.p95) {
anomalies.push({
type: 'latency',
severity: p95 > this.thresholds.latency.p95 * 2 ? 'high' : 'medium',
value: p95,
threshold: this.thresholds.latency.p95,
timestamp: Date.now(),
message: `P95 latency ${p95}ms exceeds threshold ${this.thresholds.latency.p95}ms`
});
}
if (p99 > this.thresholds.latency.p99) {
anomalies.push({
type: 'latency',
severity: 'critical',
value: p99,
threshold: this.thresholds.latency.p99,
timestamp: Date.now(),
message: `P99 latency ${p99}ms exceeds threshold ${this.thresholds.latency.p99}ms`
});
}
return anomalies;
}
detectMemoryLeaks() {
const anomalies = [];
const memoryData = this.metrics.query({
metric: 'memory.heapUsed',
startTime: Date.now() - 3600000 // Last hour
});
if (memoryData.length < 2)
return anomalies;
// Calculate growth rate
const firstValue = memoryData[0]?.value || 0;
const lastValue = memoryData[memoryData.length - 1]?.value || 0;
const growthMB = lastValue - firstValue;
if (growthMB > this.thresholds.memory.growthRate) {
anomalies.push({
type: 'memory',
severity: growthMB > this.thresholds.memory.growthRate * 2 ? 'critical' : 'high',
value: growthMB,
threshold: this.thresholds.memory.growthRate,
timestamp: Date.now(),
message: `Memory grew by ${growthMB.toFixed(2)}MB in the last hour`
});
}
// Check absolute memory usage
if (lastValue > this.thresholds.memory.max) {
anomalies.push({
type: 'memory',
severity: 'critical',
value: lastValue,
threshold: this.thresholds.memory.max,
timestamp: Date.now(),
message: `Memory usage ${lastValue.toFixed(2)}MB exceeds maximum ${this.thresholds.memory.max}MB`
});
}
return anomalies;
}
detectErrorRateIncrease() {
const anomalies = [];
const recentRequests = this.metrics.query({
metric: 'requests.total',
startTime: Date.now() - 60000 // Last minute
});
const recentErrors = this.metrics.query({
metric: 'errors.total',
startTime: Date.now() - 60000
});
const totalRequests = recentRequests.reduce((sum, d) => sum + (d.value || 0), 0);
const totalErrors = recentErrors.reduce((sum, d) => sum + (d.value || 0), 0);
if (totalRequests === 0)
return anomalies;
const errorRate = totalErrors / totalRequests;
if (errorRate > this.thresholds.errorRate) {
anomalies.push({
type: 'error_rate',
severity: errorRate > this.thresholds.errorRate * 5 ? 'critical' : 'high',
value: errorRate * 100,
threshold: this.thresholds.errorRate * 100,
timestamp: Date.now(),
message: `Error rate ${(errorRate * 100).toFixed(2)}% exceeds threshold ${(this.thresholds.errorRate * 100).toFixed(2)}%`
});
}
return anomalies;
}
detectThroughputDegradation() {
const anomalies = [];
const rate = this.metrics.getRate('requests.total');
if (rate < this.thresholds.throughput.min && rate > 0) {
anomalies.push({
type: 'throughput',
severity: 'medium',
value: rate,
threshold: this.thresholds.throughput.min,
timestamp: Date.now(),
message: `Request rate ${rate.toFixed(2)} req/sec below minimum ${this.thresholds.throughput.min} req/sec`
});
}
return anomalies;
}
checkAnomalies() {
// Periodically check for anomalies
if (Date.now() - this.lastReset > this.windowSize) {
this.detectAnomalies();
this.cleanupOldData();
this.lastReset = Date.now();
}
}
cleanupOldData() {
// Keep only recent latencies
if (this.latencies.length > 10000) {
this.latencies = this.latencies.slice(-5000);
}
// Clean up old requests (potential memory leak from stuck requests)
const now = Date.now();
for (const [id, startTime] of this.requests) {
if (now - startTime > 300000) { // 5 minutes
this.requests.delete(id);
this.emit('request:timeout', { id, duration: now - startTime });
}
}
}
calculatePercentile(values, percentile) {
if (values.length === 0)
return 0;
const sorted = [...values].sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[index] || 0;
}
getMetrics() {
const recentLatencies = this.latencies.slice(-1000);
const sortedLatencies = [...recentLatencies].sort((a, b) => a - b);
return {
requests: {
total: this.metrics.getLatest('requests.total')?.value || 0,
rate: this.metrics.getRate('requests.total'),
active: this.requests.size
},
latency: {
current: recentLatencies[recentLatencies.length - 1] || 0,
p50: this.calculatePercentile(sortedLatencies, 50),
p95: this.calculatePercentile(sortedLatencies, 95),
p99: this.calculatePercentile(sortedLatencies, 99),
avg: recentLatencies.reduce((a, b) => a + b, 0) / (recentLatencies.length || 1),
min: Math.min(...recentLatencies) || 0,
max: Math.max(...recentLatencies) || 0
},
errors: {
total: this.metrics.getLatest('errors.total')?.value || 0,
rate: this.calculateErrorRate(),
byType: this.getErrorsByType()
},
throughput: {
current: this.metrics.getRate('throughput.bytes'),
avg: this.metrics.getAverage('throughput.bytes'),
peak: Math.max(...this.metrics.query({ metric: 'throughput.bytes' }).map(d => d.value)) || 0
},
resources: {
cpu: {
usage: this.calculateCPUUsage(),
system: this.metrics.getLatest('cpu.system')?.value || 0,
user: this.metrics.getLatest('cpu.user')?.value || 0
},
memory: {
used: this.metrics.getLatest('memory.heapUsed')?.value || 0,
rss: this.metrics.getLatest('memory.rss')?.value || 0,
heapUsed: this.metrics.getLatest('memory.heapUsed')?.value || 0,
heapTotal: this.metrics.getLatest('memory.heapTotal')?.value || 0,
external: this.metrics.getLatest('memory.external')?.value || 0
},
connections: {
active: this.requests.size,
idle: 0, // Would need connection pool integration
waiting: 0
}
}
};
}
calculateErrorRate() {
const totalRequests = this.metrics.getRate('requests.total');
const totalErrors = this.metrics.getRate('errors.total');
return totalRequests > 0 ? (totalErrors / totalRequests) * 100 : 0;
}
getErrorsByType() {
const errors = {};
const errorData = this.metrics.query({
metric: 'errors.total',
startTime: Date.now() - 60000
});
errorData.forEach(point => {
const status = point.tags?.status || 'unknown';
errors[status] = (errors[status] || 0) + (point.value || 0);
});
return errors;
}
calculateCPUUsage() {
const user = this.metrics.getLatest('cpu.user')?.value || 0;
const system = this.metrics.getLatest('cpu.system')?.value || 0;
const total = user + system;
// Rough approximation of CPU percentage
// In production, would use proper CPU usage calculation
return Math.min(100, total * 10);
}
startMetricCollection() {
// Collect resource metrics every 5 seconds
setInterval(() => {
this.recordResourceMetrics();
}, 5000);
// Check for anomalies every minute
setInterval(() => {
this.detectAnomalies();
}, 60000);
}
reset() {
this.metrics.clear();
this.requests.clear();
this.latencies = [];
this.anomalies = [];
this.lastReset = Date.now();
this.emit('monitor:reset');
}
getAnomalies() {
return this.anomalies;
}
}
exports.PerformanceMonitor = PerformanceMonitor;
//# sourceMappingURL=performance-monitor.js.map