Monitoreo y Métricas de Rendimiento
Supervisa el rendimiento y detecta problemas:
// Sistema de monitoreo y métricas
class APIMonitor {
constructor() {
this.metrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
rateLimitedRequests: 0,
cacheHits: 0,
cacheMisses: 0,
averageResponseTime: 0,
requestsPerMinute: [],
errorsByType: new Map()
};
this.startTime = Date.now();
this.responseTimes = [];
}
// Registrar métricas de petición
recordRequest(endpoint, startTime, success, error = null, cached = false) {
const duration = Date.now() - startTime;
this.metrics.totalRequests++;
this.responseTimes.push(duration);
if (success) {
this.metrics.successfulRequests++;
} else {
this.metrics.failedRequests++;
this.recordError(error);
}
if (cached) {
this.metrics.cacheHits++;
} else {
this.metrics.cacheMisses++;
}
// Actualizar tiempo promedio de respuesta
this.updateAverageResponseTime();
// Registrar por minuto
this.recordRequestsPerMinute();
// Log detallado
console.log(`📊 ${endpoint}: ${duration}ms, ${success ? '✅' : '❌'}, ${cached ? '📦 cached' : '🌐 fresh'}`);
}
recordError(error) {
if (error.status === 429) {
this.metrics.rateLimitedRequests++;
}
const errorType = error.message || 'Unknown';
this.metrics.errorsByType.set(errorType,
(this.metrics.errorsByType.get(errorType) || 0) + 1
);
}
updateAverageResponseTime() {
const recentTimes = this.responseTimes.slice(-100); // Últimas 100 peticiones
this.metrics.averageResponseTime = recentTimes.reduce((sum, time) => sum + time, 0) / recentTimes.length;
}
recordRequestsPerMinute() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Limpiar timestamps antiguos
this.metrics.requestsPerMinute = this.metrics.requestsPerMinute.filter(
timestamp => timestamp > oneMinuteAgo
);
// Añadir timestamp actual
this.metrics.requestsPerMinute.push(now);
}
// Obtener estadísticas actuales
getStats() {
const uptime = Date.now() - this.startTime;
const currentRPM = this.metrics.requestsPerMinute.length;
return {
uptime: Math.floor(uptime / 1000), // segundos
totalRequests: this.metrics.totalRequests,
successRate: (this.metrics.successfulRequests / this.metrics.totalRequests * 100).toFixed(2),
errorRate: (this.metrics.failedRequests / this.metrics.totalRequests * 100).toFixed(2),
rateLimitRate: (this.metrics.rateLimitedRequests / this.metrics.totalRequests * 100).toFixed(2),
cacheHitRate: (this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) * 100).toFixed(2),
averageResponseTime: Math.round(this.metrics.averageResponseTime),
currentRequestsPerMinute: currentRPM,
errorsByType: Object.fromEntries(this.metrics.errorsByType),
memoryUsage: this.getMemoryUsage()
};
}
getMemoryUsage() {
if (performance.memory) {
return {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024),
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)
};
}
return null;
}
// Alertas de salud
checkHealth() {
const stats = this.getStats();
const alerts = [];
if (stats.errorRate > 10) {
alerts.push(`🚨 Alta tasa de errores: ${stats.errorRate}%`);
}
if (stats.rateLimitRate > 5) {
alerts.push(`⚠️ Muchos rate limits: ${stats.rateLimitRate}%`);
}
if (stats.averageResponseTime > 1000) {
alerts.push(`⏱️ Respuestas lentas: ${stats.averageResponseTime}ms promedio`);
}
if (stats.currentRequestsPerMinute > 100) {
alerts.push(`🔥 Alta carga: ${stats.currentRequestsPerMinute} RPM`);
}
return alerts;
}
// Generar reporte
generateReport() {
const stats = this.getStats();
const health = this.checkHealth();
return {
timestamp: new Date().toISOString(),
stats: stats,
health: {
status: health.length === 0 ? 'healthy' : 'degraded',
alerts: health
},
recommendations: this.generateRecommendations(stats, health)
};
}
generateRecommendations(stats, health) {
const recommendations = [];
if (stats.errorRate > 5) {
recommendations.push('Revisar logs de errores y endpoints problemáticos');
}
if (stats.rateLimitRate > 2) {
recommendations.push('Implementar caché más agresivo o reducir frecuencia de peticiones');
}
if (stats.cacheHitRate < 50) {
recommendations.push('Aumentar TTL del caché o implementar caché predictivo');
}
if (stats.averageResponseTime > 500) {
recommendations.push('Optimizar peticiones lentas o implementar timeout más estricto');
}
return recommendations;
}
}
// Dashboard de monitoreo en tiempo real
class MonitoringDashboard {
constructor(monitor) {
this.monitor = monitor;
this.updateInterval = null;
}
startUpdates(intervalMs = 5000) {
this.updateInterval = setInterval(() => {
this.updateDashboard();
}, intervalMs);
}
updateDashboard() {
const report = this.monitor.generateReport();
// Actualizar UI con las métricas
this.updateMetrics(report.stats);
this.updateHealthStatus(report.health);
this.updateRecommendations(report.recommendations);
console.log('📊 Dashboard actualizado:', report.timestamp);
}
updateMetrics(stats) {
// Actualizar elementos del DOM con las métricas
document.getElementById('successRate').textContent = stats.successRate + '%';
document.getElementById('avgResponseTime').textContent = stats.averageResponseTime + 'ms';
document.getElementById('cacheHitRate').textContent = stats.cacheHitRate + '%';
document.getElementById('currentRPM').textContent = stats.currentRequestsPerMinute;
}
stopUpdates() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
}
// Uso completo del sistema de monitoreo
const monitor = new APIMonitor();
const dashboard = new MonitoringDashboard(monitor);
// Integrar con los clientes anteriores
class MonitoredAPIClient extends OptimizedAPIClient {
constructor(concurrentManager, cache, monitor) {
super(concurrentManager, cache);
this.monitor = monitor;
}
async fetchWithOptimization(endpoint, params = {}, options = {}) {
const startTime = Date.now();
try {
const result = await super.fetchWithOptimization(endpoint, params, options);
this.monitor.recordRequest(endpoint, startTime, true, null, options.useCache !== false);
return result;
} catch (error) {
this.monitor.recordRequest(endpoint, startTime, false, error, options.useCache !== false);
throw error;
}
}
}