|
|
|
|
@@ -595,47 +595,84 @@ export class MetricsManager {
|
|
|
|
|
const backendMetrics = proxyMetrics.backends.byBackend();
|
|
|
|
|
const protocolCache = proxyMetrics.backends.detectedProtocols();
|
|
|
|
|
|
|
|
|
|
// Index protocol cache by "host:port"
|
|
|
|
|
const cacheByKey = new Map<string, (typeof protocolCache)[number]>();
|
|
|
|
|
// Group protocol cache entries by host:port so we can match them to backend metrics.
|
|
|
|
|
// The protocol cache is keyed by (host, port, domain) in Rust, so the same host:port
|
|
|
|
|
// can have multiple entries for different domains.
|
|
|
|
|
const cacheByBackend = new Map<string, (typeof protocolCache)[number][]>();
|
|
|
|
|
for (const entry of protocolCache) {
|
|
|
|
|
cacheByKey.set(`${entry.host}:${entry.port}`, entry);
|
|
|
|
|
const backendKey = `${entry.host}:${entry.port}`;
|
|
|
|
|
let entries = cacheByBackend.get(backendKey);
|
|
|
|
|
if (!entries) {
|
|
|
|
|
entries = [];
|
|
|
|
|
cacheByBackend.set(backendKey, entries);
|
|
|
|
|
}
|
|
|
|
|
entries.push(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const backends: Array<any> = [];
|
|
|
|
|
const seen = new Set<string>();
|
|
|
|
|
const seenCacheKeys = new Set<string>();
|
|
|
|
|
|
|
|
|
|
for (const [key, bm] of backendMetrics) {
|
|
|
|
|
seen.add(key);
|
|
|
|
|
const cache = cacheByKey.get(key);
|
|
|
|
|
backends.push({
|
|
|
|
|
backend: key,
|
|
|
|
|
domain: cache?.domain ?? null,
|
|
|
|
|
protocol: bm.protocol,
|
|
|
|
|
activeConnections: bm.activeConnections,
|
|
|
|
|
totalConnections: bm.totalConnections,
|
|
|
|
|
connectErrors: bm.connectErrors,
|
|
|
|
|
handshakeErrors: bm.handshakeErrors,
|
|
|
|
|
requestErrors: bm.requestErrors,
|
|
|
|
|
avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
|
|
|
|
|
poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
|
|
|
|
|
h2Failures: bm.h2Failures,
|
|
|
|
|
h2Suppressed: cache?.h2Suppressed ?? false,
|
|
|
|
|
h3Suppressed: cache?.h3Suppressed ?? false,
|
|
|
|
|
h2CooldownRemainingSecs: cache?.h2CooldownRemainingSecs ?? null,
|
|
|
|
|
h3CooldownRemainingSecs: cache?.h3CooldownRemainingSecs ?? null,
|
|
|
|
|
h2ConsecutiveFailures: cache?.h2ConsecutiveFailures ?? null,
|
|
|
|
|
h3ConsecutiveFailures: cache?.h3ConsecutiveFailures ?? null,
|
|
|
|
|
h3Port: cache?.h3Port ?? null,
|
|
|
|
|
cacheAgeSecs: cache?.ageSecs ?? null,
|
|
|
|
|
});
|
|
|
|
|
const cacheEntries = cacheByBackend.get(key);
|
|
|
|
|
if (!cacheEntries || cacheEntries.length === 0) {
|
|
|
|
|
// No protocol cache entry — emit one row with backend metrics only
|
|
|
|
|
backends.push({
|
|
|
|
|
backend: key,
|
|
|
|
|
domain: null,
|
|
|
|
|
protocol: bm.protocol,
|
|
|
|
|
activeConnections: bm.activeConnections,
|
|
|
|
|
totalConnections: bm.totalConnections,
|
|
|
|
|
connectErrors: bm.connectErrors,
|
|
|
|
|
handshakeErrors: bm.handshakeErrors,
|
|
|
|
|
requestErrors: bm.requestErrors,
|
|
|
|
|
avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
|
|
|
|
|
poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
|
|
|
|
|
h2Failures: bm.h2Failures,
|
|
|
|
|
h2Suppressed: false,
|
|
|
|
|
h3Suppressed: false,
|
|
|
|
|
h2CooldownRemainingSecs: null,
|
|
|
|
|
h3CooldownRemainingSecs: null,
|
|
|
|
|
h2ConsecutiveFailures: null,
|
|
|
|
|
h3ConsecutiveFailures: null,
|
|
|
|
|
h3Port: null,
|
|
|
|
|
cacheAgeSecs: null,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// One row per domain, each enriched with the shared backend metrics
|
|
|
|
|
for (const cache of cacheEntries) {
|
|
|
|
|
const compositeKey = `${cache.host}:${cache.port}:${cache.domain ?? ''}`;
|
|
|
|
|
seenCacheKeys.add(compositeKey);
|
|
|
|
|
backends.push({
|
|
|
|
|
backend: key,
|
|
|
|
|
domain: cache.domain ?? null,
|
|
|
|
|
protocol: cache.protocol ?? bm.protocol,
|
|
|
|
|
activeConnections: bm.activeConnections,
|
|
|
|
|
totalConnections: bm.totalConnections,
|
|
|
|
|
connectErrors: bm.connectErrors,
|
|
|
|
|
handshakeErrors: bm.handshakeErrors,
|
|
|
|
|
requestErrors: bm.requestErrors,
|
|
|
|
|
avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
|
|
|
|
|
poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
|
|
|
|
|
h2Failures: bm.h2Failures,
|
|
|
|
|
h2Suppressed: cache.h2Suppressed,
|
|
|
|
|
h3Suppressed: cache.h3Suppressed,
|
|
|
|
|
h2CooldownRemainingSecs: cache.h2CooldownRemainingSecs,
|
|
|
|
|
h3CooldownRemainingSecs: cache.h3CooldownRemainingSecs,
|
|
|
|
|
h2ConsecutiveFailures: cache.h2ConsecutiveFailures,
|
|
|
|
|
h3ConsecutiveFailures: cache.h3ConsecutiveFailures,
|
|
|
|
|
h3Port: cache.h3Port,
|
|
|
|
|
cacheAgeSecs: cache.ageSecs,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Include protocol cache entries with no matching backend metric
|
|
|
|
|
for (const entry of protocolCache) {
|
|
|
|
|
const key = `${entry.host}:${entry.port}`;
|
|
|
|
|
if (!seen.has(key)) {
|
|
|
|
|
const compositeKey = `${entry.host}:${entry.port}:${entry.domain ?? ''}`;
|
|
|
|
|
if (!seenCacheKeys.has(compositeKey)) {
|
|
|
|
|
backends.push({
|
|
|
|
|
backend: key,
|
|
|
|
|
backend: `${entry.host}:${entry.port}`,
|
|
|
|
|
domain: entry.domain,
|
|
|
|
|
protocol: entry.protocol,
|
|
|
|
|
activeConnections: 0,
|
|
|
|
|
|