完善serverless部署环境
This commit is contained in:
@@ -4,6 +4,115 @@ const db = require('./db');
|
||||
|
||||
const POLL_INTERVAL = 10000; // 10 seconds
|
||||
|
||||
async function resolveBlackboxLatency(route) {
|
||||
// Blackbox exporter probe URL
|
||||
// We assume ICMP module for now. If target is a URL, maybe use http_2xx
|
||||
let module = 'icmp';
|
||||
const target = route.latency_target;
|
||||
|
||||
if (target.startsWith('http://') || target.startsWith('https://')) {
|
||||
module = 'http_2xx';
|
||||
}
|
||||
|
||||
const probeUrl = `${route.url.replace(/\/+$/, '')}/probe?module=${module}&target=${encodeURIComponent(target)}`;
|
||||
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');
|
||||
}
|
||||
|
||||
const lines = response.data.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
||||
|
||||
// 1. Check if the probe was successful
|
||||
let isProbeSuccess = false;
|
||||
for (const line of lines) {
|
||||
if (/^probe_success(\{.*\})?\s+1/.test(line)) {
|
||||
isProbeSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extract latency from priority metrics
|
||||
const targetMetrics = [
|
||||
'probe_icmp_duration_seconds',
|
||||
'probe_http_duration_seconds',
|
||||
'probe_duration_seconds'
|
||||
];
|
||||
|
||||
let foundLatency = null;
|
||||
for (const metricName of targetMetrics) {
|
||||
let bestLine = null;
|
||||
|
||||
// First pass: look for phase="rtt" which is the most accurate "ping"
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(metricName) && line.includes('phase="rtt"')) {
|
||||
bestLine = line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: if no rtt phase, look for a line without phases (legacy format) or just the first line
|
||||
if (!bestLine) {
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(metricName)) {
|
||||
// Prefer lines without {} if possible, otherwise take the first one
|
||||
if (!line.includes('{')) {
|
||||
bestLine = line;
|
||||
break;
|
||||
}
|
||||
if (!bestLine) bestLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLine) {
|
||||
// Regex to capture the number, including scientific notation
|
||||
const regex = new RegExp(`^${metricName}(?:\\{[^}]*\\})?\\s+([\\d.eE+-]+)`);
|
||||
const match = bestLine.match(regex);
|
||||
|
||||
if (match) {
|
||||
const val = parseFloat(match[1]);
|
||||
if (!isNaN(val)) {
|
||||
foundLatency = val * 1000; // convert to ms
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Final decision
|
||||
// If it's a success, use found latency. If success=0 or missing, handle carefully.
|
||||
if (isProbeSuccess && foundLatency !== null) {
|
||||
return foundLatency;
|
||||
}
|
||||
|
||||
// If probe failed or metrics missing, do not show 0, show null (Measurement in progress/Error)
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolveLatencyForRoute(route) {
|
||||
try {
|
||||
if (route.source_type === 'blackbox' || route.type === 'blackbox') {
|
||||
const latency = await resolveBlackboxLatency(route);
|
||||
if (route.id !== undefined) {
|
||||
await cache.set(`latency:route:${route.id}`, latency, 60);
|
||||
}
|
||||
return latency;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
if (route.id !== undefined) {
|
||||
await cache.set(`latency:route:${route.id}`, null, 60);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function pollLatency() {
|
||||
try {
|
||||
const [routes] = await db.query(`
|
||||
@@ -18,99 +127,7 @@ async function pollLatency() {
|
||||
// Poll each route
|
||||
await Promise.allSettled(routes.map(async (route) => {
|
||||
try {
|
||||
// Blackbox exporter probe URL
|
||||
// We assume ICMP module for now. If target is a URL, maybe use http_2xx
|
||||
let module = 'icmp';
|
||||
let target = route.latency_target;
|
||||
|
||||
if (target.startsWith('http://') || target.startsWith('https://')) {
|
||||
module = 'http_2xx';
|
||||
}
|
||||
|
||||
const probeUrl = `${route.url.replace(/\/+$/, '')}/probe?module=${module}&target=${encodeURIComponent(target)}`;
|
||||
|
||||
const startTime = Date.now();
|
||||
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');
|
||||
}
|
||||
|
||||
const lines = response.data.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
||||
|
||||
// 1. Check if the probe was successful
|
||||
let isProbeSuccess = false;
|
||||
for (const line of lines) {
|
||||
if (/^probe_success(\{.*\})?\s+1/.test(line)) {
|
||||
isProbeSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extract latency from priority metrics
|
||||
const targetMetrics = [
|
||||
'probe_icmp_duration_seconds',
|
||||
'probe_http_duration_seconds',
|
||||
'probe_duration_seconds'
|
||||
];
|
||||
|
||||
let foundLatency = null;
|
||||
for (const metricName of targetMetrics) {
|
||||
let bestLine = null;
|
||||
|
||||
// First pass: look for phase="rtt" which is the most accurate "ping"
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(metricName) && line.includes('phase="rtt"')) {
|
||||
bestLine = line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: if no rtt phase, look for a line without phases (legacy format) or just the first line
|
||||
if (!bestLine) {
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(metricName)) {
|
||||
// Prefer lines without {} if possible, otherwise take the first one
|
||||
if (!line.includes('{')) {
|
||||
bestLine = line;
|
||||
break;
|
||||
}
|
||||
if (!bestLine) bestLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLine) {
|
||||
// Regex to capture the number, including scientific notation
|
||||
const regex = new RegExp(`^${metricName}(?:\\{[^}]*\\})?\\s+([\\d.eE+-]+)`);
|
||||
const match = bestLine.match(regex);
|
||||
|
||||
if (match) {
|
||||
const val = parseFloat(match[1]);
|
||||
if (!isNaN(val)) {
|
||||
foundLatency = val * 1000; // convert to ms
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Final decision
|
||||
// If it's a success, use found latency. If success=0 or missing, handle carefully.
|
||||
let latency;
|
||||
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);
|
||||
await resolveLatencyForRoute({ ...route, source_type: 'blackbox' });
|
||||
} catch (err) {
|
||||
await cache.set(`latency:route:${route.id}`, null, 60);
|
||||
}
|
||||
@@ -130,5 +147,7 @@ function start() {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pollLatency,
|
||||
resolveLatencyForRoute,
|
||||
start
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user