@mark1russell7/harness 0.8.0 → 0.10.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 +6 -1
- package/build/index.d.ts +64 -7
- package/build/index.js +120 -43
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ A **cell** (`cell.ts`) is all that one measurement needs, as data:
|
|
|
25
25
|
- A privileged sidecar in the namespace of the client, for its sysctls and its routes. Docker Desktop keeps `/proc/sys` read-only in a container that operates.
|
|
26
26
|
- Each hop of the topology, on the network of each of its sides.
|
|
27
27
|
- An injector sidecar in the namespace of the target, with NET_ADMIN. Thus the image of the target stays stock.
|
|
28
|
+
- The manifest of each environment gives the HZ of its kernel, from /proc/config.gz. A host without that file (a hosted runner) gets HZ from two readings of /proc/timer_list, one second apart: its jiffies against the time of the kernel (`hzOf`). `hzFrom` tells which.
|
|
28
29
|
- The prober ([`@mark1russell7/prober`](https://www.npmjs.com/package/@mark1russell7/prober)): a privileged container that loads the kernel program. It watches the namespace of the client, of the target and of each hop. Each run keeps its records from its start to its end. A run whose records the prober lost is void.
|
|
29
30
|
|
|
30
31
|
Each command is an argv array.
|
|
@@ -186,6 +187,7 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
186
187
|
- **The time.** It times the detection from the reference event of the prediction to the error report of the kernel. eBPF stamps the two events. For keepalive, the reference is the last received segment. For the give-up, it is the first retransmission of the RTO timer, not the tail loss probe one RTO before it. For a reset, it is the send of the client, which no kernel record marks. The client stamps the send before its write.
|
|
187
188
|
- **The other views.** It also gives the same interval from the `TCP_INFO` samples of the client, and the delay before the client saw the error. It also gives the steps of the kernel: each keepalive probe, or each expiry of the RTO timer.
|
|
188
189
|
- **Void runs.** A run is void when the timer that drives the measurement was two jiffies or more behind its wheel slot in the timed interval. The wheel does a slot in the tick when it is due. Thus such a firing measured the host, which stopped a vCPU, not the kernel. A keepalive timer that found its socket held also makes the run void. A run whose fault came too late is void too.
|
|
190
|
+
- The rule is in time when the run knows its HZ (`Run.hz`, from the manifest). Then the delay must reach two jiffies and δ_abs, the larger of the two (`delayVoids`). Each band includes δ_abs, thus a smaller delay does not change a result. At HZ 250 two jiffies are 8 ms, more than a δ_abs of 4 ms, and the rule stays the same. At HZ 1000 two jiffies are 2 ms. A run without its HZ keeps the rule in jiffies.
|
|
189
191
|
- **The first round trip.** The measurement gives the first round trip of the socket of the observer (`rtt`). It is the smoothed round trip in the first record of the socket: the sample of the handshake. A run whose first round trip is above the range of its prediction is void, with the reason `rtt-over-assumption` and the value. Such a run measured another path than the band assumes. For example, a round trip of 4.826 ms makes an RTO of 52 jiffies, and a ladder that assumes 1 ms has 51.
|
|
190
192
|
- **Findings.** A finding is a wrong errno, a detector that did not fire, or a detection where the prediction is silence. A router that forgot the flow in the window of a silent prediction is also a finding.
|
|
191
193
|
- **A reply.** A silent prediction with `replies` times the send of the request to the first segment with data that comes back. A proxy can acknowledge the request first, with no data.
|
|
@@ -206,6 +208,7 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
206
208
|
- Each wait goes from a `reconnecting` line, where the program starts its timer, to its next `dial` line, where it makes the socket. A wait outside its band, widened by δ_abs, is a finding. A try that did not come is a finding too.
|
|
207
209
|
- The kernel gives the time from the detection to the first SYN of a new socket (`syn`). It also gives the time to the first new socket in ESTABLISHED (`established`).
|
|
208
210
|
- It also gives the time from each `dial` line to its SYN (`connects`): the work of the program before connect(2).
|
|
211
|
+
- A measurement-only prediction can give `reconnects` too. The end of its connection has no band, but each wait has one. A wait outside its band is a finding of its run, and the verdict names that run. The decision stays `measured`.
|
|
209
212
|
- **A call in flight of a WebSocket client** (`callOf`). The run gives its error or its reply, and the time from the detection to its end.
|
|
210
213
|
- **A stream.** In a stream (`probe.mode` is `stream`), the client sends `count` requests. The run keeps the time from each send to the last byte of its reply (`samples`). A record without `replied` lines gives the time to the first data.
|
|
211
214
|
- **Reordering.** A measurement-only run also gives what reordering, duplicates and losses did (`reordering`). At the client, it counts the arrivals beyond a hole and the arrivals of data that it had already, against `rcv_nxt`. From the sender, it gives the most SACKed and lost segments, the DSACKs, the fast retransmissions, and the reordering degree and count of the kernel.
|
|
@@ -241,7 +244,9 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
241
244
|
|
|
242
245
|
`verdictOf(prediction, runs)` (`verdict.ts`) judges a cell:
|
|
243
246
|
|
|
244
|
-
- **Containment.** Each run lands in [T_lo − δ_abs, T_hi + δ_abs]. `NOISE` (δ_abs) is 4 ms, from the calibration: the worst reset took 1.141 ms, and δ_abs is one jiffy at the minimum.
|
|
247
|
+
- **Containment.** Each run lands in [T_lo − δ_abs, T_hi + δ_abs]. `NOISE` (δ_abs) is 4 ms, from the calibration of the first environment: the worst reset took 1.141 ms, and δ_abs is one jiffy at the minimum.
|
|
248
|
+
- **δ_abs of an environment.** A cell can give the δ_abs of its environment (`Cell.noise`). Stage 0 measures it in each environment. On a hosted runner of GitHub at HZ 1000, the worst reset took 0.147 ms, thus δ_abs is one jiffy, 1 ms. These read it (`noiseOf`): the bands, the pairing of a program line with its kernel record, and the drop of a program.
|
|
249
|
+
- **The injector has its own bound.** A fault that the injector commits or lifts late makes a run void. That bound is `INJECT` (4 ms), not δ_abs. Stage 0 measures the commit apart. On a hosted runner it took up to 2.75 ms at 4 environments.
|
|
245
250
|
- **Median equivalence.** The order-statistic interval of the median must be in the band, made 5 % wider.
|
|
246
251
|
- **Extension.** A median across the edge of the band extends the cell from 11 runs to 22, then 44. Then the verdict is inconclusive.
|
|
247
252
|
- **Recovery.** The recovery after a stall gets the same test as a detection, by its band. A detection is then a finding.
|
package/build/index.d.ts
CHANGED
|
@@ -426,7 +426,8 @@ interface Reconnects {
|
|
|
426
426
|
* A cell whose outcome is a distribution, for example under netem loss, has no band. Its
|
|
427
427
|
* prediction is `detects: null` with the reason, and its runs are measurements only. A
|
|
428
428
|
* measurement-only cell that names a reference (`from`) also records the time from it to the end
|
|
429
|
-
* of the connection, or to the last data.
|
|
429
|
+
* of the connection, or to the last data. It can also give the waits of the reconnect after that
|
|
430
|
+
* end (`reconnects`).
|
|
430
431
|
*
|
|
431
432
|
* For a group cell, the band is the first write that a primary accepts after the fault, and its
|
|
432
433
|
* errno is `OK`. `group` tells which primary, and the stages of the group on the way.
|
|
@@ -483,8 +484,19 @@ type Prediction = {
|
|
|
483
484
|
readonly window: Quantity;
|
|
484
485
|
readonly why: string;
|
|
485
486
|
readonly from?: Reference;
|
|
487
|
+
/**
|
|
488
|
+
* This choice ends the measured time at the close of the socket by the observer itself, the
|
|
489
|
+
* change from ESTABLISHED to FIN_WAIT1. Without it, the time ends at a report of the kernel or
|
|
490
|
+
* at the last data. Then an idle pool that closes its own socket gives no end.
|
|
491
|
+
*/
|
|
492
|
+
readonly ends?: "own-close";
|
|
486
493
|
readonly group?: GroupPrediction;
|
|
487
494
|
readonly request?: RequestPrediction;
|
|
495
|
+
/**
|
|
496
|
+
* These are the waits of the reconnect after the end of the connection, from the document of
|
|
497
|
+
* the client. The end has no band, but each wait has one. A wait outside its band is a finding of its run.
|
|
498
|
+
*/
|
|
499
|
+
readonly reconnects?: Reconnects;
|
|
488
500
|
};
|
|
489
501
|
/** A target is the server that a cell connects to: a stock image. */
|
|
490
502
|
interface Target {
|
|
@@ -757,6 +769,12 @@ interface Cell {
|
|
|
757
769
|
* many environments the machine holds before its noise increases.
|
|
758
770
|
*/
|
|
759
771
|
readonly concurrency?: number;
|
|
772
|
+
/**
|
|
773
|
+
* This time is δ_abs of the environment of the cell: the noise of the pipeline. Each band includes
|
|
774
|
+
* it, and a timer must be at least that late to make a run void. Stage 0 measures it in each
|
|
775
|
+
* environment. Without it, the cell gets `NOISE`, the δ_abs of the first environment (4 ms).
|
|
776
|
+
*/
|
|
777
|
+
readonly noise?: Quantity;
|
|
760
778
|
}
|
|
761
779
|
/** A hop place is a hop of a topology, with the side that the rules of a fault face. */
|
|
762
780
|
interface HopPlace {
|
|
@@ -817,6 +835,11 @@ interface Run {
|
|
|
817
835
|
* the run void. Null also makes the run void: no clock line came after the run to tell the count.
|
|
818
836
|
*/
|
|
819
837
|
readonly lost: number | null;
|
|
838
|
+
/**
|
|
839
|
+
* This rate is the HZ of the kernel of the environment, from its manifest. With it, the rule of a
|
|
840
|
+
* late timer is in time. A run without it keeps the rule in jiffies, as the runs before 0.9.0.
|
|
841
|
+
*/
|
|
842
|
+
readonly hz?: number;
|
|
820
843
|
/** These are the addresses and the namespaces of a topology, which the measurement of a run reads. */
|
|
821
844
|
readonly topology?: RunTopology;
|
|
822
845
|
/** These are the lines of the routers of a topology from the start to the end of the run. */
|
|
@@ -905,9 +928,9 @@ export declare const leftAfter: (socket: readonly KernelRecord[], line: ProbeEve
|
|
|
905
928
|
* A close closes the socket in δ_abs. A close frame is a write, and the program does not close
|
|
906
929
|
* the socket at the drop. A peer that answers the close frame can close it, and that is no finding.
|
|
907
930
|
*/
|
|
908
|
-
export declare const dropFinding: (expected: "frame" | "close", drop: ProgramEvent, left: Quantity | null) => string | null;
|
|
931
|
+
export declare const dropFinding: (expected: "frame" | "close", drop: ProgramEvent, left: Quantity | null, delta?: Quantity) => string | null;
|
|
909
932
|
/** This function gives each ping of the program on the connection of the run, with the kernel's stamp of its write. */
|
|
910
|
-
export declare const pingsOf: (lines: readonly ProbeEvent[], socket: readonly KernelRecord[], local: unknown) => ProgramEvent[];
|
|
933
|
+
export declare const pingsOf: (lines: readonly ProbeEvent[], socket: readonly KernelRecord[], local: unknown, delta?: Quantity) => ProgramEvent[];
|
|
911
934
|
/**
|
|
912
935
|
* This function gives the first ping that got no answer: no segment with data reached the socket
|
|
913
936
|
* after its write, up to `until`. Its write starts the wait of the heartbeat that the fault met.
|
|
@@ -918,7 +941,7 @@ export declare const firstUnanswered: (pings: readonly ProgramEvent[], socket: r
|
|
|
918
941
|
* write of a close frame. Or it stamps the change of the socket out of ESTABLISHED: a close or an
|
|
919
942
|
* abort.
|
|
920
943
|
*/
|
|
921
|
-
export declare const dropOf: (lines: readonly ProbeEvent[], socket: readonly KernelRecord[], local: unknown) => ProgramEvent | undefined;
|
|
944
|
+
export declare const dropOf: (lines: readonly ProbeEvent[], socket: readonly KernelRecord[], local: unknown, delta?: Quantity) => ProgramEvent | undefined;
|
|
922
945
|
/** A reconnect reading is what the client did after the detection, from its lines and from the records of the kernel. */
|
|
923
946
|
interface ReconnectReading {
|
|
924
947
|
/** These are the waits of the program before each try: from a `reconnecting` line to the next `dial` line. */
|
|
@@ -937,7 +960,7 @@ interface ReconnectReading {
|
|
|
937
960
|
*/
|
|
938
961
|
export declare const reconnectOf: (run: Run, detected: number, cookie: number | undefined, from?: number) => ReconnectReading;
|
|
939
962
|
/** This function tells how the waits of a reconnect differ from their bands, each widened by δ_abs, or gives null. */
|
|
940
|
-
export declare const reconnectFinding: (expected: Reconnects, read: ReconnectReading) => string | null;
|
|
963
|
+
export declare const reconnectFinding: (expected: Reconnects, read: ReconnectReading, delta?: Quantity) => string | null;
|
|
941
964
|
/**
|
|
942
965
|
* A call reading is what the call in flight of a WebSocket client met: its error, a reply, or
|
|
943
966
|
* nothing in the run. The time counts from the detection to its end.
|
|
@@ -1321,6 +1344,23 @@ export declare const requestWords: (outcome: {
|
|
|
1321
1344
|
* the harness, the kernel or Docker.
|
|
1322
1345
|
*/
|
|
1323
1346
|
export declare const NOISE: Quantity;
|
|
1347
|
+
/**
|
|
1348
|
+
* This time is how late the injector can commit or lift a fault before the run is void. Stage 0
|
|
1349
|
+
* measures the commit apart from δ_abs, and the commit is longer. On a hosted runner it took up to
|
|
1350
|
+
* 2.75 ms at 4 environments, where δ_abs is 1 ms. Thus a cell's δ_abs does not change it.
|
|
1351
|
+
*/
|
|
1352
|
+
export declare const INJECT: Quantity;
|
|
1353
|
+
/** This function gives the δ_abs of a cell: its own, or `NOISE` for a cell that gives none. */
|
|
1354
|
+
export declare const noiseOf: (cell: {
|
|
1355
|
+
readonly noise?: Quantity | undefined;
|
|
1356
|
+
}) => Quantity;
|
|
1357
|
+
/**
|
|
1358
|
+
* This function tells if a timer firing came late enough to void a run. A firing two jiffies or
|
|
1359
|
+
* more behind its wheel slot came late because the host stopped a vCPU. With the HZ of the run,
|
|
1360
|
+
* the rule is in time: the delay must also reach δ_abs, because each band already includes that
|
|
1361
|
+
* noise. At HZ 250 two jiffies are 8 ms, more than a δ_abs of 4 ms. At HZ 1000 they are 2 ms.
|
|
1362
|
+
*/
|
|
1363
|
+
export declare const delayVoids: (behind: number, hz: number | undefined, noise: Quantity) => boolean;
|
|
1324
1364
|
/** This fraction, δ_rel, is how far the median of a cell can be outside the prediction and stay equivalent. */
|
|
1325
1365
|
export declare const RELATIVE = .05;
|
|
1326
1366
|
/** These are the run counts of a cell: 11, then 22 and 44 while its median crosses the edge of the band. */
|
|
@@ -1472,9 +1512,9 @@ export declare const medianInterval: (sorted: readonly number[]) => [number, num
|
|
|
1472
1512
|
* finding. The distribution test (a DKW band) needs a predicted distribution from a simulator,
|
|
1473
1513
|
* which does not exist at this time.
|
|
1474
1514
|
*/
|
|
1475
|
-
export declare const verdictOf: (prediction: Prediction, runs: readonly Measured[]) => Verdict;
|
|
1515
|
+
export declare const verdictOf: (prediction: Prediction, runs: readonly Measured[], noise?: Quantity) => Verdict;
|
|
1476
1516
|
/** This function sums what the group of a group cell did in its counted runs, and tests each stage that has a band. */
|
|
1477
|
-
export declare const groupVerdictOf: (prediction: Prediction, counted: readonly Measured[]) => GroupVerdict;
|
|
1517
|
+
export declare const groupVerdictOf: (prediction: Prediction, counted: readonly Measured[], delta?: Quantity) => GroupVerdict;
|
|
1478
1518
|
//#endregion
|
|
1479
1519
|
//#region src/lab.d.ts
|
|
1480
1520
|
/** This function gives the image of the harness. */
|
|
@@ -1545,6 +1585,11 @@ interface Manifest {
|
|
|
1545
1585
|
/** This text is the architecture of the machine, as uname -m gives it: x86_64. */
|
|
1546
1586
|
readonly architecture: string;
|
|
1547
1587
|
readonly hz: string | null;
|
|
1588
|
+
/**
|
|
1589
|
+
* This text tells where the HZ came from: the configuration of the kernel, or a measurement. A
|
|
1590
|
+
* host without /proc/config.gz (a hosted runner) gets a measurement of jiffies against the clock.
|
|
1591
|
+
*/
|
|
1592
|
+
readonly hzFrom?: "config" | "measured";
|
|
1548
1593
|
readonly boot_id: string;
|
|
1549
1594
|
readonly docker: string;
|
|
1550
1595
|
/** This record is the target, and its settings as it read them back. */
|
|
@@ -1768,6 +1813,18 @@ export declare const routesOf: (hops: readonly Pick<Hop, "kind">[]) => Array<{
|
|
|
1768
1813
|
* is not a router. Element 0 is the client, element k is hop k - 1, and the last element is the target.
|
|
1769
1814
|
*/
|
|
1770
1815
|
export declare const endpointAfter: (hops: readonly Pick<Hop, "kind">[], element: number) => number;
|
|
1816
|
+
/**
|
|
1817
|
+
* This function gives HZ from two readings of /proc/timer_list, one second apart: its `now at`
|
|
1818
|
+
* time in ns and its jiffies. It gives the nearest rate that Linux offers, or null when the
|
|
1819
|
+
* measurement is more than 5 % from each of them.
|
|
1820
|
+
*/
|
|
1821
|
+
export declare const hzOf: (first: {
|
|
1822
|
+
now: number;
|
|
1823
|
+
jiffies: number;
|
|
1824
|
+
}, second: {
|
|
1825
|
+
now: number;
|
|
1826
|
+
jiffies: number;
|
|
1827
|
+
}) => string | null;
|
|
1771
1828
|
/** This function gives the device of a route from its line in `ip -o route get`: "10.0.0.2 dev eth0 src …". */
|
|
1772
1829
|
export declare const deviceOf: (route: string) => string;
|
|
1773
1830
|
/**
|
package/build/index.js
CHANGED
|
@@ -20,6 +20,25 @@ const requestWords = (outcome) => `${outcome.connection}${outcome.retried ? ", a
|
|
|
20
20
|
* the harness, the kernel or Docker.
|
|
21
21
|
*/
|
|
22
22
|
const NOISE = quantity(4, "ms");
|
|
23
|
+
/**
|
|
24
|
+
* This time is how late the injector can commit or lift a fault before the run is void. Stage 0
|
|
25
|
+
* measures the commit apart from δ_abs, and the commit is longer. On a hosted runner it took up to
|
|
26
|
+
* 2.75 ms at 4 environments, where δ_abs is 1 ms. Thus a cell's δ_abs does not change it.
|
|
27
|
+
*/
|
|
28
|
+
const INJECT = NOISE;
|
|
29
|
+
/** This function gives the δ_abs of a cell: its own, or `NOISE` for a cell that gives none. */
|
|
30
|
+
const noiseOf = (cell) => cell.noise ?? NOISE;
|
|
31
|
+
/**
|
|
32
|
+
* This function tells if a timer firing came late enough to void a run. A firing two jiffies or
|
|
33
|
+
* more behind its wheel slot came late because the host stopped a vCPU. With the HZ of the run,
|
|
34
|
+
* the rule is in time: the delay must also reach δ_abs, because each band already includes that
|
|
35
|
+
* noise. At HZ 250 two jiffies are 8 ms, more than a δ_abs of 4 ms. At HZ 1000 they are 2 ms.
|
|
36
|
+
*/
|
|
37
|
+
const delayVoids = (behind, hz, noise) => {
|
|
38
|
+
if (behind < 2) return false;
|
|
39
|
+
if (hz === void 0 || !(hz > 0)) return true;
|
|
40
|
+
return behind / hz >= Math.max(2 / hz, valueIn(noise, "s"));
|
|
41
|
+
};
|
|
23
42
|
/** This fraction, δ_rel, is how far the median of a cell can be outside the prediction and stay equivalent. */
|
|
24
43
|
const RELATIVE = .05;
|
|
25
44
|
/** These are the run counts of a cell: 11, then 22 and 44 while its median crosses the edge of the band. */
|
|
@@ -116,15 +135,15 @@ const middle = (sorted) => {
|
|
|
116
135
|
* finding. The distribution test (a DKW band) needs a predicted distribution from a simulator,
|
|
117
136
|
* which does not exist at this time.
|
|
118
137
|
*/
|
|
119
|
-
const verdictOf = (prediction, runs) => {
|
|
120
|
-
const band = verdictOfBand(prediction, runs);
|
|
138
|
+
const verdictOf = (prediction, runs, noise = NOISE) => {
|
|
139
|
+
const band = verdictOfBand(prediction, runs, noise);
|
|
121
140
|
const counted = runs.filter((run) => run.void === null);
|
|
122
141
|
const asked = counted.some((run) => run.request !== void 0) ? withRequests(band, counted) : band;
|
|
123
142
|
const verdict = withProgram(asked, counted);
|
|
124
143
|
if (prediction.group === void 0 && !counted.some((run) => run.group !== void 0)) return verdict;
|
|
125
144
|
return {
|
|
126
145
|
...verdict,
|
|
127
|
-
group: groupVerdictOf(prediction, counted)
|
|
146
|
+
group: groupVerdictOf(prediction, counted, noise)
|
|
128
147
|
};
|
|
129
148
|
};
|
|
130
149
|
/**
|
|
@@ -184,8 +203,8 @@ const withRequests = (verdict, counted) => {
|
|
|
184
203
|
/** This function gives the median of some counts, or null for none. */
|
|
185
204
|
const medianCount = (values) => values.length === 0 ? null : middle([...values].sort((a, b) => a - b));
|
|
186
205
|
/** This function sums what the group of a group cell did in its counted runs, and tests each stage that has a band. */
|
|
187
|
-
const groupVerdictOf = (prediction, counted) => {
|
|
188
|
-
const noise = valueIn(
|
|
206
|
+
const groupVerdictOf = (prediction, counted, delta = NOISE) => {
|
|
207
|
+
const noise = valueIn(delta, "s");
|
|
189
208
|
const stamps = prediction.group?.stamps === void 0 ? 0 : valueIn(prediction.group.stamps, "s");
|
|
190
209
|
const stages = (prediction.group?.stages ?? []).map((band) => {
|
|
191
210
|
const interval = {
|
|
@@ -261,7 +280,7 @@ const groupVerdictOf = (prediction, counted) => {
|
|
|
261
280
|
};
|
|
262
281
|
};
|
|
263
282
|
/** This function gives the verdict of the band of a cell: its containment and the equivalence of its median. */
|
|
264
|
-
const verdictOfBand = (prediction, runs) => {
|
|
283
|
+
const verdictOfBand = (prediction, runs, delta = NOISE) => {
|
|
265
284
|
const counted = runs.filter((run) => run.void === null);
|
|
266
285
|
const voided = runs.length - counted.length;
|
|
267
286
|
const outside = counted.filter((run) => run.finding !== null).map((run) => ({
|
|
@@ -310,13 +329,15 @@ const verdictOfBand = (prediction, runs) => {
|
|
|
310
329
|
}
|
|
311
330
|
const outcomes = [...kinds].map(([kind, n]) => `${n} ${kind}`).join(", ");
|
|
312
331
|
const timed = times.length === 0 ? "no time" : `median ${s(middle(times)).value} s from ${prediction.from}, range [${s(times[0]).value}, ${s(times.at(-1)).value}] s`;
|
|
332
|
+
const missed = outside.filter((each) => counted.some((run) => name(run) === each.run && run.reconnect !== void 0));
|
|
333
|
+
const late = missed.length === 0 ? "" : ` ${missed.length} runs had a wait of the reconnect outside its band.`;
|
|
313
334
|
return {
|
|
314
335
|
...empty,
|
|
315
336
|
decision: "measured",
|
|
316
337
|
band: null,
|
|
317
|
-
outside:
|
|
338
|
+
outside: missed,
|
|
318
339
|
median: times.length === 0 ? null : s(middle(times)),
|
|
319
|
-
why: `${counted.length} runs measured: ${outcomes}; ${timed}
|
|
340
|
+
why: `${counted.length} runs measured: ${outcomes}; ${timed}.${late} ${prediction.why}`
|
|
320
341
|
};
|
|
321
342
|
}
|
|
322
343
|
const samples = counted.flatMap((run) => (run.samples ?? []).map((sample) => valueIn(sample, "s"))).sort((a, b) => a - b);
|
|
@@ -355,7 +376,7 @@ const verdictOfBand = (prediction, runs) => {
|
|
|
355
376
|
};
|
|
356
377
|
}
|
|
357
378
|
const timed = prediction.detects ? prediction : prediction.recovers ?? prediction.replies;
|
|
358
|
-
const noise = valueIn(
|
|
379
|
+
const noise = valueIn(delta, "s");
|
|
359
380
|
const low = valueIn(timed.low, "s");
|
|
360
381
|
const high = valueIn(timed.high, "s");
|
|
361
382
|
const band = {
|
|
@@ -456,12 +477,12 @@ const onConnection = (line, local) => {
|
|
|
456
477
|
return (line["local"] ?? line["peer"]) === local;
|
|
457
478
|
};
|
|
458
479
|
/** This function gives the first record of a socket that matches, at the stamp of a line or in δ_abs after it. */
|
|
459
|
-
const kernelAt = (socket, line, matches) => {
|
|
460
|
-
const noise = valueIn(
|
|
480
|
+
const kernelAt = (socket, line, matches, delta) => {
|
|
481
|
+
const noise = valueIn(delta, "s") * 1e9;
|
|
461
482
|
return socket.find((record) => record.mono >= line.t && record.mono <= line.t + noise && matches(record));
|
|
462
483
|
};
|
|
463
|
-
const eventOf = (socket, line, matches) => {
|
|
464
|
-
const record = kernelAt(socket, line, matches);
|
|
484
|
+
const eventOf = (socket, line, matches, delta = NOISE) => {
|
|
485
|
+
const record = kernelAt(socket, line, matches, delta);
|
|
465
486
|
return {
|
|
466
487
|
line,
|
|
467
488
|
at: record?.mono ?? line.t,
|
|
@@ -483,8 +504,8 @@ const leftAfter = (socket, line) => {
|
|
|
483
504
|
* A close closes the socket in δ_abs. A close frame is a write, and the program does not close
|
|
484
505
|
* the socket at the drop. A peer that answers the close frame can close it, and that is no finding.
|
|
485
506
|
*/
|
|
486
|
-
const dropFinding = (expected, drop, left) => {
|
|
487
|
-
const noise = valueIn(
|
|
507
|
+
const dropFinding = (expected, drop, left, delta = NOISE) => {
|
|
508
|
+
const noise = valueIn(delta, "s");
|
|
488
509
|
const soon = left !== null && valueIn(left, "s") <= noise;
|
|
489
510
|
if (expected === "close") {
|
|
490
511
|
if (left === null) return "the program did not close its socket after the drop, where a close is at once";
|
|
@@ -495,7 +516,7 @@ const dropFinding = (expected, drop, left) => {
|
|
|
495
516
|
};
|
|
496
517
|
const isWrite = (record) => record.ev === "tx";
|
|
497
518
|
/** This function gives each ping of the program on the connection of the run, with the kernel's stamp of its write. */
|
|
498
|
-
const pingsOf = (lines, socket, local) => lines.filter((line) => line.ev === "ping" && onConnection(line, local)).map((line) => eventOf(socket, line, isWrite));
|
|
519
|
+
const pingsOf = (lines, socket, local, delta = NOISE) => lines.filter((line) => line.ev === "ping" && onConnection(line, local)).map((line) => eventOf(socket, line, isWrite, delta));
|
|
499
520
|
/**
|
|
500
521
|
* This function gives the first ping that got no answer: no segment with data reached the socket
|
|
501
522
|
* after its write, up to `until`. Its write starts the wait of the heartbeat that the fault met.
|
|
@@ -506,9 +527,9 @@ const firstUnanswered = (pings, socket, until = Number.POSITIVE_INFINITY) => pin
|
|
|
506
527
|
* write of a close frame. Or it stamps the change of the socket out of ESTABLISHED: a close or an
|
|
507
528
|
* abort.
|
|
508
529
|
*/
|
|
509
|
-
const dropOf = (lines, socket, local) => {
|
|
530
|
+
const dropOf = (lines, socket, local, delta = NOISE) => {
|
|
510
531
|
const line = lines.find((each) => each.ev === "drop" && onConnection(each, local));
|
|
511
|
-
return line === void 0 ? void 0 : eventOf(socket, line, (record) => isWrite(record) || record.ev === "state" && oldState$2(record) === 1);
|
|
532
|
+
return line === void 0 ? void 0 : eventOf(socket, line, (record) => isWrite(record) || record.ev === "state" && oldState$2(record) === 1, delta);
|
|
512
533
|
};
|
|
513
534
|
/**
|
|
514
535
|
* This function reads the reconnect of the client after the detection at `detected`. The socket of
|
|
@@ -539,8 +560,8 @@ const reconnectOf = (run, detected, cookie, from = detected) => {
|
|
|
539
560
|
};
|
|
540
561
|
};
|
|
541
562
|
/** This function tells how the waits of a reconnect differ from their bands, each widened by δ_abs, or gives null. */
|
|
542
|
-
const reconnectFinding = (expected, read) => {
|
|
543
|
-
const noise = valueIn(
|
|
563
|
+
const reconnectFinding = (expected, read, delta = NOISE) => {
|
|
564
|
+
const noise = valueIn(delta, "s");
|
|
544
565
|
const round = (value) => Math.round(value * 1e6) / 1e6;
|
|
545
566
|
const found = expected.waits.flatMap((band, at) => {
|
|
546
567
|
const wait = read.waits[at];
|
|
@@ -729,9 +750,9 @@ const settled = (cell, run) => {
|
|
|
729
750
|
* that found the socket held by its owner put itself off by HZ/20. Either way, the run measured
|
|
730
751
|
* something other than the timer.
|
|
731
752
|
*/
|
|
732
|
-
const delayed = (place, from, to) => {
|
|
753
|
+
const delayed = (place, from, to, hz, noise) => {
|
|
733
754
|
const firings = place.records.filter((record) => record.ev === "timer" && record.what === "keepalive" && record.mono >= from && record.mono <= to);
|
|
734
|
-
const late = firings.find((record) => (record.behind?.value ?? 0
|
|
755
|
+
const late = firings.find((record) => delayVoids(record.behind?.value ?? 0, hz, noise));
|
|
735
756
|
if (late !== void 0) return `its keepalive timer ran ${late.behind.value} jiffies behind its wheel slot: the VM delayed it, not the kernel`;
|
|
736
757
|
return firings.find((record) => record.owned === true) === void 0 ? null : "its keepalive timer found the socket held by its owner and put itself off by HZ/20: the run measured the owner, not the timer";
|
|
737
758
|
};
|
|
@@ -805,13 +826,13 @@ const measureDriver = (cell, run) => {
|
|
|
805
826
|
if (heal === null) voided = "the fault never lifted";
|
|
806
827
|
else {
|
|
807
828
|
const late = (heal.t0 - run.fault.t0) / 1e9 - valueIn(cell.hold, "s");
|
|
808
|
-
if (late > valueIn(
|
|
829
|
+
if (late > valueIn(INJECT, "s")) voided = `the lift came ${Math.round(late * 1e4) / 10} ms after its hold: the injector was late, not the client`;
|
|
809
830
|
}
|
|
810
831
|
}
|
|
811
832
|
const assumed = prediction.rtt;
|
|
812
833
|
if (voided === null && assumed !== void 0 && rtt !== null && valueIn(rtt, "ms") > valueIn(assumed.high, "ms")) voided = `rtt-over-assumption: its first round trip was ${rtt.value} ms, above the ${valueIn(assumed.high, "ms")} ms that the prediction assumes`;
|
|
813
834
|
const findings = [];
|
|
814
|
-
const noise = valueIn(
|
|
835
|
+
const noise = valueIn(noiseOf(cell), "s");
|
|
815
836
|
const until = prediction.detects ? Infinity : after + valueIn(prediction.window, "s") * 1e9;
|
|
816
837
|
for (const match of driver.quiet ?? []) {
|
|
817
838
|
const line = events.find((event) => event.t > after && event.t <= until && matches(event, match));
|
|
@@ -901,7 +922,7 @@ const measureDriver = (cell, run) => {
|
|
|
901
922
|
void: voided,
|
|
902
923
|
finding: `it ended without its reference event (${prediction.from} on the ${driver.socket} socket)`
|
|
903
924
|
};
|
|
904
|
-
if (driver.ends === void 0 && prediction.from === "last-received" && place !== void 0) voided ??= delayed(place, reference, ended);
|
|
925
|
+
if (driver.ends === void 0 && prediction.from === "last-received" && place !== void 0) voided ??= delayed(place, reference, ended, run.hz, noiseOf(cell));
|
|
905
926
|
const name = outcome.kind === "error" ? outcome.errno : outcome.kind === "eof" ? "EOF" : "silent";
|
|
906
927
|
if (prediction.errno !== "OK" && name !== prediction.errno) findings.unshift(`it ended with ${name}, not ${prediction.errno}`);
|
|
907
928
|
const seen = report === void 0 ? void 0 : events.find((event) => event.t >= report.at && (event.ev === "error" || event.ev === "socket-error"));
|
|
@@ -1224,14 +1245,14 @@ const measureGroup = (cell, run) => {
|
|
|
1224
1245
|
if (heal === null) voided = "the fault never lifted";
|
|
1225
1246
|
else {
|
|
1226
1247
|
const late = (heal.t0 - t0) / 1e9 - valueIn(cell.hold, "s");
|
|
1227
|
-
if (late > valueIn(
|
|
1248
|
+
if (late > valueIn(INJECT, "s")) voided = `the lift came ${Math.round(late * 1e4) / 10} ms after its hold: the injector was late, not the group`;
|
|
1228
1249
|
}
|
|
1229
1250
|
}
|
|
1230
1251
|
const assumed = prediction.detects === null ? void 0 : prediction.rtt;
|
|
1231
1252
|
const first = kernel.find((record) => record.netns === run.netns && record.dst === `${run.group?.members[primary]?.ip}:${(cell.group ?? cell.replicaSet)?.port}` && record.tcp?.srtt !== void 0);
|
|
1232
1253
|
const rtt = first?.tcp?.srtt === void 0 ? null : valueIn(first.tcp.srtt, "us") / 1e3;
|
|
1233
1254
|
if (voided === null && assumed !== void 0 && rtt !== null && rtt > valueIn(assumed.high, "ms")) voided = `rtt-over-assumption: its first round trip was ${rtt} ms, above the ${valueIn(assumed.high, "ms")} ms that the prediction assumes`;
|
|
1234
|
-
if (voided === null && prediction.detects === true && prediction.from === "fault" && (run.fault.t1 - run.fault.t0) / 1e6 > valueIn(
|
|
1255
|
+
if (voided === null && prediction.detects === true && prediction.from === "fault" && (run.fault.t1 - run.fault.t0) / 1e6 > valueIn(INJECT, "ms")) voided = `the commit of the fault took ${Math.round((run.fault.t1 - run.fault.t0) / 1e5) / 10} ms, longer than the noise of the pipeline: its start is not known`;
|
|
1235
1256
|
let reference = t0;
|
|
1236
1257
|
if (prediction.detects === false && prediction.recovers !== void 0) {
|
|
1237
1258
|
const into = events.find((event) => event.ev === "sent" && event.t > run.fault.t1);
|
|
@@ -1253,7 +1274,7 @@ const measureGroup = (cell, run) => {
|
|
|
1253
1274
|
if (accepted !== void 0 && lands === "same" && to !== primary) findings.push(`the client wrote to ${to ?? String(accepted["at"])}, where it should stay on ${primary}`);
|
|
1254
1275
|
for (const error of expected?.errors ?? []) {
|
|
1255
1276
|
const met = group.errors.find((each) => each.name === error.name);
|
|
1256
|
-
const noise = valueIn(
|
|
1277
|
+
const noise = valueIn(noiseOf(cell), "s");
|
|
1257
1278
|
if (met === void 0) findings.push(`the client never met ${error.name}`);
|
|
1258
1279
|
else if (met.first.value < valueIn(error.low, "s") - noise || met.first.value > valueIn(error.high, "s") + noise) findings.push(`the client met ${error.name} ${met.first.value} s after the fault, outside [${valueIn(error.low, "s")}, ${valueIn(error.high, "s")}] s`);
|
|
1259
1280
|
}
|
|
@@ -1537,7 +1558,7 @@ const measureSocket = (cell, run) => {
|
|
|
1537
1558
|
*/
|
|
1538
1559
|
const delayed = (from, to, timer) => {
|
|
1539
1560
|
const firings = socket.filter((record) => record.ev === "timer" && record.what === timer && record.mono >= from && record.mono <= to);
|
|
1540
|
-
const late = firings.find((record) => (record.behind?.value ?? 0
|
|
1561
|
+
const late = firings.find((record) => delayVoids(record.behind?.value ?? 0, run.hz, noiseOf(cell)));
|
|
1541
1562
|
if (late !== void 0) return `its ${timer} timer ran ${late.behind.value} jiffies behind its wheel slot: the VM delayed it, not the kernel`;
|
|
1542
1563
|
return firings.find((record) => timer === "keepalive" && record.owned === true) === void 0 ? null : "its keepalive timer found the socket held by its owner and put itself off by HZ/20: the run measured the owner, not the timer";
|
|
1543
1564
|
};
|
|
@@ -1600,10 +1621,10 @@ const measureSocket = (cell, run) => {
|
|
|
1600
1621
|
const reported = socket.find((record) => record.ev === "error" && (record.err ?? 0) !== 0);
|
|
1601
1622
|
const seen = remote ? void 0 : events.find((event) => event.ev === "error" || event.ev === "eof");
|
|
1602
1623
|
const closeWait = socket.find((record) => record.ev === "state" && newState(record) === 8);
|
|
1603
|
-
const own = cell.prediction.detects === true && cell.prediction.errno === "CLOSED" ? socket.find((record) => record.ev === "state" && oldState(record) === 1 && newState(record) === 4) : void 0;
|
|
1624
|
+
const own = cell.prediction.detects === true && cell.prediction.errno === "CLOSED" || cell.prediction.detects === null && cell.prediction.ends === "own-close" ? socket.find((record) => record.ev === "state" && oldState(record) === 1 && newState(record) === 4) : void 0;
|
|
1604
1625
|
const lines = programLines(cell, run);
|
|
1605
1626
|
const kernelEnded = reported?.mono ?? closeWait?.mono ?? own?.mono ?? seen?.t;
|
|
1606
|
-
const first = dropOf(lines, socket, local);
|
|
1627
|
+
const first = dropOf(lines, socket, local, noiseOf(cell));
|
|
1607
1628
|
const drop = first !== void 0 && (kernelEnded === void 0 || first.at <= kernelEnded) ? first : void 0;
|
|
1608
1629
|
const dropped = drop !== void 0;
|
|
1609
1630
|
const outcome = dropped ? { kind: "dropped" } : reported !== void 0 ? {
|
|
@@ -1616,7 +1637,7 @@ const measureSocket = (cell, run) => {
|
|
|
1616
1637
|
const ended = drop?.at ?? kernelEnded;
|
|
1617
1638
|
const noticed = reported !== void 0 && seen !== void 0 && !dropped ? between(reported.mono, seen.t) : null;
|
|
1618
1639
|
const { prediction } = cell;
|
|
1619
|
-
const pings = pingsOf(lines, socket, local);
|
|
1640
|
+
const pings = pingsOf(lines, socket, local, noiseOf(cell));
|
|
1620
1641
|
const before = run.fault?.t1 ?? ready.t;
|
|
1621
1642
|
const referenceAt = (from) => {
|
|
1622
1643
|
if (from === "request-sent") return request.t;
|
|
@@ -1655,10 +1676,14 @@ const measureSocket = (cell, run) => {
|
|
|
1655
1676
|
toward: "target"
|
|
1656
1677
|
}
|
|
1657
1678
|
}, run)) };
|
|
1679
|
+
const reconnect = prediction.reconnects === void 0 || ended === void 0 ? void 0 : reconnectOf(run, ended, cookie, drop?.line.t ?? ended);
|
|
1680
|
+
const late = reconnect === void 0 || prediction.reconnects === void 0 ? null : reconnectFinding(prediction.reconnects, reconnect, noiseOf(cell));
|
|
1658
1681
|
return {
|
|
1659
1682
|
...base,
|
|
1660
1683
|
...nothing,
|
|
1661
1684
|
...chain,
|
|
1685
|
+
...reconnect === void 0 ? {} : { reconnect },
|
|
1686
|
+
finding: late,
|
|
1662
1687
|
outcome: ended !== void 0 ? outcome : { kind: "answered" },
|
|
1663
1688
|
...from === void 0 || until === void 0 ? {} : { elapsed: between(from, until) },
|
|
1664
1689
|
noticed: prediction.from === void 0 ? null : noticed,
|
|
@@ -1752,7 +1777,7 @@ const measureSocket = (cell, run) => {
|
|
|
1752
1777
|
if (after !== null) finding = `a router forgot the flow ${after.value} s after the last segment, where it should keep it`;
|
|
1753
1778
|
} else if (after === null) finding = "no router forgot the flow in the window, where it should forget it";
|
|
1754
1779
|
else {
|
|
1755
|
-
const noise = valueIn(
|
|
1780
|
+
const noise = valueIn(noiseOf(cell), "s");
|
|
1756
1781
|
const low = valueIn(expected.low, "s") - noise;
|
|
1757
1782
|
const high = valueIn(expected.high, "s") + noise;
|
|
1758
1783
|
if (after.value < low || after.value > high) finding = `a router forgot the flow ${after.value} s after the last segment, outside [${Math.round(low * 1e6) / 1e6}, ${Math.round(high * 1e6) / 1e6}] s`;
|
|
@@ -1796,9 +1821,9 @@ const measureSocket = (cell, run) => {
|
|
|
1796
1821
|
const matches = prediction.errno === "EOF" ? outcome.kind === "eof" : prediction.errno === "CLOSED" ? outcome.kind === "closed" : prediction.errno === "DROPPED" ? outcome.kind === "dropped" : outcome.kind === "error" && outcome.errno === prediction.errno;
|
|
1797
1822
|
const words = outcome.kind === "error" ? outcome.errno : outcome.kind === "closed" ? "its own close" : outcome.kind === "dropped" ? "a drop by its own program" : "end of file";
|
|
1798
1823
|
const reconnect = prediction.reconnects === void 0 ? void 0 : reconnectOf(run, ended, cookie, drop?.line.t ?? ended);
|
|
1799
|
-
const late = reconnect === void 0 || prediction.reconnects === void 0 ? null : reconnectFinding(prediction.reconnects, reconnect);
|
|
1824
|
+
const late = reconnect === void 0 || prediction.reconnects === void 0 ? null : reconnectFinding(prediction.reconnects, reconnect, noiseOf(cell));
|
|
1800
1825
|
const left = prediction.drop === void 0 || drop === void 0 ? void 0 : leftAfter(socket, drop.line);
|
|
1801
|
-
const how = prediction.drop === void 0 || drop === void 0 || left === void 0 ? null : dropFinding(prediction.drop, drop, left);
|
|
1826
|
+
const how = prediction.drop === void 0 || drop === void 0 || left === void 0 ? null : dropFinding(prediction.drop, drop, left, noiseOf(cell));
|
|
1802
1827
|
const found = [
|
|
1803
1828
|
matches ? null : `it ended with ${words}, not ${prediction.errno}`,
|
|
1804
1829
|
late,
|
|
@@ -1812,7 +1837,7 @@ const measureSocket = (cell, run) => {
|
|
|
1812
1837
|
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;
|
|
1813
1838
|
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`;
|
|
1814
1839
|
const commit = run.fault === null ? 0 : (run.fault.t1 - run.fault.t0) / 1e6;
|
|
1815
|
-
if (prediction.from === "fault" && commit > valueIn(
|
|
1840
|
+
if (prediction.from === "fault" && commit > valueIn(INJECT, "ms")) voided ??= `the commit of the fault took ${Math.round(commit * 10) / 10} ms, longer than the noise of the pipeline: its start is not known`;
|
|
1816
1841
|
const unanswered = prediction.from === "ping-sent" ? firstUnanswered(pings, socket, ended) : void 0;
|
|
1817
1842
|
const endLine = drop?.line ?? seen;
|
|
1818
1843
|
const view = unanswered !== void 0 ? endLine === void 0 ? null : between(unanswered.line.t, endLine.t) : remote ? null : socketVantage(cell, events, ready, request);
|
|
@@ -2088,6 +2113,42 @@ const endpointAfter = (hops, element) => {
|
|
|
2088
2113
|
return at;
|
|
2089
2114
|
};
|
|
2090
2115
|
const STOP = { timeout: 0 };
|
|
2116
|
+
/** These are the values of CONFIG_HZ that Linux offers. */
|
|
2117
|
+
const RATES = [
|
|
2118
|
+
100,
|
|
2119
|
+
250,
|
|
2120
|
+
300,
|
|
2121
|
+
1e3
|
|
2122
|
+
];
|
|
2123
|
+
/**
|
|
2124
|
+
* This function gives HZ from two readings of /proc/timer_list, one second apart: its `now at`
|
|
2125
|
+
* time in ns and its jiffies. It gives the nearest rate that Linux offers, or null when the
|
|
2126
|
+
* measurement is more than 5 % from each of them.
|
|
2127
|
+
*/
|
|
2128
|
+
const hzOf = (first, second) => {
|
|
2129
|
+
const seconds = (second.now - first.now) / 1e9;
|
|
2130
|
+
if (!(seconds > 0)) return null;
|
|
2131
|
+
const rate = (second.jiffies - first.jiffies) / seconds;
|
|
2132
|
+
const nearest = RATES.reduce((best, each) => Math.abs(each - rate) < Math.abs(best - rate) ? each : best);
|
|
2133
|
+
return Math.abs(nearest - rate) / nearest <= .05 ? String(nearest) : null;
|
|
2134
|
+
};
|
|
2135
|
+
/** This function measures HZ in a privileged container: jiffies against the time of the kernel, one second apart. */
|
|
2136
|
+
const measuredHz = async (container) => {
|
|
2137
|
+
const read = "awk '/^now at/ {now=$3} /^jiffies:/ {print now, $2; exit}' /proc/timer_list";
|
|
2138
|
+
const values = (await output(container, [
|
|
2139
|
+
"sh",
|
|
2140
|
+
"-c",
|
|
2141
|
+
`${read}; sleep 1; ${read}`
|
|
2142
|
+
]).catch(() => "")).split(/\s+/).filter((each) => each !== "").map(Number);
|
|
2143
|
+
if (values.length !== 4 || values.some((each) => !Number.isFinite(each))) return null;
|
|
2144
|
+
return hzOf({
|
|
2145
|
+
now: values[0],
|
|
2146
|
+
jiffies: values[1]
|
|
2147
|
+
}, {
|
|
2148
|
+
now: values[2],
|
|
2149
|
+
jiffies: values[3]
|
|
2150
|
+
});
|
|
2151
|
+
};
|
|
2091
2152
|
const output = async (container, argv) => {
|
|
2092
2153
|
const result = await container.exec(argv);
|
|
2093
2154
|
if (result.exitCode !== 0) throw new Error(`${argv.join(" ")} exited ${result.exitCode}: ${result.output.trim()}`);
|
|
@@ -2723,7 +2784,18 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2723
2784
|
return {
|
|
2724
2785
|
kernel: await output(client, ["uname", "-r"]),
|
|
2725
2786
|
architecture: await output(client, ["uname", "-m"]),
|
|
2726
|
-
|
|
2787
|
+
...await (async () => {
|
|
2788
|
+
const configured = /^CONFIG_HZ=(\d+)$/m.exec(config)?.[1];
|
|
2789
|
+
if (configured !== void 0) return {
|
|
2790
|
+
hz: configured,
|
|
2791
|
+
hzFrom: "config"
|
|
2792
|
+
};
|
|
2793
|
+
const measured = await measuredHz(admin);
|
|
2794
|
+
return measured === null ? { hz: null } : {
|
|
2795
|
+
hz: measured,
|
|
2796
|
+
hzFrom: "measured"
|
|
2797
|
+
};
|
|
2798
|
+
})(),
|
|
2727
2799
|
boot_id: await output(client, ["cat", "/proc/sys/kernel/random/boot_id"]),
|
|
2728
2800
|
docker: `${runtime.info.containerRuntime.serverVersion} on ${runtime.info.containerRuntime.operatingSystem}`,
|
|
2729
2801
|
target: {
|
|
@@ -4068,7 +4140,7 @@ const judge = (cell, runs) => {
|
|
|
4068
4140
|
const measured = runs.filter((run) => run.warmup !== true).map((run) => measure(cell, run));
|
|
4069
4141
|
return {
|
|
4070
4142
|
measured,
|
|
4071
|
-
verdict: verdictOf(cell.prediction, measured)
|
|
4143
|
+
verdict: verdictOf(cell.prediction, measured, noiseOf(cell))
|
|
4072
4144
|
};
|
|
4073
4145
|
};
|
|
4074
4146
|
/** This function gives the count of lost records in the latest clock line of the prober, since it began. */
|
|
@@ -4088,7 +4160,7 @@ const probeArgs = (cell, lab) => {
|
|
|
4088
4160
|
];
|
|
4089
4161
|
if (probe.ws !== void 0) {
|
|
4090
4162
|
const { ws } = probe;
|
|
4091
|
-
const waits = (prediction.detects
|
|
4163
|
+
const waits = (prediction.detects !== false ? prediction.reconnects?.waits ?? [] : []).reduce((sum, wait) => sum + valueIn(wait.high, "s"), 0);
|
|
4092
4164
|
args.push("--websocket", ws.socket);
|
|
4093
4165
|
args.push("--heartbeat", ws.heartbeat === null ? "off" : "on");
|
|
4094
4166
|
if (ws.heartbeat !== null) args.push("--heartbeat-interval-ms", String(valueIn(ws.heartbeat.interval, "ms")), "--heartbeat-timeout-ms", String(valueIn(ws.heartbeat.timeout, "ms")), "--heartbeat-misses", String(ws.heartbeat.misses));
|
|
@@ -4465,7 +4537,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
4465
4537
|
if (toxicFirst) await lab.toxic(null);
|
|
4466
4538
|
}
|
|
4467
4539
|
if (cell.fault === "freeze-process" && !server && cell.probe.mode !== "idle") await probe.until(["data"], 5e3).catch(() => void 0);
|
|
4468
|
-
const expected = cell.prediction.detects
|
|
4540
|
+
const expected = cell.prediction.detects !== false ? cell.prediction.reconnects : void 0;
|
|
4469
4541
|
if (expected !== void 0) {
|
|
4470
4542
|
const waits = expected.waits.reduce((sum, wait) => sum + valueIn(wait.high, "s"), 0);
|
|
4471
4543
|
await dials(probe, 1 + expected.waits.length, waits * 2e3 + 5e3);
|
|
@@ -4624,8 +4696,13 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
4624
4696
|
const done = await Promise.all(batch.map(({ count, env }) => inEnvironment(cell, env, count, say)));
|
|
4625
4697
|
for (const each of done) {
|
|
4626
4698
|
manifests.push(each.manifest);
|
|
4627
|
-
|
|
4628
|
-
|
|
4699
|
+
const hz = each.manifest.hz === null ? void 0 : Number(each.manifest.hz);
|
|
4700
|
+
const withHz = (run) => hz === void 0 ? run : {
|
|
4701
|
+
...run,
|
|
4702
|
+
hz
|
|
4703
|
+
};
|
|
4704
|
+
runs.push(...each.runs.map(withHz));
|
|
4705
|
+
warmups.push(withHz(each.warmup));
|
|
4629
4706
|
}
|
|
4630
4707
|
}
|
|
4631
4708
|
const { measured, verdict } = judge(cell, runs);
|
|
@@ -4646,6 +4723,6 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
4646
4723
|
}
|
|
4647
4724
|
};
|
|
4648
4725
|
//#endregion
|
|
4649
|
-
export { CONVERGE_MS, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, crossingsOf, cutOf, deadlineOf, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, infoOf, initiateScript, isHopPlace, judge, lagsOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4726
|
+
export { CONVERGE_MS, INJECT, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, hzOf, infoOf, initiateScript, isHopPlace, judge, lagsOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, noiseOf, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4650
4727
|
|
|
4651
4728
|
//# sourceMappingURL=index.js.map
|