feat(route-optimizer): refactor bandwidth scoring and enhance speed display in UI for improved clarity
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
This commit is contained in:
@@ -77,7 +77,7 @@ function latencyScore(pingMs, aiSettings) {
|
||||
return clamp(normalized, 0, 1);
|
||||
}
|
||||
|
||||
function bandwidthScore(speedMbps, aiSettings) {
|
||||
function normalizeBandwidthValue(speedMbps, aiSettings) {
|
||||
if (typeof speedMbps !== 'number' || speedMbps <= 0) return aiSettings.noSpeedScore;
|
||||
// log-scale to avoid dominance by very high channels
|
||||
const normalized = Math.log10(1 + speedMbps) / Math.log10(1001);
|
||||
@@ -96,11 +96,29 @@ function freshnessScore(cacheUpdatedAt, aiSettings) {
|
||||
return aiSettings.staleScore;
|
||||
}
|
||||
|
||||
function buildSegmentScore({ pingMs, speedMbps, cacheUpdatedAt, aiSettings }) {
|
||||
function bandwidthScore({ speedDownloadMbps, speedUploadMbps, aiSettings }) {
|
||||
// DL/UL considered separately with equal 50/50 contribution.
|
||||
const dl = normalizeBandwidthValue(speedDownloadMbps, aiSettings);
|
||||
const ul = normalizeBandwidthValue(speedUploadMbps, aiSettings);
|
||||
return clamp((dl + ul) / 2, 0, 1);
|
||||
}
|
||||
|
||||
function buildSegmentScore({
|
||||
pingMs,
|
||||
speedMbps,
|
||||
speedDownloadMbps,
|
||||
speedUploadMbps,
|
||||
cacheUpdatedAt,
|
||||
aiSettings,
|
||||
}) {
|
||||
const l = latencyScore(pingMs, aiSettings);
|
||||
const b = bandwidthScore(speedMbps, aiSettings);
|
||||
const b = bandwidthScore({ speedDownloadMbps, speedUploadMbps, aiSettings });
|
||||
const f = freshnessScore(cacheUpdatedAt, aiSettings);
|
||||
const metricPresence = (typeof pingMs === 'number' ? 1 : 0) + (typeof speedMbps === 'number' ? 1 : 0);
|
||||
const hasAnySpeed =
|
||||
typeof speedMbps === 'number' ||
|
||||
typeof speedDownloadMbps === 'number' ||
|
||||
typeof speedUploadMbps === 'number';
|
||||
const metricPresence = (typeof pingMs === 'number' ? 1 : 0) + (hasAnySpeed ? 1 : 0);
|
||||
const confidence = metricPresence === 2 ? 1 : metricPresence === 1 ? 0.65 : 0.35;
|
||||
const score =
|
||||
l * aiSettings.latencyWeight +
|
||||
@@ -329,10 +347,24 @@ function buildInterfaceCandidates({ servers, tunnelInterfaces, pingMap, speedMap
|
||||
const speedEntry = asObject(speedMap[skey]);
|
||||
const downBps = toNumber(speedEntry.tcpDownloadBps);
|
||||
const upBps = toNumber(speedEntry.tcpUploadBps);
|
||||
const speedMbps = downBps != null || upBps != null
|
||||
? Math.max(downBps || 0, upBps || 0) / 1e6
|
||||
: null;
|
||||
const scoring = buildSegmentScore({ pingMs, speedMbps, cacheUpdatedAt, aiSettings });
|
||||
const speedDownloadMbps = downBps != null ? downBps / 1e6 : null;
|
||||
const speedUploadMbps = upBps != null ? upBps / 1e6 : null;
|
||||
const speedMbps =
|
||||
speedDownloadMbps != null && speedUploadMbps != null
|
||||
? (speedDownloadMbps + speedUploadMbps) / 2
|
||||
: speedDownloadMbps != null
|
||||
? speedDownloadMbps
|
||||
: speedUploadMbps != null
|
||||
? speedUploadMbps
|
||||
: null;
|
||||
const scoring = buildSegmentScore({
|
||||
pingMs,
|
||||
speedMbps,
|
||||
speedDownloadMbps,
|
||||
speedUploadMbps,
|
||||
cacheUpdatedAt,
|
||||
aiSettings,
|
||||
});
|
||||
|
||||
const base = {
|
||||
interfaceName: iface.name || null,
|
||||
@@ -340,8 +372,8 @@ function buildInterfaceCandidates({ servers, tunnelInterfaces, pingMap, speedMap
|
||||
remoteIp: iface.remoteIp || null,
|
||||
pingMs,
|
||||
speedMbps: speedMbps != null ? Number(speedMbps.toFixed(2)) : null,
|
||||
speedDownloadMbps: downBps != null ? Number((downBps / 1e6).toFixed(2)) : null,
|
||||
speedUploadMbps: upBps != null ? Number((upBps / 1e6).toFixed(2)) : null,
|
||||
speedDownloadMbps: speedDownloadMbps != null ? Number(speedDownloadMbps.toFixed(2)) : null,
|
||||
speedUploadMbps: speedUploadMbps != null ? Number(speedUploadMbps.toFixed(2)) : null,
|
||||
score: Number(scoring.score.toFixed(4)),
|
||||
confidence: Number(scoring.confidence.toFixed(4)),
|
||||
};
|
||||
@@ -639,6 +671,8 @@ function buildCommunityOptimization({
|
||||
probabilityOptimal: currentCandidate.probabilityOptimal,
|
||||
pingMs: currentCandidate.pingMs,
|
||||
speedMbps: currentCandidate.speedMbps,
|
||||
speedDownloadMbps: currentCandidate.speedDownloadMbps,
|
||||
speedUploadMbps: currentCandidate.speedUploadMbps,
|
||||
} : null,
|
||||
recommended: recommendedCandidate ? {
|
||||
gateway: recommendedCandidate.recursiveGateway || recommendedCandidate.gatewayIpForJumphost || null,
|
||||
@@ -650,6 +684,8 @@ function buildCommunityOptimization({
|
||||
probabilityOptimal: recommendedCandidate.probabilityOptimal,
|
||||
pingMs: recommendedCandidate.pingMs,
|
||||
speedMbps: recommendedCandidate.speedMbps,
|
||||
speedDownloadMbps: recommendedCandidate.speedDownloadMbps,
|
||||
speedUploadMbps: recommendedCandidate.speedUploadMbps,
|
||||
} : null,
|
||||
pinnedGateway: pinned?.gateway || null,
|
||||
pinnedBySettings: Boolean(pinned),
|
||||
@@ -721,6 +757,8 @@ async function getRouteOptimizer(req, res) {
|
||||
interfaceName: hj.interfaceName,
|
||||
pingMs: hj.pingMs,
|
||||
speedMbps: hj.speedMbps,
|
||||
speedDownloadMbps: hj.speedDownloadMbps,
|
||||
speedUploadMbps: hj.speedUploadMbps,
|
||||
score: hj.score,
|
||||
confidence: hj.confidence,
|
||||
},
|
||||
@@ -728,6 +766,8 @@ async function getRouteOptimizer(req, res) {
|
||||
interfaceName: jhExit.interfaceName,
|
||||
pingMs: jhExit.pingMs,
|
||||
speedMbps: jhExit.speedMbps,
|
||||
speedDownloadMbps: jhExit.speedDownloadMbps,
|
||||
speedUploadMbps: jhExit.speedUploadMbps,
|
||||
score: jhExit.score,
|
||||
confidence: jhExit.confidence,
|
||||
},
|
||||
|
||||
@@ -22,6 +22,13 @@ function fmtProb(v) {
|
||||
return typeof v === 'number' ? `${v}%` : '—';
|
||||
}
|
||||
|
||||
function fmtSpeed(dl, ul, fallback) {
|
||||
if (typeof dl === 'number' || typeof ul === 'number') {
|
||||
return `DL: ${dl ?? '—'} Mbps, UL: ${ul ?? '—'} Mbps`;
|
||||
}
|
||||
return `speed: ${fallback ?? '—'} Mbps`;
|
||||
}
|
||||
|
||||
const OPT_TABLE_STYLE = { tableLayout: 'fixed', width: '100%' };
|
||||
const OPT_COLS = ['24%', '20%', '22%', '18%', '16%'];
|
||||
|
||||
@@ -167,7 +174,7 @@ export default function RouteOptimizerPage() {
|
||||
<td className="fw-semibold text-break">{c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'}</td>
|
||||
<td className="small text-muted text-break">{c.interfaceName || '—'}</td>
|
||||
<td className="small text-muted text-break">
|
||||
ping: {c.pingMs ?? '—'} ms, speed: {c.speedMbps ?? '—'} Mbps
|
||||
ping: {c.pingMs ?? '—'} ms, {fmtSpeed(c.speedDownloadMbps, c.speedUploadMbps, c.speedMbps)}
|
||||
</td>
|
||||
<td className="small">
|
||||
{c.score} / {c.confidence}
|
||||
@@ -229,10 +236,18 @@ export default function RouteOptimizerPage() {
|
||||
<span className="fw-semibold">{r.exit?.label}</span>
|
||||
</td>
|
||||
<td className="small text-muted text-break">
|
||||
ping: {r.homeToJumphost?.pingMs ?? '—'} ms, speed: {r.homeToJumphost?.speedMbps ?? '—'} Mbps
|
||||
ping: {r.homeToJumphost?.pingMs ?? '—'} ms, {fmtSpeed(
|
||||
r.homeToJumphost?.speedDownloadMbps,
|
||||
r.homeToJumphost?.speedUploadMbps,
|
||||
r.homeToJumphost?.speedMbps
|
||||
)}
|
||||
</td>
|
||||
<td className="small text-muted text-break">
|
||||
ping: {r.jumphostToExit?.pingMs ?? '—'} ms, speed: {r.jumphostToExit?.speedMbps ?? '—'} Mbps
|
||||
ping: {r.jumphostToExit?.pingMs ?? '—'} ms, {fmtSpeed(
|
||||
r.jumphostToExit?.speedDownloadMbps,
|
||||
r.jumphostToExit?.speedUploadMbps,
|
||||
r.jumphostToExit?.speedMbps
|
||||
)}
|
||||
</td>
|
||||
<td className="small">{r.score} / {r.confidence ?? '—'}</td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user