243 lines
7.1 KiB
TypeScript
243 lines
7.1 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { MetricsManager } from '../ts/monitoring/classes.metricsmanager.js';
|
|
|
|
const emptyProtocolDistribution = {
|
|
h1Active: 0,
|
|
h1Total: 0,
|
|
h2Active: 0,
|
|
h2Total: 0,
|
|
h3Active: 0,
|
|
h3Total: 0,
|
|
wsActive: 0,
|
|
wsTotal: 0,
|
|
otherActive: 0,
|
|
otherTotal: 0,
|
|
};
|
|
|
|
function createProxyMetrics(args: {
|
|
connectionsByRoute: Map<string, number>;
|
|
throughputByRoute: Map<string, { in: number; out: number }>;
|
|
domainRequestsByIP: Map<string, Map<string, number>>;
|
|
domainRequestRates?: Map<string, { perSecond: number; lastMinute: number }>;
|
|
backendMetrics?: Map<string, any>;
|
|
protocolCache?: any[];
|
|
requestsTotal?: number;
|
|
}) {
|
|
return {
|
|
connections: {
|
|
active: () => 0,
|
|
total: () => 0,
|
|
byRoute: () => args.connectionsByRoute,
|
|
byIP: () => new Map<string, number>(),
|
|
topIPs: () => [],
|
|
domainRequestsByIP: () => args.domainRequestsByIP,
|
|
topDomainRequests: () => [],
|
|
frontendProtocols: () => emptyProtocolDistribution,
|
|
backendProtocols: () => emptyProtocolDistribution,
|
|
},
|
|
throughput: {
|
|
instant: () => ({ in: 0, out: 0 }),
|
|
recent: () => ({ in: 0, out: 0 }),
|
|
average: () => ({ in: 0, out: 0 }),
|
|
custom: () => ({ in: 0, out: 0 }),
|
|
history: () => [],
|
|
byRoute: () => args.throughputByRoute,
|
|
byIP: () => new Map<string, { in: number; out: number }>(),
|
|
},
|
|
requests: {
|
|
perSecond: () => 0,
|
|
perMinute: () => 0,
|
|
total: () => args.requestsTotal || 0,
|
|
byDomain: () => args.domainRequestRates || new Map<string, { perSecond: number; lastMinute: number }>(),
|
|
},
|
|
totals: {
|
|
bytesIn: () => 0,
|
|
bytesOut: () => 0,
|
|
connections: () => 0,
|
|
},
|
|
backends: {
|
|
byBackend: () => args.backendMetrics || new Map<string, any>(),
|
|
protocols: () => new Map<string, string>(),
|
|
topByErrors: () => [],
|
|
detectedProtocols: () => args.protocolCache || [],
|
|
},
|
|
};
|
|
}
|
|
|
|
tap.test('MetricsManager joins domain activity to id-keyed route metrics', async () => {
|
|
const proxyMetrics = createProxyMetrics({
|
|
connectionsByRoute: new Map([
|
|
['route-id-only', 4],
|
|
]),
|
|
throughputByRoute: new Map([
|
|
['route-id-only', { in: 1200, out: 2400 }],
|
|
]),
|
|
domainRequestsByIP: new Map([
|
|
['192.0.2.10', new Map([
|
|
['alpha.example.com', 3],
|
|
['beta.example.com', 1],
|
|
])],
|
|
]),
|
|
requestsTotal: 4,
|
|
});
|
|
|
|
const smartProxy = {
|
|
getMetrics: () => proxyMetrics,
|
|
routeManager: {
|
|
getRoutes: () => [
|
|
{
|
|
id: 'route-id-only',
|
|
match: {
|
|
ports: [443],
|
|
domains: ['alpha.example.com', 'beta.example.com'],
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: '127.0.0.1', port: 8443 }],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const manager = new MetricsManager({ smartProxy } as any);
|
|
const stats = await manager.getNetworkStats();
|
|
const alpha = stats.domainActivity.find((item) => item.domain === 'alpha.example.com');
|
|
const beta = stats.domainActivity.find((item) => item.domain === 'beta.example.com');
|
|
|
|
expect(alpha).toBeDefined();
|
|
expect(beta).toBeDefined();
|
|
|
|
expect(alpha!.requestCount).toEqual(3);
|
|
expect(alpha!.routeCount).toEqual(1);
|
|
expect(alpha!.activeConnections).toEqual(3);
|
|
expect(alpha!.bytesInPerSecond).toEqual(900);
|
|
expect(alpha!.bytesOutPerSecond).toEqual(1800);
|
|
|
|
expect(beta!.requestCount).toEqual(1);
|
|
expect(beta!.routeCount).toEqual(1);
|
|
expect(beta!.activeConnections).toEqual(1);
|
|
expect(beta!.bytesInPerSecond).toEqual(300);
|
|
expect(beta!.bytesOutPerSecond).toEqual(600);
|
|
});
|
|
|
|
tap.test('MetricsManager prefers live domain request rates for current activity', async () => {
|
|
const proxyMetrics = createProxyMetrics({
|
|
connectionsByRoute: new Map([
|
|
['route-id-only', 10],
|
|
]),
|
|
throughputByRoute: new Map([
|
|
['route-id-only', { in: 1000, out: 1000 }],
|
|
]),
|
|
domainRequestsByIP: new Map([
|
|
['192.0.2.10', new Map([
|
|
['alpha.example.com', 1000],
|
|
['beta.example.com', 1],
|
|
])],
|
|
]),
|
|
domainRequestRates: new Map([
|
|
['alpha.example.com', { perSecond: 0, lastMinute: 0 }],
|
|
['beta.example.com', { perSecond: 5, lastMinute: 60 }],
|
|
]),
|
|
});
|
|
|
|
const smartProxy = {
|
|
getMetrics: () => proxyMetrics,
|
|
routeManager: {
|
|
getRoutes: () => [
|
|
{
|
|
id: 'route-id-only',
|
|
match: {
|
|
ports: [443],
|
|
domains: ['alpha.example.com', 'beta.example.com'],
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: '127.0.0.1', port: 8443 }],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const manager = new MetricsManager({ smartProxy } as any);
|
|
const stats = await manager.getNetworkStats();
|
|
const alpha = stats.domainActivity.find((item) => item.domain === 'alpha.example.com');
|
|
const beta = stats.domainActivity.find((item) => item.domain === 'beta.example.com');
|
|
|
|
expect(alpha!.activeConnections).toEqual(0);
|
|
expect(alpha!.requestsPerSecond).toEqual(0);
|
|
expect(beta!.activeConnections).toEqual(10);
|
|
expect(beta!.requestsPerSecond).toEqual(5);
|
|
expect(beta!.bytesInPerSecond).toEqual(1000);
|
|
});
|
|
|
|
tap.test('MetricsManager does not duplicate backend active counts onto protocol cache rows', async () => {
|
|
const proxyMetrics = createProxyMetrics({
|
|
connectionsByRoute: new Map(),
|
|
throughputByRoute: new Map(),
|
|
domainRequestsByIP: new Map(),
|
|
backendMetrics: new Map([
|
|
['192.0.2.1:443', {
|
|
protocol: 'h2',
|
|
activeConnections: 257,
|
|
totalConnections: 1000,
|
|
connectErrors: 1,
|
|
handshakeErrors: 2,
|
|
requestErrors: 3,
|
|
avgConnectTimeMs: 4,
|
|
poolHitRate: 0.9,
|
|
h2Failures: 5,
|
|
}],
|
|
]),
|
|
protocolCache: [
|
|
{
|
|
host: '192.0.2.1',
|
|
port: 443,
|
|
domain: 'alpha.example.com',
|
|
protocol: 'h2',
|
|
h2Suppressed: false,
|
|
h3Suppressed: false,
|
|
h2CooldownRemainingSecs: null,
|
|
h3CooldownRemainingSecs: null,
|
|
h2ConsecutiveFailures: null,
|
|
h3ConsecutiveFailures: null,
|
|
h3Port: null,
|
|
ageSecs: 1,
|
|
},
|
|
{
|
|
host: '192.0.2.1',
|
|
port: 443,
|
|
domain: 'beta.example.com',
|
|
protocol: 'h2',
|
|
h2Suppressed: false,
|
|
h3Suppressed: false,
|
|
h2CooldownRemainingSecs: null,
|
|
h3CooldownRemainingSecs: null,
|
|
h2ConsecutiveFailures: null,
|
|
h3ConsecutiveFailures: null,
|
|
h3Port: null,
|
|
ageSecs: 1,
|
|
},
|
|
],
|
|
});
|
|
|
|
const smartProxy = {
|
|
getMetrics: () => proxyMetrics,
|
|
routeManager: {
|
|
getRoutes: () => [],
|
|
},
|
|
};
|
|
|
|
const manager = new MetricsManager({ smartProxy } as any);
|
|
const stats = await manager.getNetworkStats();
|
|
const aggregate = stats.backends.find((item) => item.id === 'backend:192.0.2.1:443');
|
|
const cacheRows = stats.backends.filter((item) => item.id?.startsWith('cache:'));
|
|
|
|
expect(aggregate!.activeConnections).toEqual(257);
|
|
expect(cacheRows.length).toEqual(2);
|
|
expect(cacheRows.every((item) => item.activeConnections === 0)).toBeTrue();
|
|
});
|
|
|
|
export default tap.start();
|