@mark1russell7/harness 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/build/index.d.ts +132 -8
- package/build/index.js +163 -15
- package/build/index.js.map +1 -1
- package/image/go-http/Dockerfile +14 -0
- package/image/go-http/go.mod +3 -0
- package/image/go-http/main.go +324 -0
- package/image/node-http/Dockerfile +12 -0
- package/image/node-http/client-agent.mjs +163 -0
- package/image/node-http/client-undici.mjs +141 -0
- package/image/node-http/lines.mjs +59 -0
- package/image/node-http/npm-lock.json +22 -0
- package/image/node-http/package.json +8 -0
- package/package.json +11 -2
package/build/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
const isHopPlace = (place) => typeof place === "object" && "hop" in place;
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/verdict.ts
|
|
13
|
+
/** This function gives the words of the outcome of a second request: its connection, a retry, and its end. */
|
|
14
|
+
const requestWords = (outcome) => `${outcome.connection}${outcome.retried ? ", a retry" : ""}, ${outcome.pending === true ? "pending" : outcome.error ?? "a reply"}`;
|
|
13
15
|
/**
|
|
14
16
|
* This amount is the noise of the pipeline itself, δ_abs, as the calibration measured it. It is
|
|
15
17
|
* the 99.9th percentile of the whole latency of a reset, at the concurrency cap or less. Its
|
|
@@ -115,14 +117,32 @@ const middle = (sorted) => {
|
|
|
115
117
|
* which does not exist at this time.
|
|
116
118
|
*/
|
|
117
119
|
const verdictOf = (prediction, runs) => {
|
|
118
|
-
const
|
|
120
|
+
const band = verdictOfBand(prediction, runs);
|
|
119
121
|
const counted = runs.filter((run) => run.void === null);
|
|
122
|
+
const verdict = counted.some((run) => run.request !== void 0) ? withRequests(band, counted) : band;
|
|
120
123
|
if (prediction.group === void 0 && !counted.some((run) => run.group !== void 0)) return verdict;
|
|
121
124
|
return {
|
|
122
125
|
...verdict,
|
|
123
126
|
group: groupVerdictOf(prediction, counted)
|
|
124
127
|
};
|
|
125
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* This function adds the outcomes of the second request of an HTTP client to a verdict. A cell
|
|
131
|
+
* whose outcome is a distribution gets them in its reason too.
|
|
132
|
+
*/
|
|
133
|
+
const withRequests = (verdict, counted) => {
|
|
134
|
+
const requests = {};
|
|
135
|
+
for (const run of counted) {
|
|
136
|
+
const words = run.request === null || run.request === void 0 ? "no second request" : requestWords(run.request);
|
|
137
|
+
requests[words] = (requests[words] ?? 0) + 1;
|
|
138
|
+
}
|
|
139
|
+
const listed = Object.entries(requests).map(([words, n]) => `${n} ${words}`).join("; ");
|
|
140
|
+
return {
|
|
141
|
+
...verdict,
|
|
142
|
+
requests,
|
|
143
|
+
why: verdict.decision === "measured" ? `${verdict.why} Second requests: ${listed}.` : verdict.why
|
|
144
|
+
};
|
|
145
|
+
};
|
|
126
146
|
/** This function gives the median of some counts, or null for none. */
|
|
127
147
|
const medianCount = (values) => values.length === 0 ? null : middle([...values].sort((a, b) => a - b));
|
|
128
148
|
/** This function sums what the group of a group cell did in its counted runs, and tests each stage that has a band. */
|
|
@@ -761,8 +781,60 @@ const crossingsOf = (run, hop, toClient, toTarget) => {
|
|
|
761
781
|
* it, and before the first keepalive probe. A live peer answers that probe, and the answer
|
|
762
782
|
* starts the idle time again. A run where the fault was late is void. A run whose records the
|
|
763
783
|
* prober lost is also void.
|
|
784
|
+
* - For an HTTP client, it also reads what the second request met (`requestOf`). An outcome other
|
|
785
|
+
* than the one that the prediction gives is a finding.
|
|
764
786
|
*/
|
|
765
787
|
const measure = (cell, run) => {
|
|
788
|
+
const read = measureSocket(cell, run);
|
|
789
|
+
const expected = cell.prediction.request;
|
|
790
|
+
if (cell.probe.http === void 0 && expected === void 0) return read;
|
|
791
|
+
const request = requestOf(run.events);
|
|
792
|
+
if (expected === void 0 || read.void !== null) return {
|
|
793
|
+
...read,
|
|
794
|
+
request
|
|
795
|
+
};
|
|
796
|
+
const differs = requestFinding(expected, request);
|
|
797
|
+
const finding = read.finding === null ? differs : differs === null ? read.finding : `${read.finding}. ${differs}`;
|
|
798
|
+
return {
|
|
799
|
+
...read,
|
|
800
|
+
request,
|
|
801
|
+
finding
|
|
802
|
+
};
|
|
803
|
+
};
|
|
804
|
+
/** This function tells how a request reading differs from its prediction, or gives null. */
|
|
805
|
+
const requestFinding = (expected, read) => {
|
|
806
|
+
if (read === null) return `the second request never went out, where it should meet: ${requestWords(expected)}`;
|
|
807
|
+
return read.connection === expected.connection && read.retried === expected.retried && read.error === expected.error && read.pending === (expected.pending === true) ? null : `the second request met: ${requestWords(read)}, not ${requestWords(expected)}`;
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* This function reads the second request of an HTTP client from its lines. Its first try is its
|
|
811
|
+
* first `request` line, and each `retry` line is a new try. Its end is its `error` line, or its
|
|
812
|
+
* `replied` line. A request with neither is pending. It gives null when the client wrote no try.
|
|
813
|
+
*
|
|
814
|
+
* A new try that gets no connection writes no `retry` line. Its dial writes a `dial-error` line.
|
|
815
|
+
* Thus a `dial-error` line after the first try and before the end is also a new try.
|
|
816
|
+
*/
|
|
817
|
+
const requestOf = (events) => {
|
|
818
|
+
const of = (event) => Number(event["i"]) === 2;
|
|
819
|
+
const tries = events.filter((event) => (event.ev === "request" || event.ev === "retry") && of(event));
|
|
820
|
+
const first = tries[0];
|
|
821
|
+
if (first === void 0) return null;
|
|
822
|
+
const failed = events.find((event) => event.ev === "error" && of(event));
|
|
823
|
+
const replied = events.some((event) => event.ev === "replied" && of(event));
|
|
824
|
+
const ready = events.find((event) => event.ev === "ready");
|
|
825
|
+
const sent = events.find((event) => event.ev === "sent" && of(event));
|
|
826
|
+
const end = failed ?? events.find((event) => event.ev === "done" && of(event));
|
|
827
|
+
const dialed = events.some((event) => event.ev === "dial-error" && event.t > first.t && (end === void 0 || event.t <= end.t));
|
|
828
|
+
return {
|
|
829
|
+
connection: first["reused"] === true ? "reused" : "new",
|
|
830
|
+
retried: dialed || tries.some((each) => each.ev === "retry"),
|
|
831
|
+
error: failed === void 0 ? null : String(failed["errno"]),
|
|
832
|
+
pending: failed === void 0 && !replied,
|
|
833
|
+
gap: ready === void 0 || sent === void 0 ? null : between(ready.t, sent.t)
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
/** This function reads the socket of one run against its cell, as `measure` does without the second request of an HTTP client. */
|
|
837
|
+
const measureSocket = (cell, run) => {
|
|
766
838
|
if (cell.group !== void 0) {
|
|
767
839
|
const read = measureGroup(cell, run);
|
|
768
840
|
return {
|
|
@@ -784,7 +856,7 @@ const measure = (cell, run) => {
|
|
|
784
856
|
const { events, kernel } = run;
|
|
785
857
|
const ready = events.find((event) => event.ev === "ready");
|
|
786
858
|
const local = events.find((event) => event.ev === "connected")?.["local"];
|
|
787
|
-
const inflight = cell.probe.mode !== "idle";
|
|
859
|
+
const inflight = cell.probe.mode !== "idle" && cell.probe.mode !== "gap";
|
|
788
860
|
const request = inflight ? events.filter((event) => event.ev === "sent")[1] : ready;
|
|
789
861
|
const server = cell.observer === "server";
|
|
790
862
|
const place = socketOf(cell, run);
|
|
@@ -873,14 +945,15 @@ const measure = (cell, run) => {
|
|
|
873
945
|
const reported = socket.find((record) => record.ev === "error" && (record.err ?? 0) !== 0);
|
|
874
946
|
const seen = remote ? void 0 : events.find((event) => event.ev === "error" || event.ev === "eof");
|
|
875
947
|
const closeWait = socket.find((record) => record.ev === "state" && newState(record) === 8);
|
|
948
|
+
const own = cell.prediction.detects === true && cell.prediction.errno === "CLOSED" ? socket.find((record) => record.ev === "state" && oldState(record) === 1 && newState(record) === 4) : void 0;
|
|
876
949
|
const outcome = reported !== void 0 ? {
|
|
877
950
|
kind: "error",
|
|
878
951
|
errno: ERRNO[reported.err] ?? `E${reported.err}`
|
|
879
|
-
} : closeWait !== void 0 || seen?.ev === "eof" ? { kind: "eof" } : seen !== void 0 ? {
|
|
952
|
+
} : closeWait !== void 0 || seen?.ev === "eof" ? { kind: "eof" } : own !== void 0 ? { kind: "closed" } : seen !== void 0 ? {
|
|
880
953
|
kind: "error",
|
|
881
954
|
errno: String(seen["errno"])
|
|
882
955
|
} : { kind: "silent" };
|
|
883
|
-
const ended = reported?.mono ?? closeWait?.mono ?? seen?.t;
|
|
956
|
+
const ended = reported?.mono ?? closeWait?.mono ?? own?.mono ?? seen?.t;
|
|
884
957
|
const noticed = reported !== void 0 && seen !== void 0 ? between(reported.mono, seen.t) : null;
|
|
885
958
|
const { prediction } = cell;
|
|
886
959
|
const before = run.fault?.t1 ?? ready.t;
|
|
@@ -1056,8 +1129,9 @@ const measure = (cell, run) => {
|
|
|
1056
1129
|
finding: `it ended without its reference event (${prediction.from})`
|
|
1057
1130
|
};
|
|
1058
1131
|
const steps = (prediction.from === "last-received" ? keepalives : prediction.from === "first-retransmission" ? retransmitted.filter((record) => record.why === "rto" && record.mono > reference) : []).map((record) => between(reference, record.mono));
|
|
1059
|
-
const finding = (prediction.errno === "EOF" ? outcome.kind === "eof" : outcome.kind === "error" && outcome.errno === prediction.errno) ? null : `it ended with ${outcome.kind === "error" ? outcome.errno : "end of file"}, not ${prediction.errno}`;
|
|
1060
|
-
|
|
1132
|
+
const finding = (prediction.errno === "EOF" ? outcome.kind === "eof" : prediction.errno === "CLOSED" ? outcome.kind === "closed" : outcome.kind === "error" && outcome.errno === prediction.errno) ? null : `it ended with ${outcome.kind === "error" ? outcome.errno : outcome.kind === "closed" ? "its own close" : "end of file"}, not ${prediction.errno}`;
|
|
1133
|
+
const programTimer = cell.probe.http !== void 0 && prediction.from === "last-received" && prediction.timer !== "keepalive";
|
|
1134
|
+
if (prediction.from === "last-received" && !programTimer || prediction.from === "first-retransmission") voided ??= delayed(reference, ended, prediction.from === "last-received" ? "keepalive" : "retransmit");
|
|
1061
1135
|
const lag = (valueIn(prediction.high, "s") - valueIn(prediction.low, "s")) * 1e9;
|
|
1062
1136
|
const slow = prediction.timer.startsWith("expire_") ? (run.middlebox ?? []).find((line) => line.ev === "pump-slow" && line.t <= ended && line.t + Number(line["took"]) * 1e9 >= ended - lag) : void 0;
|
|
1063
1137
|
if (slow !== void 0) voided ??= `a listing of the router took ${Math.round(Number(slow["took"]) * 1e4) / 10} ms, longer than the bound of its prediction`;
|
|
@@ -1114,6 +1188,18 @@ const NODE = {
|
|
|
1114
1188
|
dockerfile: "harness/image/node/Dockerfile",
|
|
1115
1189
|
from: ["harness/image/node"]
|
|
1116
1190
|
};
|
|
1191
|
+
/** This image holds the Go HTTP client: the http.Transport of Go 1.27.2. */
|
|
1192
|
+
const GO_HTTP = {
|
|
1193
|
+
name: "loom-harness-go-http",
|
|
1194
|
+
dockerfile: "harness/image/go-http/Dockerfile",
|
|
1195
|
+
from: ["harness/image/go-http"]
|
|
1196
|
+
};
|
|
1197
|
+
/** This image holds the Node HTTP clients: the http.Agent of Node 24.21.0, and undici 8.12.2. */
|
|
1198
|
+
const NODE_HTTP = {
|
|
1199
|
+
name: "loom-harness-node-http",
|
|
1200
|
+
dockerfile: "harness/image/node-http/Dockerfile",
|
|
1201
|
+
from: ["harness/image/node-http"]
|
|
1202
|
+
};
|
|
1117
1203
|
/** This image is the router of a middlebox chain: NAT and conntrack, with a form for a forgotten flow. */
|
|
1118
1204
|
const ROUTER = {
|
|
1119
1205
|
name: "loom-harness-router",
|
|
@@ -1180,6 +1266,10 @@ const harnessImage = () => build(HARNESS);
|
|
|
1180
1266
|
const nodeClientImage = () => build(NODE);
|
|
1181
1267
|
/** This function gives the image of the router. */
|
|
1182
1268
|
const routerImage = () => build(ROUTER);
|
|
1269
|
+
/** This function gives the image of the Go HTTP client. */
|
|
1270
|
+
const goHttpImage = () => build(GO_HTTP);
|
|
1271
|
+
/** This function gives the image of the Node HTTP clients. */
|
|
1272
|
+
const nodeHttpImage = () => build(NODE_HTTP);
|
|
1183
1273
|
/**
|
|
1184
1274
|
* This time is the interval of the listing of a router, when its hop does not set one. A listing
|
|
1185
1275
|
* evicts each expired flow that it walks past, and the DESTROY event of that flow follows.
|
|
@@ -1322,6 +1412,16 @@ const clientOf = async (kind, image) => {
|
|
|
1322
1412
|
command: ["node", "/opt/harness/node/client-ioredis.mjs"],
|
|
1323
1413
|
clientKind: kind
|
|
1324
1414
|
};
|
|
1415
|
+
if (kind === "go-http") return {
|
|
1416
|
+
clientImage: await goHttpImage(),
|
|
1417
|
+
command: ["/opt/harness/client-go"],
|
|
1418
|
+
clientKind: kind
|
|
1419
|
+
};
|
|
1420
|
+
if (kind === "http-agent" || kind === "undici") return {
|
|
1421
|
+
clientImage: await nodeHttpImage(),
|
|
1422
|
+
command: ["node", `/opt/harness/node-http/client-${kind === "undici" ? "undici" : "agent"}.mjs`],
|
|
1423
|
+
clientKind: kind
|
|
1424
|
+
};
|
|
1325
1425
|
return {
|
|
1326
1426
|
clientImage: image,
|
|
1327
1427
|
command: ["/opt/harness/client"],
|
|
@@ -1362,6 +1462,7 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
1362
1462
|
const runtime = await getContainerRuntimeClient();
|
|
1363
1463
|
const opened = [];
|
|
1364
1464
|
const networks = [];
|
|
1465
|
+
const serverLines = lines();
|
|
1365
1466
|
const close = async () => {
|
|
1366
1467
|
for (const container of opened.reverse()) await container.stop(STOP).catch(() => void 0);
|
|
1367
1468
|
for (const network of networks.reverse()) await network.stop().catch(() => void 0);
|
|
@@ -1381,7 +1482,12 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
1381
1482
|
try {
|
|
1382
1483
|
for (let at = 0; at <= hops.length; at += 1) networks.push(await new Network().start());
|
|
1383
1484
|
const last = networks[hops.length];
|
|
1384
|
-
|
|
1485
|
+
let stock = new GenericContainer(target.image).withNetwork(last).withWaitStrategy(Wait.forLogMessage(target.ready));
|
|
1486
|
+
if (target.files !== void 0) stock = stock.withCopyContentToContainer(Object.entries(target.files).map(([path, content]) => ({
|
|
1487
|
+
content,
|
|
1488
|
+
target: path
|
|
1489
|
+
})));
|
|
1490
|
+
if (target.lines === true) stock = stock.withLogConsumer(serverLines.consume);
|
|
1385
1491
|
const server = await (target.command === void 0 ? stock : stock.withCommand([...target.command])).start();
|
|
1386
1492
|
opened.push(server);
|
|
1387
1493
|
const serverIp = await ipOn(server, last);
|
|
@@ -1633,6 +1739,7 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
1633
1739
|
netns,
|
|
1634
1740
|
topology: placed,
|
|
1635
1741
|
middlebox,
|
|
1742
|
+
server: serverLines.seen,
|
|
1636
1743
|
kernel: records.seen,
|
|
1637
1744
|
async sysctl(values) {
|
|
1638
1745
|
const settings = Object.entries(values).map(([key, value]) => `${key}=${value}`);
|
|
@@ -1898,7 +2005,9 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
1898
2005
|
image: target.image,
|
|
1899
2006
|
id: (await inspect(server)).Image,
|
|
1900
2007
|
settings,
|
|
1901
|
-
sysctls: targetSysctls
|
|
2008
|
+
sysctls: targetSysctls,
|
|
2009
|
+
...target.files === void 0 ? {} : { files: Object.fromEntries(Object.entries(target.files).map(([path, content]) => [path, createHash("sha256").update(content).digest("hex")])) },
|
|
2010
|
+
...serverLines.seen[0] === void 0 ? {} : { first: serverLines.seen[0] }
|
|
1902
2011
|
},
|
|
1903
2012
|
harness: {
|
|
1904
2013
|
image,
|
|
@@ -2621,6 +2730,18 @@ const probeArgs = (cell, lab) => {
|
|
|
2621
2730
|
"--mode",
|
|
2622
2731
|
probe.mode
|
|
2623
2732
|
];
|
|
2733
|
+
if (probe.http !== void 0) {
|
|
2734
|
+
const { http } = probe;
|
|
2735
|
+
const gap = http.gap === void 0 ? 0 : valueIn(http.gap, "s");
|
|
2736
|
+
args.push("--method", http.method);
|
|
2737
|
+
if (http.gap !== void 0) args.push("--gap-ms", String(valueIn(http.gap, "ms")));
|
|
2738
|
+
if (http.hold !== void 0) args.push("--hold-ms", String(valueIn(http.hold, "ms")));
|
|
2739
|
+
if (http.bytes !== void 0) args.push("--bytes", String(http.bytes));
|
|
2740
|
+
for (const [name, value] of Object.entries(http.options ?? {})) args.push(`--${name}`, String(value));
|
|
2741
|
+
args.push("--linger-ms", String(LINGER_MS));
|
|
2742
|
+
args.push("--max-s", String(Math.ceil(longest * 2 + gap + 10)));
|
|
2743
|
+
return args;
|
|
2744
|
+
}
|
|
2624
2745
|
if (probe.request !== void 0) args.push("--request", probe.request);
|
|
2625
2746
|
if (probe.count !== void 0) args.push("--count", String(probe.count));
|
|
2626
2747
|
if (probe.interval !== void 0) args.push("--interval-ms", String(valueIn(probe.interval, "ms")));
|
|
@@ -2651,6 +2772,22 @@ const observerError = (cell, partial, within) => new Promise((resolve) => {
|
|
|
2651
2772
|
});
|
|
2652
2773
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2653
2774
|
/**
|
|
2775
|
+
* This time is the pause between the client's first reply and the fault. It is more than Linux's
|
|
2776
|
+
* TCP_DELACK_MAX of 200 ms. Thus the delayed ACK of the reply is out before the fault.
|
|
2777
|
+
*/
|
|
2778
|
+
const QUIET_MS = 250;
|
|
2779
|
+
/**
|
|
2780
|
+
* This time is how long an HTTP client waits after its second request, before it exits. The exit
|
|
2781
|
+
* closes its sockets, thus that close comes after the window of the cell.
|
|
2782
|
+
*/
|
|
2783
|
+
const LINGER_MS = 1e3;
|
|
2784
|
+
/** These are the lines that end the workload of an HTTP client: the end of its second request, its deadline, or its failure. */
|
|
2785
|
+
const HTTP_DONE = [
|
|
2786
|
+
"done",
|
|
2787
|
+
"deadline",
|
|
2788
|
+
"fail"
|
|
2789
|
+
];
|
|
2790
|
+
/**
|
|
2654
2791
|
* This function waits for the request that an in-flight client sends on the signal: its second
|
|
2655
2792
|
* send, after the PING that made it ready. Its stamp is when the request first met the fault.
|
|
2656
2793
|
*/
|
|
@@ -2686,6 +2823,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2686
2823
|
await lab.forget();
|
|
2687
2824
|
const from = lab.kernel.length;
|
|
2688
2825
|
const middleboxFrom = lab.middlebox.length;
|
|
2826
|
+
const serverFrom = lab.server.length;
|
|
2689
2827
|
const lostBefore = droppedSoFar(lab.kernel);
|
|
2690
2828
|
const probe = await lab.probe(probeArgs(cell, lab));
|
|
2691
2829
|
const server = cell.observer === "server";
|
|
@@ -2748,11 +2886,12 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2748
2886
|
return null;
|
|
2749
2887
|
case "unreachable": return lab.fault("reject", faultAt, { code: cell.icmp ?? "host" });
|
|
2750
2888
|
case "simplex": return lab.fault("blackhole", faultAt, options);
|
|
2751
|
-
case "mtu-blackhole": return lab.fault("mtu", "client", { above: cell.mtu ?? 1200 });
|
|
2889
|
+
case "mtu-blackhole": return lab.fault("mtu", cell.faultAt ?? "client", { above: cell.mtu ?? 1200 });
|
|
2752
2890
|
case "netem": return netemOn(cell.netem);
|
|
2753
2891
|
case "rst": return lab.fault("rst", faultAt);
|
|
2754
2892
|
case "middlebox-forget":
|
|
2755
2893
|
case "proxy-idle":
|
|
2894
|
+
case "app-stall":
|
|
2756
2895
|
case "none": return null;
|
|
2757
2896
|
case "toxic":
|
|
2758
2897
|
if (cell.toxic === void 0) throw new Error(`${cell.name} names no toxic`);
|
|
@@ -2770,10 +2909,11 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2770
2909
|
case "middlebox-forget":
|
|
2771
2910
|
case "proxy-idle":
|
|
2772
2911
|
case "server-close":
|
|
2912
|
+
case "app-stall":
|
|
2773
2913
|
case "none": return;
|
|
2774
2914
|
case "unreachable": return lab.lift("reject", faultAt, { code: cell.icmp ?? "host" });
|
|
2775
2915
|
case "simplex": return lab.lift("blackhole", faultAt, options);
|
|
2776
|
-
case "mtu-blackhole": return lab.lift("mtu", "client", { above: cell.mtu ?? 1200 });
|
|
2916
|
+
case "mtu-blackhole": return lab.lift("mtu", cell.faultAt ?? "client", { above: cell.mtu ?? 1200 });
|
|
2777
2917
|
case "netem":
|
|
2778
2918
|
await netemOn(void 0);
|
|
2779
2919
|
return;
|
|
@@ -2787,9 +2927,14 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2787
2927
|
fault = await apply();
|
|
2788
2928
|
try {
|
|
2789
2929
|
if (cell.idleBefore !== void 0 && cell.probe.mode !== "idle") await sleep(valueIn(cell.idleBefore, "ms"));
|
|
2790
|
-
if (cell.probe.mode
|
|
2930
|
+
if ((cell.probe.mode === "inflight" || cell.probe.mode === "stream") && !replyInFlight) await go();
|
|
2791
2931
|
const { prediction } = cell;
|
|
2792
|
-
|
|
2932
|
+
const http = cell.probe.http !== void 0;
|
|
2933
|
+
if (cell.probe.mode === "gap") {
|
|
2934
|
+
const gap = cell.probe.http?.gap === void 0 ? 0 : valueIn(cell.probe.http.gap, "s");
|
|
2935
|
+
const window = valueIn(prediction.detects ? prediction.high : prediction.window, "s");
|
|
2936
|
+
await probe.until(HTTP_DONE, (gap + window) * 2e3 + 1e4).catch(() => void 0);
|
|
2937
|
+
} else if (cell.fault === "stall") {
|
|
2793
2938
|
if (cell.stall === void 0) throw new Error(`${cell.name} says no stall's length`);
|
|
2794
2939
|
const sent = await requestSent(probe, 5e3);
|
|
2795
2940
|
heal = await lab.heal(faultAt, sent.t + valueIn(cell.stall, "s") * 1e9);
|
|
@@ -2810,10 +2955,12 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2810
2955
|
} else if (prediction.detects) {
|
|
2811
2956
|
const within = valueIn(prediction.high, "s") * 2e3 + 1e4;
|
|
2812
2957
|
if (cell.observer !== void 0 && cell.observer !== "client") await observerError(cell, partial, within);
|
|
2958
|
+
else if (http && cell.probe.mode !== "idle") await probe.until(HTTP_DONE, within).catch(() => void 0);
|
|
2813
2959
|
else await probe.until([
|
|
2814
2960
|
"error",
|
|
2815
2961
|
"eof",
|
|
2816
|
-
"deadline"
|
|
2962
|
+
"deadline",
|
|
2963
|
+
...http ? ["closed"] : []
|
|
2817
2964
|
], within).catch(() => void 0);
|
|
2818
2965
|
} else await sleep(valueIn(prediction.window, "s") * 1e3);
|
|
2819
2966
|
} finally {
|
|
@@ -2838,7 +2985,8 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
2838
2985
|
targetNetns: lab.netns.target,
|
|
2839
2986
|
lost,
|
|
2840
2987
|
...placed,
|
|
2841
|
-
...lab.topology === null ? {} : { middlebox: lab.middlebox.slice(middleboxFrom) }
|
|
2988
|
+
...lab.topology === null ? {} : { middlebox: lab.middlebox.slice(middleboxFrom) },
|
|
2989
|
+
...cell.target.lines === true ? { server: lab.server.slice(serverFrom) } : {}
|
|
2842
2990
|
};
|
|
2843
2991
|
};
|
|
2844
2992
|
/** This function divides a count of runs across environments, as equally as possible. */
|
|
@@ -2967,6 +3115,6 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
2967
3115
|
}
|
|
2968
3116
|
};
|
|
2969
3117
|
//#endregion
|
|
2970
|
-
export { CONVERGE_MS, LISTING, NOISE, PUMP, RELATIVE, STAGES, STEPS, crossingsOf, deadlineOf, deviceOf, endpointAfter, errorsOf, fieldsOf, groupOnce, groupVerdictOf, harnessImage, infoOf, isHopPlace, judge, lagsOf, measure, measureGroup, medianInterval, memberConfig, nodeClientImage, openGroupLab, openLab, peersOf, quantileOf, reorderingOf, routerImage, routesOf, runCell, sentinelConfig, socketOf, spread, spreadOf, stagesOf, subscriptionsOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
3118
|
+
export { CONVERGE_MS, LINGER_MS, LISTING, NOISE, PUMP, QUIET_MS, RELATIVE, STAGES, STEPS, crossingsOf, deadlineOf, deviceOf, endpointAfter, errorsOf, fieldsOf, goHttpImage, groupOnce, groupVerdictOf, harnessImage, infoOf, isHopPlace, judge, lagsOf, measure, measureGroup, medianInterval, memberConfig, nodeClientImage, nodeHttpImage, openGroupLab, openLab, peersOf, quantileOf, reorderingOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketOf, spread, spreadOf, stagesOf, subscriptionsOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
2971
3119
|
|
|
2972
3120
|
//# sourceMappingURL=index.js.map
|