import * as plugins from '../plugins.js'; import { DcRouter } from '../classes.dcrouter.js'; export class MetricsManager { private logger: plugins.smartlog.Smartlog; private smartMetrics: plugins.smartmetrics.SmartMetrics; private dcRouter: DcRouter; private resetInterval?: NodeJS.Timeout; // Constants private readonly MAX_TOP_DOMAINS = 1000; // Limit topDomains Map size // Track email-specific metrics private emailMetrics = { sentToday: 0, receivedToday: 0, failedToday: 0, bouncedToday: 0, queueSize: 0, lastResetDate: new Date().toDateString(), }; // Track DNS-specific metrics private dnsMetrics = { totalQueries: 0, cacheHits: 0, cacheMisses: 0, queryTypes: {} as Record, topDomains: new Map(), lastResetDate: new Date().toDateString(), }; // Track security-specific metrics private securityMetrics = { blockedIPs: 0, authFailures: 0, spamDetected: 0, malwareDetected: 0, phishingDetected: 0, lastResetDate: new Date().toDateString(), }; constructor(dcRouter: DcRouter) { this.dcRouter = dcRouter; // Create a new Smartlog instance for metrics this.logger = new plugins.smartlog.Smartlog({ logContext: { environment: 'production', runtime: 'node', zone: 'dcrouter-metrics', } }); this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.logger, 'dcrouter'); } public async start(): Promise { // Start SmartMetrics collection this.smartMetrics.start(); // Reset daily counters at midnight this.resetInterval = setInterval(() => { const currentDate = new Date().toDateString(); if (currentDate !== this.emailMetrics.lastResetDate) { this.emailMetrics.sentToday = 0; this.emailMetrics.receivedToday = 0; this.emailMetrics.failedToday = 0; this.emailMetrics.bouncedToday = 0; this.emailMetrics.lastResetDate = currentDate; } if (currentDate !== this.dnsMetrics.lastResetDate) { this.dnsMetrics.totalQueries = 0; this.dnsMetrics.cacheHits = 0; this.dnsMetrics.cacheMisses = 0; this.dnsMetrics.queryTypes = {}; this.dnsMetrics.topDomains.clear(); this.dnsMetrics.lastResetDate = currentDate; } if (currentDate !== this.securityMetrics.lastResetDate) { this.securityMetrics.blockedIPs = 0; this.securityMetrics.authFailures = 0; this.securityMetrics.spamDetected = 0; this.securityMetrics.malwareDetected = 0; this.securityMetrics.phishingDetected = 0; this.securityMetrics.lastResetDate = currentDate; } }, 60000); // Check every minute this.logger.log('info', 'MetricsManager started'); } public async stop(): Promise { // Clear the reset interval if (this.resetInterval) { clearInterval(this.resetInterval); this.resetInterval = undefined; } this.smartMetrics.stop(); this.logger.log('info', 'MetricsManager stopped'); } // Get server metrics from SmartMetrics and SmartProxy public async getServerStats() { const smartMetricsData = await this.smartMetrics.getMetrics(); const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStats() : null; return { uptime: process.uptime(), startTime: Date.now() - (process.uptime() * 1000), memoryUsage: { heapUsed: process.memoryUsage().heapUsed, heapTotal: process.memoryUsage().heapTotal, external: process.memoryUsage().external, rss: process.memoryUsage().rss, }, cpuUsage: { user: parseFloat(smartMetricsData.cpuUsageText || '0'), system: 0, // SmartMetrics doesn't separate user/system }, activeConnections: proxyStats ? proxyStats.getActiveConnections() : 0, totalConnections: proxyStats ? proxyStats.getTotalConnections() : 0, requestsPerSecond: proxyStats ? proxyStats.getRequestsPerSecond() : 0, throughput: proxyStats ? proxyStats.getThroughput() : { bytesIn: 0, bytesOut: 0 }, }; } // Get email metrics public async getEmailStats() { return { sentToday: this.emailMetrics.sentToday, receivedToday: this.emailMetrics.receivedToday, failedToday: this.emailMetrics.failedToday, bounceRate: this.emailMetrics.bouncedToday > 0 ? (this.emailMetrics.bouncedToday / this.emailMetrics.sentToday) * 100 : 0, deliveryRate: this.emailMetrics.sentToday > 0 ? ((this.emailMetrics.sentToday - this.emailMetrics.failedToday) / this.emailMetrics.sentToday) * 100 : 100, queueSize: this.emailMetrics.queueSize, averageDeliveryTime: 0, // TODO: Implement when delivery tracking is added topRecipients: [], // TODO: Implement recipient tracking recentActivity: [], // TODO: Implement activity log }; } // Get DNS metrics public async getDnsStats() { const cacheHitRate = this.dnsMetrics.totalQueries > 0 ? (this.dnsMetrics.cacheHits / this.dnsMetrics.totalQueries) * 100 : 0; const topDomains = Array.from(this.dnsMetrics.topDomains.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([domain, count]) => ({ domain, count })); return { queriesPerSecond: 0, // TODO: Calculate based on time window totalQueries: this.dnsMetrics.totalQueries, cacheHits: this.dnsMetrics.cacheHits, cacheMisses: this.dnsMetrics.cacheMisses, cacheHitRate: cacheHitRate, topDomains: topDomains, queryTypes: this.dnsMetrics.queryTypes, averageResponseTime: 0, // TODO: Implement response time tracking activeDomains: this.dnsMetrics.topDomains.size, }; } // Get security metrics public async getSecurityStats() { return { blockedIPs: this.securityMetrics.blockedIPs, authFailures: this.securityMetrics.authFailures, spamDetected: this.securityMetrics.spamDetected, malwareDetected: this.securityMetrics.malwareDetected, phishingDetected: this.securityMetrics.phishingDetected, totalThreatsBlocked: this.securityMetrics.spamDetected + this.securityMetrics.malwareDetected + this.securityMetrics.phishingDetected, recentIncidents: [], // TODO: Implement incident logging }; } // Get connection info from SmartProxy public async getConnectionInfo() { const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStats() : null; if (!proxyStats) { return []; } const connectionsByRoute = proxyStats.getConnectionsByRoute(); const connectionInfo = []; for (const [routeName, count] of connectionsByRoute) { connectionInfo.push({ type: 'https', count, source: routeName, lastActivity: new Date(), }); } return connectionInfo; } // Email event tracking methods public trackEmailSent(): void { this.emailMetrics.sentToday++; } public trackEmailReceived(): void { this.emailMetrics.receivedToday++; } public trackEmailFailed(): void { this.emailMetrics.failedToday++; } public trackEmailBounced(): void { this.emailMetrics.bouncedToday++; } public updateQueueSize(size: number): void { this.emailMetrics.queueSize = size; } // DNS event tracking methods public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean): void { this.dnsMetrics.totalQueries++; if (cacheHit) { this.dnsMetrics.cacheHits++; } else { this.dnsMetrics.cacheMisses++; } // Track query types this.dnsMetrics.queryTypes[queryType] = (this.dnsMetrics.queryTypes[queryType] || 0) + 1; // Track top domains with size limit const currentCount = this.dnsMetrics.topDomains.get(domain) || 0; this.dnsMetrics.topDomains.set(domain, currentCount + 1); // If we've exceeded the limit, remove the least accessed domains if (this.dnsMetrics.topDomains.size > this.MAX_TOP_DOMAINS) { // Convert to array, sort by count, and keep only top domains const sortedDomains = Array.from(this.dnsMetrics.topDomains.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, Math.floor(this.MAX_TOP_DOMAINS * 0.8)); // Keep 80% to avoid frequent cleanup // Clear and repopulate with top domains this.dnsMetrics.topDomains.clear(); sortedDomains.forEach(([domain, count]) => { this.dnsMetrics.topDomains.set(domain, count); }); } } // Security event tracking methods public trackBlockedIP(): void { this.securityMetrics.blockedIPs++; } public trackAuthFailure(): void { this.securityMetrics.authFailures++; } public trackSpamDetected(): void { this.securityMetrics.spamDetected++; } public trackMalwareDetected(): void { this.securityMetrics.malwareDetected++; } public trackPhishingDetected(): void { this.securityMetrics.phishingDetected++; } }