优化数据消耗
This commit is contained in:
1 parent
a7194dd287
commit
c971049290
35 files changed
+1491
-854
No files matched your search
@@ -9,6 +9,7 @@ import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
|
||||
const binary = resolve(process.env.MONITOR_AGENT_BINARY ?? "agent/target/debug/nodeflare-agent");
|
||||
const directory = mkdtempSync(join(tmpdir(), "nodeflare-agent-runtime-"));
|
||||
@@ -67,7 +68,7 @@ try {
|
||||
proxy.listen(0, "127.0.0.1");
|
||||
await once(proxy, "listening");
|
||||
|
||||
console.log("agent runtime: automatic update during one-second sampling");
|
||||
console.log("agent runtime: automatic update during three-second sampling");
|
||||
websocketServer = new WebSocketServer({ noServer: true });
|
||||
upgradeServer = createHttpServer();
|
||||
let redirected = false;
|
||||
@@ -89,27 +90,30 @@ try {
|
||||
socket.send(JSON.stringify({
|
||||
type: "config", ts: Math.floor(Date.now() / 1_000),
|
||||
config: {
|
||||
report_interval: 15, collect_interval: 1, network_interface: "",
|
||||
report_interval: 15, collect_interval: 3, network_interface: "",
|
||||
agent_mirror: "", auto_update: true, latency_tasks: [],
|
||||
},
|
||||
}));
|
||||
socket.on("message", (raw) => {
|
||||
const message = JSON.parse(raw.toString());
|
||||
if (message.type !== "update") return;
|
||||
let receivedThrough = 0;
|
||||
socket.on("message", (raw, binary) => {
|
||||
if (!binary) return;
|
||||
const message = JSON.parse(gunzipSync(raw).toString());
|
||||
samples += message.samples.length;
|
||||
if (message.persist) persistedThrough = Math.max(...message.samples.map((sample) => sample.timestamp));
|
||||
receivedThrough = Math.max(receivedThrough, ...message.samples.map((sample) => sample.metrics.timestamp));
|
||||
if (!message.persist) return;
|
||||
persistedThrough = receivedThrough;
|
||||
socket.send(JSON.stringify({
|
||||
type: "ack", ts: Math.floor(Date.now() / 1_000),
|
||||
persisted: message.persist, persistenceError: false,
|
||||
persistedThroughTs: persistedThrough,
|
||||
nextPersistAfterMs: 1_000, nextWssReportAfterMs: 1_000, realtimeHint: false,
|
||||
nextPersistAfterMs: 15_000,
|
||||
}));
|
||||
});
|
||||
});
|
||||
let token;
|
||||
for (let index = 0; index < 100_000; index++) {
|
||||
const candidate = `runtime-update-${index}`;
|
||||
if (createHash("sha256").update(candidate).digest().readBigUInt64BE(0) % 1_801n === 2n) {
|
||||
if (createHash("sha256").update(candidate).digest().readBigUInt64BE(0) % 1_801n === 5n) {
|
||||
token = candidate;
|
||||
break;
|
||||
}
|
||||
@@ -121,6 +125,40 @@ try {
|
||||
assert(redirected, "Same-origin WebSocket redirects must remain supported");
|
||||
await stopAgent(updating);
|
||||
|
||||
console.log("agent runtime: three-second uploads are unique and reconnect replays unconfirmed samples");
|
||||
const telemetryConnections = [];
|
||||
const trackTelemetry = (socket) => {
|
||||
const connection = { timestamps: [], durable: 0, firstInfo: false, infoCount: 0, commits: 0 };
|
||||
telemetryConnections.push(connection);
|
||||
socket.on("message", (raw, binary) => {
|
||||
if (!binary) return;
|
||||
const message = JSON.parse(gunzipSync(raw).toString());
|
||||
for (const sample of message.samples) {
|
||||
if (!connection.timestamps.length) connection.firstInfo = Boolean(sample.info);
|
||||
connection.infoCount += Number(Boolean(sample.info));
|
||||
assert(!connection.timestamps.includes(sample.metrics.timestamp), "Sample was uploaded twice on the same connection");
|
||||
connection.timestamps.push(sample.metrics.timestamp);
|
||||
}
|
||||
if (message.persist) {
|
||||
connection.durable = connection.timestamps.at(-1);
|
||||
connection.commits++;
|
||||
}
|
||||
if (telemetryConnections.length === 1 && connection.timestamps.length === 3) socket.terminate();
|
||||
});
|
||||
};
|
||||
websocketServer.on("connection", trackTelemetry);
|
||||
const telemetry = startAgent(upgradeServer.address().port, "runtime-telemetry", "telemetry");
|
||||
await waitUntil(() => telemetryConnections.length >= 2 && telemetryConnections[1].commits >= 2,
|
||||
40_000, "Telemetry reconnect or commit did not complete");
|
||||
await stopAgent(telemetry);
|
||||
websocketServer.off("connection", trackTelemetry);
|
||||
const [before, after] = telemetryConnections;
|
||||
assert(before.firstInfo && after.firstInfo, "Static info must be resent on reconnect");
|
||||
assert.equal(before.infoCount, 1, "Unchanged static info must not be uploaded repeatedly");
|
||||
assert(before.timestamps.slice(1).every((timestamp) => after.timestamps.includes(timestamp)), "Unconfirmed samples were lost");
|
||||
assert(!after.timestamps.includes(before.durable), "Durable samples must not be replayed");
|
||||
assert(before.timestamps.slice(1).every((timestamp, index) => timestamp - before.timestamps[index] >= 3), "Sampling interval is shorter than three seconds");
|
||||
|
||||
console.log("agent runtime: remote command survives disconnect and returns its result");
|
||||
const taskId = randomUUID();
|
||||
let remoteConnections = 0;
|
||||
@@ -128,7 +166,8 @@ try {
|
||||
let remoteResult;
|
||||
websocketServer.on("connection", (socket) => {
|
||||
remoteConnections++;
|
||||
socket.on("message", (raw) => {
|
||||
socket.on("message", (raw, binary) => {
|
||||
if (binary) return;
|
||||
const message = JSON.parse(raw.toString());
|
||||
if (message.type === "task_received" && message.task_id === taskId) {
|
||||
received = true;
|
||||
|
||||
@@ -272,7 +272,7 @@ MONITOR_BASE_URL="$MONITOR_BASE_URL" MONITOR_ADMIN_TOKEN="$admin_token" \
|
||||
if [ -n "${MONITOR_AGENT_BINARY:-}" ]; then
|
||||
step "real Agent failed-latency and persistence"
|
||||
request -H "Authorization: Bearer $admin_token" -H 'Content-Type: application/json' -X PATCH \
|
||||
--data "$(printf '%s' "$server_input" | jq '.report_interval=15 | .collect_interval=1')" \
|
||||
--data "$(printf '%s' "$server_input" | jq '.report_interval=15 | .collect_interval=3')" \
|
||||
"$MONITOR_BASE_URL/api/admin/servers/$server_id" >/dev/null
|
||||
agent_latency_task_id=$(request -H "Authorization: Bearer $admin_token" \
|
||||
-H 'Content-Type: application/json' \
|
||||
|
||||
+24
-40
@@ -1,6 +1,22 @@
|
||||
import WebSocket from "ws";
|
||||
import assert from "node:assert/strict";
|
||||
import { once } from "node:events";
|
||||
import { gzipSync, gunzipSync } from "node:zlib";
|
||||
|
||||
function telemetryPayload(reports, persist) {
|
||||
return gzipSync(JSON.stringify({
|
||||
samples: reports.map((report) => {
|
||||
const metrics = { ...report };
|
||||
const info = {};
|
||||
for (const key of ["cpu_model", "os", "kernel", "arch", "virtualization", "gpu_model", "agent_version", "ip_v4", "ip_v6"]) {
|
||||
info[key] = metrics[key] ?? "";
|
||||
delete metrics[key];
|
||||
}
|
||||
return { metrics, info };
|
||||
}),
|
||||
persist,
|
||||
}));
|
||||
}
|
||||
|
||||
const baseUrl = process.env.MONITOR_BASE_URL;
|
||||
const adminToken = process.env.MONITOR_ADMIN_TOKEN;
|
||||
@@ -190,10 +206,10 @@ function waitForJsonMessage(socket, expected, predicate) {
|
||||
cleanup();
|
||||
reject(error);
|
||||
};
|
||||
const onMessage = (data) => {
|
||||
const onMessage = (data, binary) => {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(data.toString());
|
||||
message = JSON.parse((binary ? gunzipSync(data) : data).toString());
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
@@ -358,7 +374,7 @@ try {
|
||||
name: "Offline remote smoke", region: "", group_name: "", tags: "", hidden: true,
|
||||
expires_at: null, traffic_limit: 0, traffic_limit_type: "max", price: 0,
|
||||
billing_cycle: 30, currency: "USD", auto_renewal: false, network_interface: "",
|
||||
reset_day: 1, report_interval: 60, collect_interval: 1, rx_correction: 0,
|
||||
reset_day: 1, report_interval: 60, collect_interval: 3, rx_correction: 0,
|
||||
tx_correction: 0, agent_mirror: "", offline_notify_disabled: true, auto_update: false,
|
||||
}),
|
||||
});
|
||||
@@ -554,27 +570,6 @@ try {
|
||||
});
|
||||
}
|
||||
|
||||
const wakeHintPromise = waitForJsonMessage(
|
||||
agent,
|
||||
"batched overview wake hint",
|
||||
(message) => message.type === "ack" && message.realtimeHint === true,
|
||||
);
|
||||
const wakeResponse = await fetch(new URL("/api/live/wake", baseUrl), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ server_ids: [serverId] }),
|
||||
});
|
||||
if (wakeResponse.status !== 204) {
|
||||
throw new Error(`Batch wake returned HTTP ${wakeResponse.status}: ${await wakeResponse.text()}`);
|
||||
}
|
||||
const wakeHint = await wakeHintPromise;
|
||||
if (wakeHint.nextWssReportAfterMs !== 5_000) {
|
||||
throw new Error(`Invalid batch wake hint: ${JSON.stringify(wakeHint)}`);
|
||||
}
|
||||
|
||||
const timestamp = Math.floor(Date.now() / 1_000);
|
||||
const baseSample = {
|
||||
timestamp,
|
||||
@@ -654,22 +649,10 @@ try {
|
||||
sample(5, { net_rx_total: 536870912, net_tx_total: 268435456 }),
|
||||
];
|
||||
const latestTimestamp = timestamp + 5;
|
||||
for (const persist of [undefined, null]) {
|
||||
const rejectedAck = waitForJsonMessage(
|
||||
agent,
|
||||
"invalid persistence flag ACK",
|
||||
(message) => message.type === "ack" && message.realtimeHint === false,
|
||||
);
|
||||
agent.send(JSON.stringify({ type: "update", batchId: "invalid-persist", samples, persist }));
|
||||
const rejected = await rejectedAck;
|
||||
if (rejected.persistenceError !== true || rejected.persisted !== false) {
|
||||
throw new Error(`Accepted invalid persistence flag: ${JSON.stringify(rejected)}`);
|
||||
}
|
||||
}
|
||||
const ackPromise = waitForJsonMessage(
|
||||
agent,
|
||||
"Agent metric ACK",
|
||||
(message) => message.type === "ack" && message.realtimeHint === false,
|
||||
(message) => message.type === "ack",
|
||||
);
|
||||
const updatePromise = waitForJsonMessage(
|
||||
dashboard,
|
||||
@@ -684,7 +667,7 @@ try {
|
||||
),
|
||||
),
|
||||
);
|
||||
agent.send(JSON.stringify({ type: "update", batchId: `smoke-${timestamp}`, samples, persist: true }));
|
||||
agent.send(telemetryPayload(samples, true));
|
||||
const [ack, update] = await Promise.all([ackPromise, updatePromise]);
|
||||
if (
|
||||
ack.ts <= 0 ||
|
||||
@@ -692,11 +675,12 @@ try {
|
||||
ack.persistenceError !== false ||
|
||||
!Number.isInteger(ack.persistedThroughTs) ||
|
||||
ack.persistedThroughTs < latestTimestamp ||
|
||||
ack.nextPersistAfterMs !== 60_000 ||
|
||||
ack.nextWssReportAfterMs !== 5_000
|
||||
ack.nextPersistAfterMs !== 60_000
|
||||
) {
|
||||
throw new Error(`Invalid Agent metric ACK: ${JSON.stringify(ack)}`);
|
||||
}
|
||||
assert.equal(update.updates[0].samples.length, 1, "Browser receives only the latest metric snapshot");
|
||||
assert.equal(update.updates[0].samples[0].data.latency_results.length, 2, "All new latency results are retained");
|
||||
|
||||
const publicIdentityFields = [
|
||||
"cpu_model",
|
||||
|
||||
Reference in new issue
Block a user