This commit is contained in:
Juergen Kunz
2025-07-02 11:33:50 +00:00
parent 7bd94884f4
commit 2f46b3c9f3
9 changed files with 688 additions and 300 deletions

View File

@@ -1,11 +1,13 @@
import * as plugins from '../plugins.js';
import { DcRouter } from '../classes.dcrouter.js';
import { MetricsCache } from './classes.metricscache.js';
export class MetricsManager {
private logger: plugins.smartlog.Smartlog;
private smartMetrics: plugins.smartmetrics.SmartMetrics;
private dcRouter: DcRouter;
private resetInterval?: NodeJS.Timeout;
private metricsCache: MetricsCache;
// Constants
private readonly MAX_TOP_DOMAINS = 1000; // Limit topDomains Map size
@@ -57,6 +59,8 @@ export class MetricsManager {
}
});
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.logger, 'dcrouter');
// Initialize metrics cache with 500ms TTL
this.metricsCache = new MetricsCache(500);
}
public async start(): Promise<void> {
@@ -116,144 +120,154 @@ export class MetricsManager {
// Get server metrics from SmartMetrics and SmartProxy
public async getServerStats() {
const smartMetricsData = await this.smartMetrics.getMetrics();
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStatistics() : 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,
// Add SmartMetrics memory data
maxMemoryMB: this.smartMetrics.maxMemoryMB,
actualUsageBytes: smartMetricsData.memoryUsageBytes,
actualUsagePercentage: smartMetricsData.memoryPercentage,
},
cpuUsage: {
user: parseFloat(smartMetricsData.cpuUsageText || '0'),
system: 0, // SmartMetrics doesn't separate user/system
},
activeConnections: proxyStats ? proxyStats.activeConnections : 0,
totalConnections: proxyMetrics ? proxyMetrics.totals.connections() : 0,
requestsPerSecond: proxyMetrics ? proxyMetrics.requests.perSecond() : 0,
throughput: proxyMetrics ? {
bytesIn: proxyMetrics.totals.bytesIn(),
bytesOut: proxyMetrics.totals.bytesOut()
} : { bytesIn: 0, bytesOut: 0 },
};
return this.metricsCache.get('serverStats', async () => {
const smartMetricsData = await this.smartMetrics.getMetrics();
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStatistics() : 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,
// Add SmartMetrics memory data
maxMemoryMB: this.smartMetrics.maxMemoryMB,
actualUsageBytes: smartMetricsData.memoryUsageBytes,
actualUsagePercentage: smartMetricsData.memoryPercentage,
},
cpuUsage: {
user: parseFloat(smartMetricsData.cpuUsageText || '0'),
system: 0, // SmartMetrics doesn't separate user/system
},
activeConnections: proxyStats ? proxyStats.activeConnections : 0,
totalConnections: proxyMetrics ? proxyMetrics.totals.connections() : 0,
requestsPerSecond: proxyMetrics ? proxyMetrics.requests.perSecond() : 0,
throughput: proxyMetrics ? {
bytesIn: proxyMetrics.totals.bytesIn(),
bytesOut: proxyMetrics.totals.bytesOut()
} : { bytesIn: 0, bytesOut: 0 },
};
});
}
// Get email metrics
public async getEmailStats() {
// Calculate average delivery time
const avgDeliveryTime = this.emailMetrics.deliveryTimes.length > 0
? this.emailMetrics.deliveryTimes.reduce((a, b) => a + b, 0) / this.emailMetrics.deliveryTimes.length
: 0;
// Get top recipients
const topRecipients = Array.from(this.emailMetrics.recipients.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([email, count]) => ({ email, count }));
// Get recent activity (last 50 entries)
const recentActivity = this.emailMetrics.recentActivity.slice(-50);
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: Math.round(avgDeliveryTime),
topRecipients,
recentActivity,
};
return this.metricsCache.get('emailStats', () => {
// Calculate average delivery time
const avgDeliveryTime = this.emailMetrics.deliveryTimes.length > 0
? this.emailMetrics.deliveryTimes.reduce((a, b) => a + b, 0) / this.emailMetrics.deliveryTimes.length
: 0;
// Get top recipients
const topRecipients = Array.from(this.emailMetrics.recipients.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([email, count]) => ({ email, count }));
// Get recent activity (last 50 entries)
const recentActivity = this.emailMetrics.recentActivity.slice(-50);
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: Math.round(avgDeliveryTime),
topRecipients,
recentActivity,
};
});
}
// 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 }));
// Calculate queries per second from recent timestamps
const now = Date.now();
const oneMinuteAgo = now - 60000;
const recentQueries = this.dnsMetrics.queryTimestamps.filter(ts => ts >= oneMinuteAgo);
const queriesPerSecond = recentQueries.length / 60;
// Calculate average response time
const avgResponseTime = this.dnsMetrics.responseTimes.length > 0
? this.dnsMetrics.responseTimes.reduce((a, b) => a + b, 0) / this.dnsMetrics.responseTimes.length
: 0;
return {
queriesPerSecond: Math.round(queriesPerSecond * 10) / 10,
totalQueries: this.dnsMetrics.totalQueries,
cacheHits: this.dnsMetrics.cacheHits,
cacheMisses: this.dnsMetrics.cacheMisses,
cacheHitRate: cacheHitRate,
topDomains: topDomains,
queryTypes: this.dnsMetrics.queryTypes,
averageResponseTime: Math.round(avgResponseTime),
activeDomains: this.dnsMetrics.topDomains.size,
};
return this.metricsCache.get('dnsStats', () => {
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 }));
// Calculate queries per second from recent timestamps
const now = Date.now();
const oneMinuteAgo = now - 60000;
const recentQueries = this.dnsMetrics.queryTimestamps.filter(ts => ts >= oneMinuteAgo);
const queriesPerSecond = recentQueries.length / 60;
// Calculate average response time
const avgResponseTime = this.dnsMetrics.responseTimes.length > 0
? this.dnsMetrics.responseTimes.reduce((a, b) => a + b, 0) / this.dnsMetrics.responseTimes.length
: 0;
return {
queriesPerSecond: Math.round(queriesPerSecond * 10) / 10,
totalQueries: this.dnsMetrics.totalQueries,
cacheHits: this.dnsMetrics.cacheHits,
cacheMisses: this.dnsMetrics.cacheMisses,
cacheHitRate: cacheHitRate,
topDomains: topDomains,
queryTypes: this.dnsMetrics.queryTypes,
averageResponseTime: Math.round(avgResponseTime),
activeDomains: this.dnsMetrics.topDomains.size,
};
});
}
// Get security metrics
public async getSecurityStats() {
// Get recent incidents (last 20)
const recentIncidents = this.securityMetrics.incidents.slice(-20);
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,
};
return this.metricsCache.get('securityStats', () => {
// Get recent incidents (last 20)
const recentIncidents = this.securityMetrics.incidents.slice(-20);
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,
};
});
}
// Get connection info from SmartProxy
public async getConnectionInfo() {
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyMetrics) {
return [];
}
const connectionsByRoute = proxyMetrics.connections.byRoute();
const connectionInfo = [];
for (const [routeName, count] of connectionsByRoute) {
connectionInfo.push({
type: 'https',
count,
source: routeName,
lastActivity: new Date(),
});
}
return connectionInfo;
return this.metricsCache.get('connectionInfo', () => {
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyMetrics) {
return [];
}
const connectionsByRoute = proxyMetrics.connections.byRoute();
const connectionInfo = [];
for (const [routeName, count] of connectionsByRoute) {
connectionInfo.push({
type: 'https',
count,
source: routeName,
lastActivity: new Date(),
});
}
return connectionInfo;
});
}
// Email event tracking methods
@@ -465,41 +479,44 @@ export class MetricsManager {
// Get network metrics from SmartProxy
public async getNetworkStats() {
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyMetrics) {
return {
connectionsByIP: new Map<string, number>(),
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
topIPs: [],
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
// Use shorter cache TTL for network stats to ensure real-time updates
return this.metricsCache.get('networkStats', () => {
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyMetrics) {
return {
connectionsByIP: new Map<string, number>(),
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
topIPs: [],
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
};
}
// Get metrics using the new API
const connectionsByIP = proxyMetrics.connections.byIP();
const instantThroughput = proxyMetrics.throughput.instant();
// Get throughput rate
const throughputRate = {
bytesInPerSecond: instantThroughput.in,
bytesOutPerSecond: instantThroughput.out
};
}
// Get metrics using the new API
const connectionsByIP = proxyMetrics.connections.byIP();
const instantThroughput = proxyMetrics.throughput.instant();
// Get throughput rate
const throughputRate = {
bytesInPerSecond: instantThroughput.in,
bytesOutPerSecond: instantThroughput.out
};
// Get top IPs
const topIPs = proxyMetrics.connections.topIPs(10);
// Get total data transferred
const totalDataTransferred = {
bytesIn: proxyMetrics.totals.bytesIn(),
bytesOut: proxyMetrics.totals.bytesOut()
};
return {
connectionsByIP,
throughputRate,
topIPs,
totalDataTransferred,
};
// Get top IPs
const topIPs = proxyMetrics.connections.topIPs(10);
// Get total data transferred
const totalDataTransferred = {
bytesIn: proxyMetrics.totals.bytesIn(),
bytesOut: proxyMetrics.totals.bytesOut()
};
return {
connectionsByIP,
throughputRate,
topIPs,
totalDataTransferred,
};
}, 1000); // Use 200ms cache for more frequent updates
}
}