fix(metrics): fix metrics

This commit is contained in:
Juergen Kunz
2025-06-22 23:40:02 +00:00
parent 92fde9d0d7
commit d24e51117d
6 changed files with 545 additions and 127 deletions

View File

@ -18,6 +18,9 @@ export class MetricsManager {
bouncedToday: 0,
queueSize: 0,
lastResetDate: new Date().toDateString(),
deliveryTimes: [] as number[], // Track delivery times in ms
recipients: new Map<string, number>(), // Track email count by recipient
recentActivity: [] as Array<{ timestamp: number; type: string; details: string }>,
};
// Track DNS-specific metrics
@ -28,6 +31,8 @@ export class MetricsManager {
queryTypes: {} as Record<string, number>,
topDomains: new Map<string, number>(),
lastResetDate: new Date().toDateString(),
queryTimestamps: [] as number[], // Track query timestamps for rate calculation
responseTimes: [] as number[], // Track response times in ms
};
// Track security-specific metrics
@ -38,6 +43,7 @@ export class MetricsManager {
malwareDetected: 0,
phishingDetected: 0,
lastResetDate: new Date().toDateString(),
incidents: [] as Array<{ timestamp: number; type: string; severity: string; details: string }>,
};
constructor(dcRouter: DcRouter) {
@ -66,6 +72,9 @@ export class MetricsManager {
this.emailMetrics.receivedToday = 0;
this.emailMetrics.failedToday = 0;
this.emailMetrics.bouncedToday = 0;
this.emailMetrics.deliveryTimes = [];
this.emailMetrics.recipients.clear();
this.emailMetrics.recentActivity = [];
this.emailMetrics.lastResetDate = currentDate;
}
@ -75,6 +84,8 @@ export class MetricsManager {
this.dnsMetrics.cacheMisses = 0;
this.dnsMetrics.queryTypes = {};
this.dnsMetrics.topDomains.clear();
this.dnsMetrics.queryTimestamps = [];
this.dnsMetrics.responseTimes = [];
this.dnsMetrics.lastResetDate = currentDate;
}
@ -84,6 +95,7 @@ export class MetricsManager {
this.securityMetrics.spamDetected = 0;
this.securityMetrics.malwareDetected = 0;
this.securityMetrics.phishingDetected = 0;
this.securityMetrics.incidents = [];
this.securityMetrics.lastResetDate = currentDate;
}
}, 60000); // Check every minute
@ -105,7 +117,8 @@ export class MetricsManager {
// 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;
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStatistics() : null;
return {
uptime: process.uptime(),
@ -124,15 +137,32 @@ export class MetricsManager {
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 },
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,
@ -144,9 +174,9 @@ export class MetricsManager {
? ((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
averageDeliveryTime: Math.round(avgDeliveryTime),
topRecipients,
recentActivity,
};
}
@ -161,21 +191,35 @@ export class MetricsManager {
.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: 0, // TODO: Calculate based on time window
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: 0, // TODO: Implement response time tracking
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,
@ -185,19 +229,19 @@ export class MetricsManager {
totalThreatsBlocked: this.securityMetrics.spamDetected +
this.securityMetrics.malwareDetected +
this.securityMetrics.phishingDetected,
recentIncidents: [], // TODO: Implement incident logging
recentIncidents,
};
}
// Get connection info from SmartProxy
public async getConnectionInfo() {
const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStats() : null;
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyStats) {
if (!proxyMetrics) {
return [];
}
const connectionsByRoute = proxyStats.getConnectionsByRoute();
const connectionsByRoute = proxyMetrics.connections.byRoute();
const connectionInfo = [];
for (const [routeName, count] of connectionsByRoute) {
@ -213,20 +257,77 @@ export class MetricsManager {
}
// Email event tracking methods
public trackEmailSent(): void {
public trackEmailSent(recipient?: string, deliveryTimeMs?: number): void {
this.emailMetrics.sentToday++;
if (recipient) {
const count = this.emailMetrics.recipients.get(recipient) || 0;
this.emailMetrics.recipients.set(recipient, count + 1);
}
if (deliveryTimeMs) {
this.emailMetrics.deliveryTimes.push(deliveryTimeMs);
// Keep only last 1000 delivery times
if (this.emailMetrics.deliveryTimes.length > 1000) {
this.emailMetrics.deliveryTimes.shift();
}
}
this.emailMetrics.recentActivity.push({
timestamp: Date.now(),
type: 'sent',
details: recipient || 'unknown',
});
// Keep only last 1000 activities
if (this.emailMetrics.recentActivity.length > 1000) {
this.emailMetrics.recentActivity.shift();
}
}
public trackEmailReceived(): void {
public trackEmailReceived(sender?: string): void {
this.emailMetrics.receivedToday++;
this.emailMetrics.recentActivity.push({
timestamp: Date.now(),
type: 'received',
details: sender || 'unknown',
});
// Keep only last 1000 activities
if (this.emailMetrics.recentActivity.length > 1000) {
this.emailMetrics.recentActivity.shift();
}
}
public trackEmailFailed(): void {
public trackEmailFailed(recipient?: string, reason?: string): void {
this.emailMetrics.failedToday++;
this.emailMetrics.recentActivity.push({
timestamp: Date.now(),
type: 'failed',
details: `${recipient || 'unknown'}: ${reason || 'unknown error'}`,
});
// Keep only last 1000 activities
if (this.emailMetrics.recentActivity.length > 1000) {
this.emailMetrics.recentActivity.shift();
}
}
public trackEmailBounced(): void {
public trackEmailBounced(recipient?: string): void {
this.emailMetrics.bouncedToday++;
this.emailMetrics.recentActivity.push({
timestamp: Date.now(),
type: 'bounced',
details: recipient || 'unknown',
});
// Keep only last 1000 activities
if (this.emailMetrics.recentActivity.length > 1000) {
this.emailMetrics.recentActivity.shift();
}
}
public updateQueueSize(size: number): void {
@ -234,7 +335,7 @@ export class MetricsManager {
}
// DNS event tracking methods
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean): void {
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
this.dnsMetrics.totalQueries++;
if (cacheHit) {
@ -243,6 +344,22 @@ export class MetricsManager {
this.dnsMetrics.cacheMisses++;
}
// Track query timestamp
this.dnsMetrics.queryTimestamps.push(Date.now());
// Keep only timestamps from last 5 minutes
const fiveMinutesAgo = Date.now() - 300000;
this.dnsMetrics.queryTimestamps = this.dnsMetrics.queryTimestamps.filter(ts => ts >= fiveMinutesAgo);
// Track response time if provided
if (responseTimeMs) {
this.dnsMetrics.responseTimes.push(responseTimeMs);
// Keep only last 1000 response times
if (this.dnsMetrics.responseTimes.length > 1000) {
this.dnsMetrics.responseTimes.shift();
}
}
// Track query types
this.dnsMetrics.queryTypes[queryType] = (this.dnsMetrics.queryTypes[queryType] || 0) + 1;
@ -266,31 +383,91 @@ export class MetricsManager {
}
// Security event tracking methods
public trackBlockedIP(): void {
public trackBlockedIP(ip?: string, reason?: string): void {
this.securityMetrics.blockedIPs++;
this.securityMetrics.incidents.push({
timestamp: Date.now(),
type: 'ip_blocked',
severity: 'medium',
details: `IP ${ip || 'unknown'} blocked: ${reason || 'security policy'}`,
});
// Keep only last 1000 incidents
if (this.securityMetrics.incidents.length > 1000) {
this.securityMetrics.incidents.shift();
}
}
public trackAuthFailure(): void {
public trackAuthFailure(username?: string, ip?: string): void {
this.securityMetrics.authFailures++;
this.securityMetrics.incidents.push({
timestamp: Date.now(),
type: 'auth_failure',
severity: 'low',
details: `Authentication failed for ${username || 'unknown'} from ${ip || 'unknown'}`,
});
// Keep only last 1000 incidents
if (this.securityMetrics.incidents.length > 1000) {
this.securityMetrics.incidents.shift();
}
}
public trackSpamDetected(): void {
public trackSpamDetected(sender?: string): void {
this.securityMetrics.spamDetected++;
this.securityMetrics.incidents.push({
timestamp: Date.now(),
type: 'spam_detected',
severity: 'low',
details: `Spam detected from ${sender || 'unknown'}`,
});
// Keep only last 1000 incidents
if (this.securityMetrics.incidents.length > 1000) {
this.securityMetrics.incidents.shift();
}
}
public trackMalwareDetected(): void {
public trackMalwareDetected(source?: string): void {
this.securityMetrics.malwareDetected++;
this.securityMetrics.incidents.push({
timestamp: Date.now(),
type: 'malware_detected',
severity: 'high',
details: `Malware detected from ${source || 'unknown'}`,
});
// Keep only last 1000 incidents
if (this.securityMetrics.incidents.length > 1000) {
this.securityMetrics.incidents.shift();
}
}
public trackPhishingDetected(): void {
public trackPhishingDetected(source?: string): void {
this.securityMetrics.phishingDetected++;
this.securityMetrics.incidents.push({
timestamp: Date.now(),
type: 'phishing_detected',
severity: 'high',
details: `Phishing attempt from ${source || 'unknown'}`,
});
// Keep only last 1000 incidents
if (this.securityMetrics.incidents.length > 1000) {
this.securityMetrics.incidents.shift();
}
}
// Get network metrics from SmartProxy
public async getNetworkStats() {
const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStats() : null;
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
if (!proxyStats) {
if (!proxyMetrics) {
return {
connectionsByIP: new Map<string, number>(),
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
@ -299,47 +476,30 @@ export class MetricsManager {
};
}
// Get unused SmartProxy metrics
const connectionsByIP = proxyStats.getConnectionsByIP();
const throughput = proxyStats.getThroughput();
// Get metrics using the new API
const connectionsByIP = proxyMetrics.connections.byIP();
const instantThroughput = proxyMetrics.throughput.instant();
// Check if extended methods exist and call them
const throughputRate = ('getThroughputRate' in proxyStats && typeof proxyStats.getThroughputRate === 'function')
? (() => {
const rate = (proxyStats as any).getThroughputRate();
return {
bytesInPerSecond: rate.bytesInPerSec || 0,
bytesOutPerSecond: rate.bytesOutPerSec || 0
};
})()
: { bytesInPerSecond: 0, bytesOutPerSecond: 0 };
const topIPs: Array<{ ip: string; count: number }> = [];
// Get throughput rate
const throughputRate = {
bytesInPerSecond: instantThroughput.in,
bytesOutPerSecond: instantThroughput.out
};
// Check if getTopIPs method exists
if ('getTopIPs' in proxyStats && typeof proxyStats.getTopIPs === 'function') {
const ips = (proxyStats as any).getTopIPs(10);
if (Array.isArray(ips)) {
ips.forEach(ipData => {
topIPs.push({ ip: ipData.ip, count: ipData.connections || ipData.count || 0 });
});
}
} else {
// Fallback: Convert connectionsByIP to topIPs manually
if (connectionsByIP && connectionsByIP.size > 0) {
const ipArray = Array.from(connectionsByIP.entries())
.map(([ip, count]) => ({ ip, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
topIPs.push(...ipArray);
}
}
// 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: throughput,
totalDataTransferred,
};
}
}