awg-ctrl: keep CPU mode order in /api/metrics; diffbench compare and host runner
The differential bench (GEN 2 and 3.1, with I1, S4=0, upgrade with a 3.x module) now shows the Go awg-ctrl identical to the TS one. The only real difference it found: host.cpu was a Go map, so its keys came out sorted instead of in /proc/stat order. - tools/diffbench/compare.mjs: decodes vpn:// (Node and Go zlib differ), masks live host numbers, Express dev-mode stack traces, temp file names and the order of calls from concurrent background work - tools/diffbench/local.sh: the same run without Docker, stubs only on the run's own PATH (for disposable machines) - run.sh: wait for the background shaping apply before stopping the service, otherwise the stub log is cut short
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
// compare.mjs <ts.json> <go.json>: расхождения двух прогонов стенда. vpn:// раскрываются
|
||||
// (сжатие у Node и Go разное), время маскируется.
|
||||
import fs from "fs"; import zlib from "zlib";
|
||||
const [a, b] = process.argv.slice(2).map(f => JSON.parse(fs.readFileSync(f, "utf8")));
|
||||
const dec = s => { try { const buf = Buffer.from(s.slice(6), "base64url"); return "vpn<" + zlib.inflateSync(buf.subarray(4)).toString() + ">"; } catch (e) { return "vpn<undecodable " + e.message + ">"; } };
|
||||
const norm = v => {
|
||||
if (typeof v === "string") {
|
||||
let s = v.replace(/vpn:\/\/[A-Za-z0-9_-]+/g, dec);
|
||||
s = s.replace(/\d{4}-\d\d-\d\dT[\d:.-]+Z/g, "<ts>").replace(/"(created_at|lastHandshake|ts|time|uptime|generated_at)":\d+/g, '"$1":<n>');
|
||||
return s;
|
||||
}
|
||||
if (Array.isArray(v)) return v.map(norm);
|
||||
if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, /^(created_at|lastHandshake|ts|uptime)$/.test(k) && typeof x === "number" ? "<n>" : norm(x)]));
|
||||
return v;
|
||||
};
|
||||
// Известный шум: живые цифры хоста (порядок ключей сохраняем), стек Express в dev-режиме,
|
||||
// порядок вызовов из параллельных фоновых задач, отступы в CREATE TABLE.
|
||||
const host = h => h && { cpu: Object.keys(h.cpu), cores: h.cores, load: h.load?.length, mem: Object.keys(h.mem) };
|
||||
for (const r of [a, b]) {
|
||||
for (const s of r.steps) {
|
||||
if (s.body?.host) { s.body.host = host(s.body.host); s.text = JSON.stringify(s.body); }
|
||||
if (s.ctype === "text/html") s.body = s.body.replace(/<pre>([A-Za-z]+Error)[^<]*(<br>[\s\S]*)?<\/pre>/, "<pre>$1</pre>");
|
||||
}
|
||||
r.state.stubLog = r.state.stubLog.map(e => JSON.stringify(e).replace(/awg_(sync|psk)_\d+/g, "awg_$1_N")).sort();
|
||||
r.state.schema = r.state.schema.replace(/\s+/g, " ");
|
||||
}
|
||||
let diffs = 0;
|
||||
const show = (label, x, y) => { const X = JSON.stringify(norm(x)), Y = JSON.stringify(norm(y)); if (X !== Y) { diffs++; let i = 0; while (X[i] === Y[i]) i++; const from = Math.max(0, i - 150); console.log(`### ${label} @${i}\n TS: …${X.slice(from, i + 350)}\n GO: …${Y.slice(from, i + 350)}`); } };
|
||||
a.steps.forEach((s, i) => { const t = b.steps[i]; show(`${i} ${s.name}`, { st: s.status, ct: s.ctype, body: s.body, text: s.text }, { st: t.status, ct: t.ctype, body: t.body, text: t.text }); });
|
||||
for (const k of Object.keys(a.state)) show("state." + k, a.state[k], b.state[k]);
|
||||
console.log(diffs ? `${diffs} differences` : "IDENTICAL");
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
# local.sh <ts|go> <имя-прогона> — тот же прогон, что run.sh, но без Docker: на одноразовой
|
||||
# машине (CI-раннер, облачная песочница). Заглушки кладутся в свой каталог и попадают только
|
||||
# в PATH этого прогона; ВНИМАНИЕ: setup.sh пересоздаёт /etc/amnezia — не запускать на сервере.
|
||||
# Нужны node 20+, sqlite3, openssl, go; npm install в awg-ctrl/ делается при первом запуске.
|
||||
# Окружение то же: GEN, WITH_I1, S4, STUB_MODULE. Результат — tools/diffbench/out/<имя>.json.
|
||||
set -e
|
||||
IMPL="$1"; NAME="$2"
|
||||
B=$(cd "$(dirname "$0")" && pwd); ROOT=$(cd "$B/../.." && pwd)
|
||||
mkdir -p "$B/bin/stubs" "$B/out"
|
||||
for c in awg awg-quick ip tc nft modinfo; do
|
||||
printf '#!/bin/sh\nexec node %s %s "$@"\n' "$B/stubs/stub.mjs" "$c" > "$B/bin/stubs/$c"; chmod +x "$B/bin/stubs/$c"
|
||||
done
|
||||
export PATH="$B/bin/stubs:$PATH"
|
||||
if [ "$IMPL" = "go" ]; then (cd "$ROOT" && go build -o "$B/bin/awg-control" ./cmd/awg-control)
|
||||
elif [ ! -d "$ROOT/awg-ctrl/node_modules" ]; then (cd "$ROOT/awg-ctrl" && npm install --no-audit --no-fund --loglevel=error --no-package-lock); fi
|
||||
sh "$B/setup.sh"
|
||||
export PORT=3005 SERVER_IP=203.0.113.7 SERVER_PORT=47619 SERVER_NAME=VPN \
|
||||
INTERNAL_AUTH_PUB_FILE=/etc/amnezia/amneziawg/internal_auth_public.key \
|
||||
SHAPE_SAMPLE_S=3600 AWG_CLI=/nonexistent/awg-ctrl
|
||||
cd "$ROOT/awg-ctrl"
|
||||
if [ "$IMPL" = "ts" ]; then node node_modules/.bin/tsx index.ts > /run/bench/ctrl.log 2>&1 &
|
||||
else "$B/bin/awg-control" ctrl > /run/bench/ctrl.log 2>&1 &
|
||||
fi
|
||||
PID=$!
|
||||
i=0; until curl -s -o /dev/null http://127.0.0.1:3005/health; do i=$((i+1)); [ $i -gt 150 ] && { cat /run/bench/ctrl.log; kill $PID; exit 1; }; sleep 0.1; done
|
||||
sleep 1
|
||||
cd "$B"
|
||||
node scenario-ctrl.mjs "$B/out/$NAME.json"
|
||||
sleep 3
|
||||
kill $PID 2>/dev/null || true; wait $PID 2>/dev/null || true
|
||||
node collect.mjs "$B/out/$NAME.json"
|
||||
cp /run/bench/ctrl.log "$B/out/$NAME.log"
|
||||
@@ -17,6 +17,8 @@ i=0; until curl -s -o /dev/null http://127.0.0.1:3005/health; do i=$((i+1)); [ $
|
||||
sleep 1 # стартовый apply шейпинга асинхронный — даём ему отработать до сценария
|
||||
cd /bench
|
||||
node scenario-ctrl.mjs "/out/$NAME.json"
|
||||
# фоновый apply шейпинга после restart/upgrade должен доработать, иначе журнал заглушек обрежется
|
||||
sleep 3
|
||||
kill $PID 2>/dev/null || true; wait $PID 2>/dev/null || true
|
||||
node collect.mjs "/out/$NAME.json"
|
||||
cp /run/bench/ctrl.log "/out/$NAME.log"
|
||||
|
||||
Reference in New Issue
Block a user