From 5baffb7e05102c2e739ffe4f2a6986d648662a8d Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Mon, 6 Apr 2026 01:07:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BB=B6=E8=BF=9F=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/latency-service.js | 60 ++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/server/latency-service.js b/server/latency-service.js index 9683c63..10566bb 100644 --- a/server/latency-service.js +++ b/server/latency-service.js @@ -30,22 +30,28 @@ async function pollLatency() { const probeUrl = `${route.url.replace(/\/+$/, '')}/probe?module=${module}&target=${encodeURIComponent(target)}`; const startTime = Date.now(); - const response = await axios.get(probeUrl, { timeout: 5000 }); + const response = await axios.get(probeUrl, { + timeout: 5000, + responseType: 'text', + validateStatus: false + }); + + if (typeof response.data !== 'string') { + throw new Error('Response data is not a string'); + } - // 1. Parse prometheus text format for success and specific metrics - let latency = null; const lines = response.data.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')); - // Track success but also try to parse latency anyway as a backup + // 1. Check if the probe was successful let isProbeSuccess = false; for (const line of lines) { - if (line.match(/^probe_success(\{.*\})?\s+1/)) { + if (/^probe_success(\{.*\})?\s+1/.test(line)) { isProbeSuccess = true; break; } } - // Try a wider set of potential latency metrics + // 2. Extract latency from priority metrics const targetMetrics = [ 'probe_icmp_duration_seconds', 'probe_http_duration_seconds', @@ -53,34 +59,50 @@ async function pollLatency() { 'probe_duration_seconds' ]; + let foundLatency = null; + const encodedTarget = target.toLowerCase(); + for (const metricName of targetMetrics) { - // Match: metric_name{labels} value - // Regex handles optional labels and scientific notation - const regex = new RegExp(`^${metricName}(?:\\{[^}]*\\})?\\s+([\\d.eE+-]+)`); for (const line of lines) { + if (!line.startsWith(metricName)) continue; + + // Precise matching for the specific target if multiple targets exist in data + // Handles: metric{instance="target"} value OR metric value + const regex = new RegExp(`^${metricName}(?:\\{[^}]*\\})?\\s+([\\d.eE+-]+)`); const match = line.match(regex); + if (match) { - const val = parseFloat(match[1]); - if (!isNaN(val)) { - latency = val * 1000; // to ms - break; + // If there are labels, verify they relate to our target (if target label exists) + if (line.includes('{')) { + const labelsPart = line.substring(line.indexOf('{') + 1, line.lastIndexOf('}')).toLowerCase(); + // Only check if the labels contain our target to be safe, + // though /probe usually only returns one target anyway. + if (labelsPart.includes(encodedTarget) || labelsPart.includes('instance') || labelsPart.includes('target')) { + const val = parseFloat(match[1]); + if (!isNaN(val)) foundLatency = val * 1000; + } + } else { + const val = parseFloat(match[1]); + if (!isNaN(val)) foundLatency = val * 1000; } } + if (foundLatency !== null) break; } - if (latency !== null) break; + if (foundLatency !== null) break; } - // If probe reported failure but we found a duration, we might still want to show null/error - if (!isProbeSuccess && latency !== null) { - // If probe failed, force null to indicate error/offline on UI + // 3. Final decision + // If it's a success, use found latency. If success=0 or missing, handle carefully. + if (isProbeSuccess && foundLatency !== null) { + latency = foundLatency; + } else { + // If probe failed or metrics missing, do not show 0, show null (Measurement in progress/Error) latency = null; } // Save to Valkey await cache.set(`latency:route:${route.id}`, latency, 60); - // console.log(`[Latency] Route ${route.id} (${target}): ${latency.toFixed(2)}ms`); } catch (err) { - // console.error(`[Latency] Error polling route ${route.id}:`, err.message); await cache.set(`latency:route:${route.id}`, null, 60); } }));