@mark1russell7/harness 0.14.4 → 0.15.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 +1 -0
- package/build/{cell-DZm8zXN4.d.ts → cell-biffSiEJ.d.ts} +178 -4
- package/build/{image-EZ8ioZ3L.js → image-BxVTFZ1o.js} +23 -2
- package/build/image-BxVTFZ1o.js.map +1 -0
- package/build/index.d.ts +244 -5
- package/build/index.js +823 -28
- package/build/index.js.map +1 -1
- package/build/schema.d.ts +5 -2
- package/build/schema.js +7 -3
- package/build/schema.js.map +1 -1
- package/image/Dockerfile +3 -3
- package/image/kafka/AdminKafka.java +44 -9
- package/image/kafka/ConsumerKafka.java +359 -0
- package/image/kafka/Dockerfile +2 -2
- package/image/native/Cargo.toml +1 -1
- package/image/native/src/bin/client.rs +164 -28
- package/image/native/src/bin/ctfull.rs +633 -0
- package/image/native/src/bin/inject.rs +16 -0
- package/image/native/src/bin/load.rs +481 -0
- package/image/native/src/bin/ports.rs +1 -8
- package/image/native/src/bin/queue.rs +1 -12
- package/image/native/src/lib.rs +51 -0
- package/package.json +5 -2
- package/build/image-EZ8ioZ3L.js.map +0 -1
package/build/index.js
CHANGED
|
@@ -1,25 +1,251 @@
|
|
|
1
|
-
import { A as unlockedManifests, C as programOf, D as subjectProblems, E as subjectClient, F as jarVersions, I as locksOf, L as npmVersions, M as LOCK_NAMES, N as cargoVersions, O as targetImageOf, P as goVersions, R as versionsOf, S as planOf, T as shippedSource, _ as kafkaClientImage, a as SHIPPED_NAMES, b as nodeHttpImage, c as buildRecordOf, d as harnessImage, f as harnessVersion, g as inContext, h as imageProblems, i as SHIPPED_CLIENTS, j as unpinnedBases, k as targetSourceOf, l as goHttpImage, m as imageOf, n as PINNED, o as amqpClientImage, p as hikariClientImage, r as REPOSITORY, s as basesOf, t as CLIENT_NAMES, u as grpcClientImage, v as mongoClientImage, w as routerImage, x as pgClientImage, y as nodeClientImage } from "./image-
|
|
1
|
+
import { A as unlockedManifests, B as isHopPlace, C as programOf, D as subjectProblems, E as subjectClient, F as jarVersions, H as requestFirst, I as locksOf, L as npmVersions, M as LOCK_NAMES, N as cargoVersions, O as targetImageOf, P as goVersions, R as versionsOf, S as planOf, T as shippedSource, V as latestEndOf, _ as kafkaClientImage, a as SHIPPED_NAMES, b as nodeHttpImage, c as buildRecordOf, d as harnessImage, f as harnessVersion, g as inContext, h as imageProblems, i as SHIPPED_CLIENTS, j as unpinnedBases, k as targetSourceOf, l as goHttpImage, m as imageOf, n as PINNED, o as amqpClientImage, p as hikariClientImage, r as REPOSITORY, s as basesOf, t as CLIENT_NAMES, u as grpcClientImage, v as mongoClientImage, w as routerImage, x as pgClientImage, y as nodeClientImage, z as CLIENT_CAPABILITIES } from "./image-BxVTFZ1o.js";
|
|
2
2
|
import { quantity, valueIn } from "@mark1russell7/quantity";
|
|
3
|
-
import { median, medianInterval as medianInterval$1, quantileSorted } from "@mark1russell7/rigor-statistics";
|
|
3
|
+
import { median, medianInterval as medianInterval$1, quantile, quantileSorted } from "@mark1russell7/rigor-statistics";
|
|
4
|
+
import { rungVerdict } from "@mark1russell7/rigor-bench";
|
|
5
|
+
import { measureLoad, schedule, scheduleHeld } from "@mark1russell7/rigor-chaos";
|
|
4
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
7
|
import { execFile } from "node:child_process";
|
|
6
8
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
7
9
|
import { release, tmpdir } from "node:os";
|
|
8
10
|
import { join } from "node:path";
|
|
9
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
-
//#region src/
|
|
11
|
-
/**
|
|
12
|
-
|
|
12
|
+
//#region src/load.ts
|
|
13
|
+
/**
|
|
14
|
+
* The measure and the verdict of an open-loop cell (SYS-68). A client on a schedule sends each
|
|
15
|
+
* arrival at its time, thus a stall counts in each arrival that it delayed. The package rigor-chaos
|
|
16
|
+
* counts each latency from the planned time. The package rigor-bench judges a quantile by a
|
|
17
|
+
* distribution-free interval.
|
|
18
|
+
*/
|
|
19
|
+
/** This path is where the client of an open-loop cell finds its schedule. */
|
|
20
|
+
const SCHEDULE_PATH = "/tmp/schedule.json";
|
|
21
|
+
/** This function gives the time of a line of the client, in ms from the start of the schedule. */
|
|
22
|
+
const fromStart = (start, t) => (t - start) / 1e6;
|
|
23
|
+
/**
|
|
24
|
+
* This function gives the measure of one run of a schedule from the lines of its client. It gives
|
|
25
|
+
* null when the client did not start the schedule. The first `sent` line of a client is its request
|
|
26
|
+
* before `ready`, and it names no arrival. The end of the run is its last line.
|
|
27
|
+
*/
|
|
28
|
+
const loadOf = (events, plan) => {
|
|
29
|
+
const start = events.find((event) => event.ev === "scheduled")?.t;
|
|
30
|
+
if (start === void 0) return null;
|
|
31
|
+
return measureOf(events, start, plan, void 0);
|
|
32
|
+
};
|
|
33
|
+
/** This function tells if a status of a reply is a failure: a number outside 200 to 299. */
|
|
34
|
+
const failedStatus = (status) => typeof status === "number" && (status < 200 || status > 299);
|
|
35
|
+
/** This function gives the measure of the arrivals of one stream: the lines with the key `key`, or each line for no key. */
|
|
36
|
+
const measureOf = (events, start, plan, key) => {
|
|
37
|
+
const sent = /* @__PURE__ */ new Map();
|
|
38
|
+
const done = /* @__PURE__ */ new Map();
|
|
39
|
+
let failed = 0;
|
|
40
|
+
for (const event of events) {
|
|
41
|
+
const index = event["i"];
|
|
42
|
+
if (typeof index !== "number" || key !== void 0 && event["k"] !== key) continue;
|
|
43
|
+
if (event.ev === "sent") sent.set(index, fromStart(start, event.t));
|
|
44
|
+
if (event.ev === "replied") {
|
|
45
|
+
done.set(index, fromStart(start, event.t));
|
|
46
|
+
if (failedStatus(event["status"])) failed += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const completions = [...sent].map(([index, at]) => ({
|
|
50
|
+
index,
|
|
51
|
+
sent: at,
|
|
52
|
+
done: done.get(index) ?? null
|
|
53
|
+
}));
|
|
54
|
+
const end = events.reduce((latest, event) => Math.max(latest, fromStart(start, event.t)), 0);
|
|
55
|
+
return {
|
|
56
|
+
...measureLoad(plan, completions, end),
|
|
57
|
+
failed
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** This path is where the client `load` finds its plan. */
|
|
61
|
+
const LOAD_PATH = "/tmp/load.json";
|
|
62
|
+
/** This text is the format of the plan of the client `load`. */
|
|
63
|
+
const LOAD_FORMAT = "systems-load/1";
|
|
13
64
|
/**
|
|
14
|
-
* This function
|
|
15
|
-
*
|
|
65
|
+
* This function gives the schedule of each stream of a cell, and the text of the plan of the client
|
|
66
|
+
* `load`. It throws for no stream, for two streams with one key and for a stream with no request.
|
|
16
67
|
*/
|
|
17
|
-
const
|
|
68
|
+
const loadPlanOf = (streams) => {
|
|
69
|
+
if (streams.length === 0) throw new RangeError("a plan of the client load has no stream");
|
|
70
|
+
const keys = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const stream of streams) {
|
|
72
|
+
if (keys.has(stream.key)) throw new RangeError(`two streams have the key ${stream.key}`);
|
|
73
|
+
if (stream.requests.length === 0) throw new RangeError(`the stream ${stream.key} has no request`);
|
|
74
|
+
keys.add(stream.key);
|
|
75
|
+
}
|
|
76
|
+
const scheduled = streams.map((stream) => ({
|
|
77
|
+
key: stream.key,
|
|
78
|
+
schedule: schedule(stream.plan.profile, stream.plan.options),
|
|
79
|
+
requests: stream.requests
|
|
80
|
+
}));
|
|
81
|
+
return {
|
|
82
|
+
streams: scheduled.map(({ key, schedule: each }) => ({
|
|
83
|
+
key,
|
|
84
|
+
schedule: each
|
|
85
|
+
})),
|
|
86
|
+
content: JSON.stringify({
|
|
87
|
+
format: LOAD_FORMAT,
|
|
88
|
+
streams: scheduled
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
};
|
|
18
92
|
/**
|
|
19
|
-
* This function gives the
|
|
20
|
-
*
|
|
93
|
+
* This function gives the measure of each stream of a run of the client `load`, and their pool. Each
|
|
94
|
+
* stream counts its arrivals from 0, thus the lines of a stream are the lines with its key. The pool
|
|
95
|
+
* sums the counts and joins the times, in the order of the streams. It gives null when the client did
|
|
96
|
+
* not start its schedule.
|
|
21
97
|
*/
|
|
22
|
-
const
|
|
98
|
+
const streamsLoadOf = (events, streams) => {
|
|
99
|
+
const start = events.find((event) => event.ev === "scheduled")?.t;
|
|
100
|
+
if (start === void 0) return null;
|
|
101
|
+
const each = streams.map((stream) => [stream.key, measureOf(events, start, stream.schedule, stream.key)]);
|
|
102
|
+
const measures = each.map(([, measure]) => measure);
|
|
103
|
+
const sum = (count) => measures.reduce((total, measure) => total + count(measure), 0);
|
|
104
|
+
const join = (times) => measures.flatMap(times);
|
|
105
|
+
return {
|
|
106
|
+
pool: {
|
|
107
|
+
planned: sum((measure) => measure.planned),
|
|
108
|
+
unsent: sum((measure) => measure.unsent),
|
|
109
|
+
unfinished: sum((measure) => measure.unfinished),
|
|
110
|
+
failed: sum((measure) => measure.failed ?? 0),
|
|
111
|
+
latency: join((measure) => measure.latency),
|
|
112
|
+
service: join((measure) => measure.service),
|
|
113
|
+
lag: join((measure) => measure.lag),
|
|
114
|
+
censored: join((measure) => measure.censored)
|
|
115
|
+
},
|
|
116
|
+
streams: Object.fromEntries(each)
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/** This function gives the spread of a list of times in ms, or null for no time. */
|
|
120
|
+
const spreadOf$1 = (values) => values.length === 0 ? null : {
|
|
121
|
+
p50: quantity(quantile(values, .5, "nearest-rank"), "ms"),
|
|
122
|
+
p99: quantity(quantile(values, .99, "nearest-rank"), "ms"),
|
|
123
|
+
max: quantity(values.reduce((most, value) => Math.max(most, value), 0), "ms")
|
|
124
|
+
};
|
|
125
|
+
/** This time is the date of the record of a band and the date of its verdict. The record has no age, because it comes from this run. */
|
|
126
|
+
const NOW = "2026-01-01T00:00:00.000Z";
|
|
127
|
+
/**
|
|
128
|
+
* This function judges one band of the latency over the pooled arrivals of the counted runs. The
|
|
129
|
+
* top of the band reads the interval of rigor-bench: the band holds only if the interval is under its top.
|
|
130
|
+
* The latencies of one run can follow each other, thus the width of the interval is an estimate.
|
|
131
|
+
* The bottom of the band reads the quantile itself. It gives the text of the verdict, and if it holds.
|
|
132
|
+
*/
|
|
133
|
+
const bandVerdict = (band, values, censored) => {
|
|
134
|
+
const high = valueIn(band.high, "ms");
|
|
135
|
+
const rung = {
|
|
136
|
+
name: "band",
|
|
137
|
+
at: {},
|
|
138
|
+
budgets: [{
|
|
139
|
+
kind: "quantile",
|
|
140
|
+
measure: "latency",
|
|
141
|
+
unit: "ms",
|
|
142
|
+
quantile: band.quantile,
|
|
143
|
+
limit: high
|
|
144
|
+
}],
|
|
145
|
+
test: "the cell"
|
|
146
|
+
};
|
|
147
|
+
const top = rungVerdict(rung, {
|
|
148
|
+
rung: "band",
|
|
149
|
+
commit: "the cell",
|
|
150
|
+
at: NOW,
|
|
151
|
+
real: ["the counted runs"],
|
|
152
|
+
synthetic: [],
|
|
153
|
+
evidence: [{
|
|
154
|
+
kind: "quantile",
|
|
155
|
+
measure: "latency",
|
|
156
|
+
values,
|
|
157
|
+
censored
|
|
158
|
+
}]
|
|
159
|
+
}, { now: NOW });
|
|
160
|
+
const read = values.length + censored.length === 0 ? null : quantile([...values, ...censored], band.quantile, "nearest-rank");
|
|
161
|
+
const low = band.low === void 0 ? null : valueIn(band.low, "ms");
|
|
162
|
+
const above = low === null || read !== null && read >= low;
|
|
163
|
+
const place = low === null ? `under ${high} ms` : `in [${low}, ${high}] ms`;
|
|
164
|
+
const outcome = top.outcome === "within" && !above ? "under the bottom of the band" : top.outcome;
|
|
165
|
+
return {
|
|
166
|
+
holds: top.outcome === "within" && above,
|
|
167
|
+
words: `p${band.quantile * 100} of the latency ${read === null ? "has no value" : `is ${read} ms`}, wanted ${place}: ${outcome}. ${top.reason}`
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* This function judges the share of failed arrivals over the counted runs against a limit. Each
|
|
172
|
+
* planned arrival is a trial. An arrival fails if the client did not send it. It also fails with no
|
|
173
|
+
* reply at the end of its run, or with the status of a failure. The rate budget of rigor-bench
|
|
174
|
+
* reads the upper end of the Wilson interval at 0.95. Thus 381 arrivals with no failure hold a limit
|
|
175
|
+
* of 1 %, and fewer arrivals cannot decide.
|
|
176
|
+
*/
|
|
177
|
+
const failuresVerdict = (limit, failures, trials) => {
|
|
178
|
+
const verdict = rungVerdict({
|
|
179
|
+
name: "failures",
|
|
180
|
+
at: {},
|
|
181
|
+
budgets: [{
|
|
182
|
+
kind: "rate",
|
|
183
|
+
measure: "failures",
|
|
184
|
+
limit
|
|
185
|
+
}],
|
|
186
|
+
test: "the cell"
|
|
187
|
+
}, {
|
|
188
|
+
rung: "failures",
|
|
189
|
+
commit: "the cell",
|
|
190
|
+
at: NOW,
|
|
191
|
+
real: ["the counted runs"],
|
|
192
|
+
synthetic: [],
|
|
193
|
+
evidence: [{
|
|
194
|
+
kind: "rate",
|
|
195
|
+
measure: "failures",
|
|
196
|
+
failures,
|
|
197
|
+
trials
|
|
198
|
+
}]
|
|
199
|
+
}, { now: NOW });
|
|
200
|
+
return {
|
|
201
|
+
holds: verdict.outcome === "within",
|
|
202
|
+
words: `${failures} of ${trials} arrivals failed, wanted a share under ${limit * 100} %: ${verdict.outcome}. ${verdict.reason}`
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* This function judges the load of an open-loop cell over its counted runs. A run that did not keep
|
|
207
|
+
* its schedule measures its driver, not the target, thus it is a finding of that run. Each
|
|
208
|
+
* band reads all the arrivals of the counted runs.
|
|
209
|
+
*/
|
|
210
|
+
const loadVerdict = (prediction, runs) => {
|
|
211
|
+
const limit = valueIn(prediction.lag, "ms");
|
|
212
|
+
const outside = runs.flatMap((run) => {
|
|
213
|
+
if (run.load === void 0) return [{
|
|
214
|
+
run: run.name,
|
|
215
|
+
why: "the client never started its schedule"
|
|
216
|
+
}];
|
|
217
|
+
const check = scheduleHeld(run.load, limit);
|
|
218
|
+
return check.held ? [] : [{
|
|
219
|
+
run: run.name,
|
|
220
|
+
why: check.reason
|
|
221
|
+
}];
|
|
222
|
+
});
|
|
223
|
+
const measures = runs.flatMap((run) => run.load === void 0 ? [] : [run.load]);
|
|
224
|
+
const latency = measures.flatMap((measure) => measure.latency);
|
|
225
|
+
const censored = measures.flatMap((measure) => measure.censored);
|
|
226
|
+
const bands = prediction.bands.map((band) => bandVerdict(band, latency, censored));
|
|
227
|
+
const total = (count) => measures.reduce((sum, measure) => sum + count(measure), 0);
|
|
228
|
+
const planned = total((measure) => measure.planned);
|
|
229
|
+
const unsent = total((measure) => measure.unsent);
|
|
230
|
+
const unfinished = total((measure) => measure.unfinished);
|
|
231
|
+
const failed = total((measure) => measure.failed ?? 0);
|
|
232
|
+
const failures = prediction.failures === void 0 ? void 0 : failuresVerdict(prediction.failures, unsent + unfinished + failed, planned);
|
|
233
|
+
return {
|
|
234
|
+
holds: runs.length > 0 && outside.length === 0 && bands.every((band) => band.holds) && (failures?.holds ?? true),
|
|
235
|
+
outside,
|
|
236
|
+
load: {
|
|
237
|
+
planned,
|
|
238
|
+
unsent,
|
|
239
|
+
unfinished,
|
|
240
|
+
failed,
|
|
241
|
+
latency: spreadOf$1([...latency, ...censored]),
|
|
242
|
+
service: spreadOf$1(measures.flatMap((measure) => measure.service)),
|
|
243
|
+
lag: spreadOf$1(measures.flatMap((measure) => measure.lag.map((lag) => Math.abs(lag)))),
|
|
244
|
+
bands: bands.map((band) => band.words),
|
|
245
|
+
...failures === void 0 ? {} : { failures: failures.words }
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
};
|
|
23
249
|
//#endregion
|
|
24
250
|
//#region src/verdict.ts
|
|
25
251
|
/** This function gives the words of the outcome of a second request: its connection, a retry, and its end. */
|
|
@@ -345,14 +571,15 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
|
|
|
345
571
|
why: "no run counted"
|
|
346
572
|
};
|
|
347
573
|
if (prediction.detects === null) {
|
|
348
|
-
|
|
574
|
+
const law = prediction.queue !== void 0 ? "queue" : prediction.ports !== void 0 ? "port range" : prediction.pause !== void 0 ? "paused consumer" : prediction.conntrack !== void 0 ? "conntrack table" : void 0;
|
|
575
|
+
if (law !== void 0) {
|
|
349
576
|
const off = new Set(outside.map((each) => each.run)).size;
|
|
350
577
|
return {
|
|
351
578
|
...empty,
|
|
352
579
|
decision: off === 0 ? "holds" : "fails",
|
|
353
580
|
band: null,
|
|
354
581
|
outside,
|
|
355
|
-
why: `${counted.length - off} of ${counted.length} runs hold the law of the ${
|
|
582
|
+
why: `${counted.length - off} of ${counted.length} runs hold the law of the ${law}; ${prediction.why}`
|
|
356
583
|
};
|
|
357
584
|
}
|
|
358
585
|
const crossed = counted.flatMap((run) => run.crossings === void 0 ? [] : [run.crossings]);
|
|
@@ -420,6 +647,33 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
|
|
|
420
647
|
why: `${counted.length} runs measured: ${outcomes}; ${timed}.${late} ${prediction.why}`
|
|
421
648
|
};
|
|
422
649
|
}
|
|
650
|
+
if (counted.some((run) => run.load !== void 0)) {
|
|
651
|
+
const judged = loadVerdict(prediction.load ?? {
|
|
652
|
+
lag: quantity(Number.MAX_SAFE_INTEGER, "ms"),
|
|
653
|
+
bands: []
|
|
654
|
+
}, counted.map((run) => ({
|
|
655
|
+
name: name(run),
|
|
656
|
+
load: run.load
|
|
657
|
+
})));
|
|
658
|
+
const middleOfLoad = judged.load.latency?.p50;
|
|
659
|
+
const failed = judged.load.failed === 0 ? "" : `, ${judged.load.failed} with the status of a failure`;
|
|
660
|
+
const read = [
|
|
661
|
+
`${counted.length} runs on a schedule, ${judged.load.planned} arrivals, ${judged.load.unsent} not sent, ${judged.load.unfinished} with no reply${failed}.`,
|
|
662
|
+
...judged.load.bands,
|
|
663
|
+
...judged.load.failures === void 0 ? [] : [judged.load.failures],
|
|
664
|
+
prediction.why
|
|
665
|
+
].join(" ");
|
|
666
|
+
return {
|
|
667
|
+
...empty,
|
|
668
|
+
decision: prediction.load === void 0 ? "measured" : judged.holds ? "holds" : "fails",
|
|
669
|
+
band: null,
|
|
670
|
+
outside: judged.outside,
|
|
671
|
+
median: middleOfLoad === void 0 ? null : s(valueIn(middleOfLoad, "s")),
|
|
672
|
+
...judged.load.latency === null ? {} : { slowest: s(valueIn(judged.load.latency.max, "s")) },
|
|
673
|
+
load: judged.load,
|
|
674
|
+
why: read
|
|
675
|
+
};
|
|
676
|
+
}
|
|
423
677
|
const samples = counted.flatMap((run) => (run.samples ?? []).map((sample) => valueIn(sample, "s"))).sort((a, b) => a - b);
|
|
424
678
|
const retransmissions = counted.reduce((sum, run) => sum + run.retransmissions, 0);
|
|
425
679
|
const ended = counted.filter((run) => run.outcome.kind === "error" || run.outcome.kind === "eof");
|
|
@@ -1351,6 +1605,285 @@ const measureQueue = (run, expected) => {
|
|
|
1351
1605
|
};
|
|
1352
1606
|
};
|
|
1353
1607
|
//#endregion
|
|
1608
|
+
//#region src/conntrack.ts
|
|
1609
|
+
/** This errno tells that a connect got a slot. */
|
|
1610
|
+
const NONE = "none";
|
|
1611
|
+
const numberOr = (line, field) => {
|
|
1612
|
+
const value = line?.[field];
|
|
1613
|
+
return typeof value === "number" ? value : null;
|
|
1614
|
+
};
|
|
1615
|
+
/** This function gives the time from one stamp to a stamp of a field of a line, when the line has it. */
|
|
1616
|
+
const untilField = (from, line, field) => {
|
|
1617
|
+
const stamp = numberOr(line, field);
|
|
1618
|
+
return from === void 0 || stamp === null ? null : between$2(from.t, stamp);
|
|
1619
|
+
};
|
|
1620
|
+
const stallOf = (line) => line === void 0 ? null : {
|
|
1621
|
+
errno: String(line["errno"]),
|
|
1622
|
+
time: quantity(Math.round((numberOr(line, "elapsed") ?? 0) / 1e3) / 1e6, "s"),
|
|
1623
|
+
drops: numberOr(line, "drops") ?? 0,
|
|
1624
|
+
retransFail: numberOr(line, "retrans_fail") ?? 0
|
|
1625
|
+
};
|
|
1626
|
+
/** This function tells where a reading differs from the law of its cell. */
|
|
1627
|
+
const findingsOf$1 = (read, failed, expected) => {
|
|
1628
|
+
const found = [];
|
|
1629
|
+
const { max, neighbor, refused, drained, pressure } = expected;
|
|
1630
|
+
if (typeof failed === "string") found.push(`a connect failed with ${failed} before the client held its flows`);
|
|
1631
|
+
if (read.flows !== max) found.push(`the client held ${read.flows} flows, not ${max}`);
|
|
1632
|
+
if (read.count !== max) found.push(`the count read ${read.count} at the fill, not ${max}`);
|
|
1633
|
+
if (neighbor !== void 0 && read.neighbor !== neighbor) found.push(`the namespace of the target read ${read.neighbor ?? "nothing"}, not ${neighbor}`);
|
|
1634
|
+
if (refused !== void 0) {
|
|
1635
|
+
const stall = read.stall;
|
|
1636
|
+
if (stall === null) found.push("no connect after the fill ended");
|
|
1637
|
+
else {
|
|
1638
|
+
if (stall.errno !== refused.errno) found.push(`the connect after the fill ended with ${stall.errno}, not ${refused.errno}`);
|
|
1639
|
+
if (!inside(stall.time, refused.within)) found.push(`the connect after the fill took ${valueIn(stall.time, "s")} s, outside ${bandWords(refused.within)}`);
|
|
1640
|
+
if (stall.drops !== refused.drops) found.push(`the field drop rose by ${stall.drops}, not ${refused.drops}`);
|
|
1641
|
+
if (stall.retransFail !== refused.retransFail) found.push(`TCPRetransFail rose by ${stall.retransFail}, not ${refused.retransFail}`);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (drained !== void 0) {
|
|
1645
|
+
for (const [name, time] of [["first fall", read.firstFall], ["zero", read.zero]]) if (time === null || !inside(time, drained)) found.push(`the ${name} of the count came at ${time === null ? "no time" : `${valueIn(time, "s")} s`}, outside ${bandWords(drained)}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (pressure !== void 0) {
|
|
1648
|
+
const stall = read.stall;
|
|
1649
|
+
if (read.closed !== max) found.push(`the count read ${read.closed ?? "nothing"} after each side closed, not ${max}`);
|
|
1650
|
+
const { highest } = pressure;
|
|
1651
|
+
if (highest === void 0) {
|
|
1652
|
+
if (read.lowest !== pressure.lowest) found.push(`the lowest count was ${read.lowest ?? "nothing"}, not ${pressure.lowest}`);
|
|
1653
|
+
if (read.after !== pressure.after) found.push(`the count read ${read.after ?? "nothing"} after the connect, not ${pressure.after}`);
|
|
1654
|
+
} else if (read.lowest === null || read.lowest < pressure.lowest || read.lowest > highest) found.push(`the lowest count was ${read.lowest ?? "nothing"}, outside ${pressure.lowest} to ${highest}`);
|
|
1655
|
+
else if (read.after !== read.lowest + 1) found.push(`the count read ${read.after ?? "nothing"} after the connect, not ${read.lowest + 1}`);
|
|
1656
|
+
if (stall === null) found.push("no connect after the close ended");
|
|
1657
|
+
else {
|
|
1658
|
+
if (stall.errno !== NONE) found.push(`the connect after the close ended with ${stall.errno}`);
|
|
1659
|
+
if (!pressure.points.some((point) => inside(stall.time, point))) found.push(`the connect after the close took ${valueIn(stall.time, "s")} s, on no point of the schedule`);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return found;
|
|
1663
|
+
};
|
|
1664
|
+
/**
|
|
1665
|
+
* This function reads one run of a conntrack cell from the lines of its client and its target. An
|
|
1666
|
+
* `unsettled` line makes the run void. The entries of the run before it were still in the table, thus
|
|
1667
|
+
* the run did not start from an empty table.
|
|
1668
|
+
*/
|
|
1669
|
+
const measureConntrack = (run, expected) => {
|
|
1670
|
+
const { events, kernel } = run;
|
|
1671
|
+
const retransmissions = kernel.filter((record) => record.netns === run.netns && record.ev === "retrans").length;
|
|
1672
|
+
const base = {
|
|
1673
|
+
environment: run.environment,
|
|
1674
|
+
index: run.index,
|
|
1675
|
+
outcome: { kind: "answered" },
|
|
1676
|
+
elapsed: null,
|
|
1677
|
+
socket: null,
|
|
1678
|
+
noticed: null,
|
|
1679
|
+
steps: [],
|
|
1680
|
+
probes: 0,
|
|
1681
|
+
retransmissions,
|
|
1682
|
+
acks: 0
|
|
1683
|
+
};
|
|
1684
|
+
const voided = (why) => ({
|
|
1685
|
+
...base,
|
|
1686
|
+
void: why,
|
|
1687
|
+
finding: null
|
|
1688
|
+
});
|
|
1689
|
+
const unsettled = events.find((event) => event.ev === "unsettled");
|
|
1690
|
+
if (unsettled !== void 0) return voided(`the table held ${String(unsettled["count"])} entries at the end of the wait before the run`);
|
|
1691
|
+
if (events.find((event) => event.ev === "done") === void 0) {
|
|
1692
|
+
const error = events.find((event) => event.ev === "error");
|
|
1693
|
+
return voided(error === void 0 ? "the client did not reach its end" : `the client did not reach its end: ${String(error["what"])}`);
|
|
1694
|
+
}
|
|
1695
|
+
if (run.lost === null) return voided("the prober wrote no clock line after the run, so nothing says what it dropped");
|
|
1696
|
+
if (run.lost > 0) return voided(`the prober dropped ${run.lost} records`);
|
|
1697
|
+
const full = events.find((event) => event.ev === "full");
|
|
1698
|
+
if (full === void 0) return voided("the client wrote no line for its fill");
|
|
1699
|
+
const stall = events.find((event) => event.ev === "stall");
|
|
1700
|
+
const closing = events.find((event) => event.ev === "closing");
|
|
1701
|
+
const drained = events.find((event) => event.ev === "drained");
|
|
1702
|
+
const closed = events.find((event) => event.ev === "closed");
|
|
1703
|
+
const neighbor = (run.server ?? []).filter((line) => line.ev === "count" && line.t >= full.t && (stall === void 0 || line.t <= stall.t)).at(-1);
|
|
1704
|
+
const conntrack = {
|
|
1705
|
+
flows: numberOr(full, "flows") ?? 0,
|
|
1706
|
+
count: numberOr(full, "count") ?? 0,
|
|
1707
|
+
neighbor: numberOr(neighbor, "count"),
|
|
1708
|
+
stall: stallOf(stall),
|
|
1709
|
+
firstFall: untilField(closing, drained, "first_fall"),
|
|
1710
|
+
zero: untilField(closing, drained, "zero"),
|
|
1711
|
+
closed: numberOr(closed, "count"),
|
|
1712
|
+
lowest: numberOr(drained ?? stall, "lowest"),
|
|
1713
|
+
after: numberOr(stall, "after")
|
|
1714
|
+
};
|
|
1715
|
+
const found = findingsOf$1(conntrack, full["errno"], expected);
|
|
1716
|
+
return {
|
|
1717
|
+
...base,
|
|
1718
|
+
void: null,
|
|
1719
|
+
finding: found.length === 0 ? null : found.join(". "),
|
|
1720
|
+
conntrack
|
|
1721
|
+
};
|
|
1722
|
+
};
|
|
1723
|
+
//#endregion
|
|
1724
|
+
//#region src/pause.ts
|
|
1725
|
+
/** This expression finds the stamp of a log line of a broker, as its console writes it, on UTC. */
|
|
1726
|
+
const LOG_TIME = /\[(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d),(\d{3})\]/;
|
|
1727
|
+
/** This function gives the time of a log line on the monotonic clock of a member, in ns, from the `start` line of that member. It is null for a line with no stamp. */
|
|
1728
|
+
const logTimeOf = (line, start) => {
|
|
1729
|
+
const found = LOG_TIME.exec(line);
|
|
1730
|
+
if (found === null) return null;
|
|
1731
|
+
const wall = Date.UTC(Number(found[1]), Number(found[2]) - 1, Number(found[3]), Number(found[4]), Number(found[5]), Number(found[6]), Number(found[7]));
|
|
1732
|
+
return start.t + (wall - Number(start["wall"])) * 1e6;
|
|
1733
|
+
};
|
|
1734
|
+
/** This function gives the time between two instants of the monotonic clock, in s, to 1 microsecond. */
|
|
1735
|
+
const secondsBetween = (from, to) => quantity(Math.round((to - from) / 1e3) / 1e6, "s");
|
|
1736
|
+
const isInside = (time, band) => {
|
|
1737
|
+
const seconds = valueIn(time, "s");
|
|
1738
|
+
return seconds >= valueIn(band.low, "s") && seconds <= valueIn(band.high, "s");
|
|
1739
|
+
};
|
|
1740
|
+
const bandText = (band) => `[${valueIn(band.low, "s")}, ${valueIn(band.high, "s")}] s`;
|
|
1741
|
+
const isChange = (event) => event.ev === "assigned" || event.ev === "revoked" || event.ev === "lost";
|
|
1742
|
+
/** This function gives the partitions that a line of the rebalance listener names, in its field `partitions` or `held`. */
|
|
1743
|
+
const partitionsOf = (event, field) => event[field] ?? [];
|
|
1744
|
+
/** This function gives the last line of the rebalance listener in a list of lines. */
|
|
1745
|
+
const lastChange = (events) => events.findLast(isChange);
|
|
1746
|
+
/** This function gives the offset that a commit names for a partition, or null. */
|
|
1747
|
+
const offsetOf = (event, partition) => {
|
|
1748
|
+
const value = event["offsets"]?.[String(partition)];
|
|
1749
|
+
return typeof value === "number" ? value : null;
|
|
1750
|
+
};
|
|
1751
|
+
/**
|
|
1752
|
+
* This function tells if the two members can take the fault. Each member holds one partition, the
|
|
1753
|
+
* two partitions differ, and the first member has two acknowledged commits on its partition after its
|
|
1754
|
+
* last change. Thus the first member reads and commits at a steady rate.
|
|
1755
|
+
*/
|
|
1756
|
+
const pauseReady = (first, other) => {
|
|
1757
|
+
const mine = lastChange(first);
|
|
1758
|
+
const theirs = lastChange(other);
|
|
1759
|
+
if (mine === void 0 || theirs === void 0) return false;
|
|
1760
|
+
const held = partitionsOf(mine, "held");
|
|
1761
|
+
const others = partitionsOf(theirs, "held");
|
|
1762
|
+
if (held.length !== 1 || others.length !== 1 || held[0] === others[0]) return false;
|
|
1763
|
+
return first.filter((event) => event.ev === "commit" && event.t > mine.t && offsetOf(event, held[0]) !== null).length >= 2;
|
|
1764
|
+
};
|
|
1765
|
+
/** This function tells if a log line of a broker tells that a member left a group, under either protocol. */
|
|
1766
|
+
const isFenceLine = (line) => /Member \S+ fenced from the group because|Member \S+ in group \S+ has failed, removing it from the group/.test(line);
|
|
1767
|
+
/** This function tells if a fence line names a group. The two protocols write the name in different words. */
|
|
1768
|
+
const namesGroup = (line, group) => line.includes(`[GroupId ${group}]`) || line.includes(`in group ${group} has`);
|
|
1769
|
+
/** This function compares a reading to the law, and gives each disagreement. */
|
|
1770
|
+
const findingsOf = (reading, prediction, fences, otherReports) => {
|
|
1771
|
+
const found = [];
|
|
1772
|
+
const { commit, fence, takeover } = reading;
|
|
1773
|
+
if (prediction.fence === null) {
|
|
1774
|
+
if (fences.length > 0) found.push(`the group removed a member: ${fences[0]}`);
|
|
1775
|
+
if (otherReports) found.push("the other member reported the partition");
|
|
1776
|
+
} else {
|
|
1777
|
+
if (fence === null) found.push("no log line tells of a fence, and the other member did not report the partition");
|
|
1778
|
+
else if (!isInside(fence, prediction.fence)) found.push(`the fence is ${valueIn(fence, "s")} s, outside ${bandText(prediction.fence)}`);
|
|
1779
|
+
if (reading.otherFirst !== reading.lastAck) found.push(`the other member started at offset ${reading.otherFirst}, and the last commit is ${reading.lastAck}`);
|
|
1780
|
+
}
|
|
1781
|
+
if (prediction.takeover !== null) {
|
|
1782
|
+
if (takeover === null) found.push("the other member did not report the partition");
|
|
1783
|
+
else if (!isInside(takeover, prediction.takeover)) found.push(`the other member reported the partition at ${valueIn(takeover, "s")} s, outside ${bandText(prediction.takeover)}`);
|
|
1784
|
+
}
|
|
1785
|
+
if (commit === null) {
|
|
1786
|
+
found.push("the member made no commit after the stop");
|
|
1787
|
+
return found;
|
|
1788
|
+
}
|
|
1789
|
+
switch (prediction.thaw) {
|
|
1790
|
+
case "fails":
|
|
1791
|
+
if (commit.name !== "CommitFailedException") found.push(`the commit after the stop gave ${commit.name ?? "an acknowledgment"}, not CommitFailedException`);
|
|
1792
|
+
break;
|
|
1793
|
+
case "acknowledged":
|
|
1794
|
+
if (!commit.acked) found.push(`the commit after the stop failed with ${commit.name}`);
|
|
1795
|
+
if (commit.epoch !== reading.epochBefore) found.push(`the epoch changed from ${reading.epochBefore} to ${commit.epoch}`);
|
|
1796
|
+
break;
|
|
1797
|
+
case "rejoin": {
|
|
1798
|
+
const within = prediction.rejoinWithin;
|
|
1799
|
+
if (reading.rejoin === null) found.push("the member did not get a new epoch");
|
|
1800
|
+
else if (within !== void 0 && valueIn(reading.rejoin, "s") > valueIn(within, "s")) found.push(`the new epoch came at ${valueIn(reading.rejoin, "s")} s, above ${valueIn(within, "s")} s`);
|
|
1801
|
+
if (!commit.acked) found.push(`the stale commit failed with ${commit.name}`);
|
|
1802
|
+
if (reading.reassigned) found.push("the member held the partition again before its commit");
|
|
1803
|
+
if (reading.committed !== reading.stale) found.push(`the committed offset is ${reading.committed}, and the stale commit named ${reading.stale}`);
|
|
1804
|
+
if (reading.stale === null || reading.otherLast === null || reading.stale >= reading.otherLast) found.push(`the stale commit ${reading.stale} is not below the progress ${reading.otherLast} of the other member`);
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return found;
|
|
1809
|
+
};
|
|
1810
|
+
/** This function reads one run of a paused consumer against its law. */
|
|
1811
|
+
const measurePause = (run, prediction) => {
|
|
1812
|
+
const retransmissions = run.kernel.filter((record) => record.netns === run.netns && record.ev === "retrans").length;
|
|
1813
|
+
const base = {
|
|
1814
|
+
environment: run.environment,
|
|
1815
|
+
index: run.index,
|
|
1816
|
+
outcome: { kind: "answered" },
|
|
1817
|
+
elapsed: null,
|
|
1818
|
+
socket: null,
|
|
1819
|
+
noticed: null,
|
|
1820
|
+
steps: [],
|
|
1821
|
+
probes: 0,
|
|
1822
|
+
retransmissions,
|
|
1823
|
+
acks: 0
|
|
1824
|
+
};
|
|
1825
|
+
const voided = (why) => ({
|
|
1826
|
+
...base,
|
|
1827
|
+
void: why,
|
|
1828
|
+
finding: null
|
|
1829
|
+
});
|
|
1830
|
+
const { fault, heal, pause } = run;
|
|
1831
|
+
if (fault === null || pause === void 0) return voided("the partitions were not one for each member before the stop");
|
|
1832
|
+
if (heal === void 0 || heal === null) return voided("the stop did not end");
|
|
1833
|
+
const stop = fault.t0;
|
|
1834
|
+
const thaw = heal.t0;
|
|
1835
|
+
const before = run.events.filter((event) => event.t < stop);
|
|
1836
|
+
const after = run.events.filter((event) => event.t >= stop);
|
|
1837
|
+
const start = run.events.find((event) => event.ev === "start");
|
|
1838
|
+
if (start === void 0) return voided("the member wrote no start line");
|
|
1839
|
+
const mine = lastChange(before);
|
|
1840
|
+
const theirs = lastChange(pause.other.filter((event) => event.t < stop));
|
|
1841
|
+
const held = mine === void 0 ? [] : partitionsOf(mine, "held");
|
|
1842
|
+
const others = theirs === void 0 ? [] : partitionsOf(theirs, "held");
|
|
1843
|
+
const partition = held[0];
|
|
1844
|
+
if (partition === void 0 || held.length !== 1 || others.length !== 1 || others[0] === partition) return voided("the partitions were not one for each member before the stop");
|
|
1845
|
+
if (after.find((event) => event.ev === "pause")?.["in"] !== "process") return voided("the stop landed outside the processing of a batch");
|
|
1846
|
+
if (before.findLast((event) => event.ev === "read")?.["partition"] !== partition) return voided("the last read before the stop was on another partition");
|
|
1847
|
+
const lastCommit = before.findLast((event) => event.ev === "commit" && offsetOf(event, partition) !== null);
|
|
1848
|
+
if (lastCommit === void 0) return voided("the member had no acknowledged commit on its partition before the stop");
|
|
1849
|
+
const member = String(mine?.["member"]);
|
|
1850
|
+
const fenceAt = pause.fences.filter((line) => line.includes(member)).map((line) => logTimeOf(line, start)).find((time) => time !== null && time >= stop) ?? null;
|
|
1851
|
+
const taken = pause.other.find((event) => event.ev === "assigned" && event.t >= stop && partitionsOf(event, "partitions").includes(partition));
|
|
1852
|
+
if (prediction.fence !== null && fenceAt === null && taken !== void 0) return voided("the broker log lost the line of the fence");
|
|
1853
|
+
const thawed = after.find((event) => event.ev === "commit" || event.ev === "commit-fail");
|
|
1854
|
+
const rejoined = after.find((event) => event.ev === "rejoined");
|
|
1855
|
+
const ackAt = thawed?.t ?? Number.POSITIVE_INFINITY;
|
|
1856
|
+
const otherCommit = pause.other.findLast((event) => event.ev === "commit" && event.t < ackAt && offsetOf(event, partition) !== null);
|
|
1857
|
+
const committed = after.find((event) => event.ev === "committed");
|
|
1858
|
+
const epochOf = (event) => typeof event?.["epoch"] === "number" ? event["epoch"] : null;
|
|
1859
|
+
const pauseReading = {
|
|
1860
|
+
partition,
|
|
1861
|
+
fence: fenceAt === null ? null : secondsBetween(stop, fenceAt),
|
|
1862
|
+
takeover: taken === void 0 ? null : secondsBetween(stop, taken.t),
|
|
1863
|
+
commit: thawed === void 0 ? null : {
|
|
1864
|
+
acked: thawed.ev === "commit",
|
|
1865
|
+
name: typeof thawed["name"] === "string" ? thawed["name"] : null,
|
|
1866
|
+
message: typeof thawed["message"] === "string" ? thawed["message"] : null,
|
|
1867
|
+
epoch: epochOf(thawed)
|
|
1868
|
+
},
|
|
1869
|
+
epochBefore: epochOf(lastCommit),
|
|
1870
|
+
lastAck: offsetOf(lastCommit, partition),
|
|
1871
|
+
otherFirst: pause.other.find((event) => event.ev === "read" && event.t >= stop && event["partition"] === partition)?.["first"] ?? null,
|
|
1872
|
+
otherLast: otherCommit === void 0 ? null : offsetOf(otherCommit, partition),
|
|
1873
|
+
rejoin: rejoined === void 0 ? null : secondsBetween(thaw, rejoined.t),
|
|
1874
|
+
reassigned: after.some((event) => event.ev === "assigned" && event.t >= thaw && event.t <= ackAt && partitionsOf(event, "partitions").includes(partition)),
|
|
1875
|
+
committed: committed === void 0 ? null : offsetOf(committed, partition),
|
|
1876
|
+
stale: thawed === void 0 ? null : offsetOf(thawed, partition)
|
|
1877
|
+
};
|
|
1878
|
+
const findings = findingsOf(pauseReading, prediction, pause.fences, taken !== void 0);
|
|
1879
|
+
return {
|
|
1880
|
+
...base,
|
|
1881
|
+
void: null,
|
|
1882
|
+
finding: findings.length === 0 ? null : findings.join(". "),
|
|
1883
|
+
pause: pauseReading
|
|
1884
|
+
};
|
|
1885
|
+
};
|
|
1886
|
+
//#endregion
|
|
1354
1887
|
//#region src/ports.ts
|
|
1355
1888
|
/** This errno ends a search that found no port. */
|
|
1356
1889
|
const NO_PORT = "EADDRNOTAVAIL";
|
|
@@ -2312,6 +2845,21 @@ const requestOf = (events) => {
|
|
|
2312
2845
|
};
|
|
2313
2846
|
/** This function reads the socket of one run against its cell, as `measure` does without the second request of an HTTP client. */
|
|
2314
2847
|
const measureSocket = (cell, run) => {
|
|
2848
|
+
if (run.missing !== void 0) return {
|
|
2849
|
+
environment: run.environment,
|
|
2850
|
+
index: run.index,
|
|
2851
|
+
outcome: { kind: "answered" },
|
|
2852
|
+
elapsed: null,
|
|
2853
|
+
socket: null,
|
|
2854
|
+
noticed: null,
|
|
2855
|
+
steps: [],
|
|
2856
|
+
probes: 0,
|
|
2857
|
+
retransmissions: 0,
|
|
2858
|
+
acks: 0,
|
|
2859
|
+
void: `the environment cannot do this cell: ${run.missing}`,
|
|
2860
|
+
finding: null
|
|
2861
|
+
};
|
|
2862
|
+
if (cell.prediction.detects === null && cell.prediction.pause !== void 0) return measurePause(run, cell.prediction.pause);
|
|
2315
2863
|
if (cell.group !== void 0 || cell.replicaSet !== void 0 || cell.cluster !== void 0 || cell.pair !== void 0 || cell.kafka !== void 0) {
|
|
2316
2864
|
const read = measureGroup(cell, run);
|
|
2317
2865
|
return {
|
|
@@ -2332,6 +2880,7 @@ const measureSocket = (cell, run) => {
|
|
|
2332
2880
|
}
|
|
2333
2881
|
if (cell.prediction.detects === null && cell.prediction.queue !== void 0) return measureQueue(run, cell.prediction.queue);
|
|
2334
2882
|
if (cell.prediction.detects === null && cell.prediction.ports !== void 0) return measurePorts(run, cell.prediction.ports);
|
|
2883
|
+
if (cell.prediction.detects === null && cell.prediction.conntrack !== void 0) return measureConntrack(run, cell.prediction.conntrack);
|
|
2335
2884
|
if (cell.prediction.detects !== null && cell.prediction.driver !== void 0) return measureDriver(cell, run);
|
|
2336
2885
|
const { events, kernel } = run;
|
|
2337
2886
|
const ready = events.find((event) => event.ev === "ready");
|
|
@@ -2462,7 +3011,13 @@ const measureSocket = (cell, run) => {
|
|
|
2462
3011
|
forgot: forgotten === void 0 ? null : between(lastRx, forgotten.t)
|
|
2463
3012
|
};
|
|
2464
3013
|
if (prediction.detects === null) {
|
|
2465
|
-
const
|
|
3014
|
+
const load = run.streams !== void 0 ? streamsLoadOf(events, run.streams)?.pool ?? null : run.schedule === void 0 ? void 0 : loadOf(events, run.schedule);
|
|
3015
|
+
if (load === null) return {
|
|
3016
|
+
...base,
|
|
3017
|
+
...nothing,
|
|
3018
|
+
void: "the client never started its schedule"
|
|
3019
|
+
};
|
|
3020
|
+
const sends = load === void 0 ? events.filter((event) => event.ev === "sent").slice(1) : [];
|
|
2466
3021
|
const answers = events.some((event) => event.ev === "replied") ? "replied" : "data";
|
|
2467
3022
|
const samples = sends.flatMap((send, n) => {
|
|
2468
3023
|
const next = sends[n + 1]?.t ?? Infinity;
|
|
@@ -2500,6 +3055,7 @@ const measureSocket = (cell, run) => {
|
|
|
2500
3055
|
noticed: prediction.from === void 0 ? null : noticed,
|
|
2501
3056
|
steps: retransmitted.filter((record) => record.mono > request.t).map((record) => between(request.t, record.mono)),
|
|
2502
3057
|
samples,
|
|
3058
|
+
...load === void 0 ? {} : { load },
|
|
2503
3059
|
reordering: reorderingOf(socket.filter((record) => record.mono > request.t), peerRecords.filter((record) => record.mono > request.t)),
|
|
2504
3060
|
...crossings,
|
|
2505
3061
|
void: voided
|
|
@@ -3446,6 +4002,7 @@ const clientOf = async (kind) => {
|
|
|
3446
4002
|
clientImage: await imageOf(client.image),
|
|
3447
4003
|
command: client.command,
|
|
3448
4004
|
clientKind: typeof kind === "string" ? kind : "image",
|
|
4005
|
+
...client.capabilities === void 0 || client.capabilities.length === 0 ? {} : { capabilities: client.capabilities },
|
|
3449
4006
|
...typeof client.image === "string" ? {} : { source: client.image }
|
|
3450
4007
|
};
|
|
3451
4008
|
};
|
|
@@ -3477,18 +4034,21 @@ const deviceOf = (route) => {
|
|
|
3477
4034
|
*/
|
|
3478
4035
|
const openLab = async (target, kind = "tcp", topology) => {
|
|
3479
4036
|
const image = await harnessImage();
|
|
3480
|
-
const { clientImage, command, clientKind, source } = await clientOf(kind);
|
|
4037
|
+
const { clientImage, command, clientKind, capabilities, source } = await clientOf(kind);
|
|
3481
4038
|
const hops = topology?.hops ?? [];
|
|
3482
4039
|
const { GenericContainer, Network, Wait, getContainerRuntimeClient } = await containers();
|
|
3483
4040
|
const runtime = await getContainerRuntimeClient();
|
|
3484
4041
|
const opened = [];
|
|
3485
4042
|
const networks = [];
|
|
3486
4043
|
const serverLines = lines();
|
|
4044
|
+
let restoreHost = () => Promise.resolve([]);
|
|
3487
4045
|
let fabric;
|
|
3488
4046
|
const close = async () => {
|
|
4047
|
+
const restored = await restoreHost().then(() => null, (error) => error);
|
|
3489
4048
|
await fabric?.close();
|
|
3490
4049
|
for (const container of opened.reverse()) await container.stop(STOP).catch(() => void 0);
|
|
3491
4050
|
for (const network of networks.reverse()) await network.stop().catch(() => void 0);
|
|
4051
|
+
if (restored !== null) throw restored;
|
|
3492
4052
|
};
|
|
3493
4053
|
/** This function gives the address of a container on a network, as Docker has it at this time. A container can join a network after its start. */
|
|
3494
4054
|
const ipOn = async (container, network) => {
|
|
@@ -3793,6 +4353,33 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
3793
4353
|
};
|
|
3794
4354
|
};
|
|
3795
4355
|
const proxies = inChain.filter((hop) => hop.hop.kind === "proxy");
|
|
4356
|
+
let hostAdmin;
|
|
4357
|
+
const hostChanges = [];
|
|
4358
|
+
let hostDone;
|
|
4359
|
+
restoreHost = async () => {
|
|
4360
|
+
if (hostDone !== void 0) return hostDone;
|
|
4361
|
+
const restored = [];
|
|
4362
|
+
for (const change of [...hostChanges].reverse()) {
|
|
4363
|
+
await output(hostAdmin, [
|
|
4364
|
+
"sysctl",
|
|
4365
|
+
"-q",
|
|
4366
|
+
"-w",
|
|
4367
|
+
`${change.name}=${change.before}`
|
|
4368
|
+
]);
|
|
4369
|
+
const read = await output(hostAdmin, [
|
|
4370
|
+
"sysctl",
|
|
4371
|
+
"-n",
|
|
4372
|
+
change.name
|
|
4373
|
+
]);
|
|
4374
|
+
if (read !== change.before) throw new Error(`${change.name} reads ${read} after the restore, not ${change.before}`);
|
|
4375
|
+
restored.unshift({
|
|
4376
|
+
...change,
|
|
4377
|
+
restored: read
|
|
4378
|
+
});
|
|
4379
|
+
}
|
|
4380
|
+
hostDone = restored;
|
|
4381
|
+
return restored;
|
|
4382
|
+
};
|
|
3796
4383
|
return {
|
|
3797
4384
|
serverIp,
|
|
3798
4385
|
targetIp,
|
|
@@ -3839,6 +4426,53 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
3839
4426
|
]);
|
|
3840
4427
|
return read;
|
|
3841
4428
|
},
|
|
4429
|
+
async namespaceSetup(commands) {
|
|
4430
|
+
for (const command of commands) {
|
|
4431
|
+
await output(admin, [...command]);
|
|
4432
|
+
await output(injector, [...command]);
|
|
4433
|
+
}
|
|
4434
|
+
},
|
|
4435
|
+
async hostSysctl(settings) {
|
|
4436
|
+
if (settings.length === 0) return;
|
|
4437
|
+
if (hostAdmin === void 0) {
|
|
4438
|
+
hostAdmin = await new GenericContainer(image).withNetworkMode("host").withPrivilegedMode().start();
|
|
4439
|
+
opened.push(hostAdmin);
|
|
4440
|
+
}
|
|
4441
|
+
for (const { name, value, guard } of settings) {
|
|
4442
|
+
if (guard !== void 0) {
|
|
4443
|
+
const reads = Number(await output(hostAdmin, [
|
|
4444
|
+
"sysctl",
|
|
4445
|
+
"-n",
|
|
4446
|
+
guard.name
|
|
4447
|
+
]));
|
|
4448
|
+
if (!(reads <= guard.atMost)) throw new Error(`${guard.name} of the host reads ${reads}, above ${guard.atMost}`);
|
|
4449
|
+
}
|
|
4450
|
+
const before = await output(hostAdmin, [
|
|
4451
|
+
"sysctl",
|
|
4452
|
+
"-n",
|
|
4453
|
+
name
|
|
4454
|
+
]);
|
|
4455
|
+
hostChanges.push({
|
|
4456
|
+
name,
|
|
4457
|
+
before,
|
|
4458
|
+
set: value,
|
|
4459
|
+
restored: null
|
|
4460
|
+
});
|
|
4461
|
+
await output(hostAdmin, [
|
|
4462
|
+
"sysctl",
|
|
4463
|
+
"-q",
|
|
4464
|
+
"-w",
|
|
4465
|
+
`${name}=${value}`
|
|
4466
|
+
]);
|
|
4467
|
+
const set = await output(hostAdmin, [
|
|
4468
|
+
"sysctl",
|
|
4469
|
+
"-n",
|
|
4470
|
+
name
|
|
4471
|
+
]);
|
|
4472
|
+
if (set !== value) throw new Error(`${name} of the host reads ${set} after the write, not ${value}`);
|
|
4473
|
+
}
|
|
4474
|
+
},
|
|
4475
|
+
restoreHost: () => restoreHost(),
|
|
3842
4476
|
async configure(values) {
|
|
3843
4477
|
const read = {};
|
|
3844
4478
|
if (Object.keys(values).length === 0) return read;
|
|
@@ -4090,9 +4724,10 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
4090
4724
|
query
|
|
4091
4725
|
]);
|
|
4092
4726
|
},
|
|
4093
|
-
async probe(args) {
|
|
4727
|
+
async probe(args, files = []) {
|
|
4094
4728
|
const events = lines();
|
|
4095
|
-
const
|
|
4729
|
+
const prober = new GenericContainer(clientImage).withNetworkMode(`container:${client.getId()}`).withCommand([...command, ...args]).withCopyContentToContainer(files.map((file) => ({ ...file }))).withLogConsumer(events.consume);
|
|
4730
|
+
const container = await (capabilities === void 0 ? prober : prober.withAddedCapabilities(...capabilities)).start();
|
|
4096
4731
|
return {
|
|
4097
4732
|
events: events.seen,
|
|
4098
4733
|
until: (kinds, within) => events.until(kinds, within, "client"),
|
|
@@ -4193,6 +4828,7 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
4193
4828
|
client: {
|
|
4194
4829
|
kind: clientKind,
|
|
4195
4830
|
image: clientImage,
|
|
4831
|
+
...capabilities === void 0 ? {} : { capabilities },
|
|
4196
4832
|
...source === void 0 ? {} : {
|
|
4197
4833
|
source,
|
|
4198
4834
|
...buildOf(source)
|
|
@@ -6978,6 +7614,7 @@ const openKafkaLab = async (kafka) => {
|
|
|
6978
7614
|
const injectors = /* @__PURE__ */ new Map();
|
|
6979
7615
|
const parts = {};
|
|
6980
7616
|
const raw = {};
|
|
7617
|
+
const fences = [];
|
|
6981
7618
|
const openNode = async (name, environment) => {
|
|
6982
7619
|
let partial = "";
|
|
6983
7620
|
const box = await new GenericContainer(kafka.image).withNetwork(started).withNetworkAliases(name).withHostname(name).withEnvironment(environment).withWaitStrategy(Wait.forLogMessage(kafka.ready)).withStartupTimeout(18e4).withLogConsumer((stream) => stream.on("data", (chunk) => {
|
|
@@ -6988,6 +7625,7 @@ const openKafkaLab = async (kafka) => {
|
|
|
6988
7625
|
const tail = raw[name] ??= [];
|
|
6989
7626
|
tail.push(text);
|
|
6990
7627
|
if (tail.length > 400) tail.shift();
|
|
7628
|
+
if (isFenceLine(text)) fences.push(text);
|
|
6991
7629
|
}
|
|
6992
7630
|
})).start();
|
|
6993
7631
|
opened.push(box);
|
|
@@ -7059,6 +7697,7 @@ const openKafkaLab = async (kafka) => {
|
|
|
7059
7697
|
netns: { client: clientNetns },
|
|
7060
7698
|
kernel: records.seen,
|
|
7061
7699
|
raw,
|
|
7700
|
+
fences,
|
|
7062
7701
|
async sysctl(values) {
|
|
7063
7702
|
const settings = Object.entries(values).map(([key, value]) => `${key}=${value}`);
|
|
7064
7703
|
if (settings.length > 0) await output(admin, [
|
|
@@ -7086,11 +7725,46 @@ const openKafkaLab = async (kafka) => {
|
|
|
7086
7725
|
}
|
|
7087
7726
|
throw new Error(`the brokers did not come up in ${within} ms: ${last}`);
|
|
7088
7727
|
},
|
|
7089
|
-
async create(topic) {
|
|
7728
|
+
async create(topic, partitions = 1) {
|
|
7090
7729
|
const ids = kafka.brokers.map((name) => nodeIdOf(kafka, name)).join(",");
|
|
7091
|
-
const reply = await tool("create", topic, String(kafka.minInsync), ids);
|
|
7730
|
+
const reply = await tool("create", topic, String(kafka.minInsync), ids, String(partitions));
|
|
7092
7731
|
if (!reply.includes("\"ok\":true")) throw new Error(`the create of the topic failed: ${reply}`);
|
|
7093
7732
|
},
|
|
7733
|
+
async fill(topic, partitions, count) {
|
|
7734
|
+
const reply = await tool("fill", topic, String(partitions), String(count));
|
|
7735
|
+
if (!reply.includes("\"ok\":true")) throw new Error(`the fill of the topic failed: ${reply}`);
|
|
7736
|
+
},
|
|
7737
|
+
async consumer(args) {
|
|
7738
|
+
const events = lines();
|
|
7739
|
+
const container = await new GenericContainer(clientImage).withNetworkMode(`container:${holder.getId()}`).withCommand([
|
|
7740
|
+
"sh",
|
|
7741
|
+
"-c",
|
|
7742
|
+
"java \"$@\"; :",
|
|
7743
|
+
"consumer",
|
|
7744
|
+
CLIENT_HEAP,
|
|
7745
|
+
"-cp",
|
|
7746
|
+
CLASS_PATH,
|
|
7747
|
+
"ConsumerKafka",
|
|
7748
|
+
bootstrap,
|
|
7749
|
+
...args
|
|
7750
|
+
]).withLogConsumer(events.consume).start();
|
|
7751
|
+
const signaller = await withPid(new GenericContainer(image).withNetworkMode(`container:${holder.getId()}`).withAddedCapabilities("NET_ADMIN", "NET_RAW"), `container:${container.getId()}`).start();
|
|
7752
|
+
return {
|
|
7753
|
+
events: events.seen,
|
|
7754
|
+
until: (kinds, within) => events.until(kinds, within, "client"),
|
|
7755
|
+
signal: (signal, at) => stamped(signaller, [
|
|
7756
|
+
"/opt/harness/inject",
|
|
7757
|
+
"signal",
|
|
7758
|
+
"java",
|
|
7759
|
+
signal,
|
|
7760
|
+
...after(at)
|
|
7761
|
+
]),
|
|
7762
|
+
stop: async () => {
|
|
7763
|
+
await container.stop({ timeout: 2 }).catch(() => void 0);
|
|
7764
|
+
await signaller.stop(STOP).catch(() => void 0);
|
|
7765
|
+
}
|
|
7766
|
+
};
|
|
7767
|
+
},
|
|
7094
7768
|
async converge(topic, within = CONVERGE_MS) {
|
|
7095
7769
|
const begin = Date.now();
|
|
7096
7770
|
let calm = 0;
|
|
@@ -7342,6 +8016,105 @@ const kafkaOnce = async (lab, cell, environment, index) => {
|
|
|
7342
8016
|
}
|
|
7343
8017
|
};
|
|
7344
8018
|
};
|
|
8019
|
+
/** This count is the partitions of the topic of a run of a paused consumer: one for each member. */
|
|
8020
|
+
const PAUSE_PARTITIONS = 2;
|
|
8021
|
+
/** This time bounds the wait for one partition for each member and for two commits, in ms. It has no source: it is far above two batches and one rebalance. */
|
|
8022
|
+
const PAUSE_READY_MS = 9e4;
|
|
8023
|
+
/** This time bounds the wait for the next batch of the first member, in ms. It has no source: it is far above one batch. */
|
|
8024
|
+
const PAUSE_BATCH_MS = 3e4;
|
|
8025
|
+
/**
|
|
8026
|
+
* This function does one run of a paused consumer:
|
|
8027
|
+
* - The topic of the run has two partitions, each with records that a run does not finish.
|
|
8028
|
+
* - Two members join one group, and the run waits until each member holds one partition and the
|
|
8029
|
+
* first member has two commits on its partition.
|
|
8030
|
+
* - A SIGSTOP stops the first member while it holds its next batch, and a SIGCONT ends the stop
|
|
8031
|
+
* after the hold of the cell. The run then waits for the tail.
|
|
8032
|
+
*
|
|
8033
|
+
* The fault is null when the members did not get one partition each. The measure then voids the run.
|
|
8034
|
+
* The law reads no kernel record, thus a lost record does not void this run.
|
|
8035
|
+
*/
|
|
8036
|
+
const kafkaPauseOnce = async (lab, cell, environment, index) => {
|
|
8037
|
+
const pause = cell.kafka?.pause;
|
|
8038
|
+
if (pause === void 0) throw new Error("a run of a paused consumer needs `Kafka.pause`");
|
|
8039
|
+
if (cell.fault !== "freeze-process") throw new Error(`a paused consumer has no fault ${cell.fault}`);
|
|
8040
|
+
if (cell.hold === void 0) throw new Error("a paused consumer needs a hold: its law reads it");
|
|
8041
|
+
const hold = valueIn(cell.hold, "s");
|
|
8042
|
+
const topic = topicOf(environment, index);
|
|
8043
|
+
const group = `pause-e${environment}-r${index + 1}`;
|
|
8044
|
+
await lab.brokersUp();
|
|
8045
|
+
await lab.create(topic, PAUSE_PARTITIONS);
|
|
8046
|
+
const leader = await lab.converge(topic);
|
|
8047
|
+
await lab.fill(topic, PAUSE_PARTITIONS, pause.fill);
|
|
8048
|
+
await lab.forget();
|
|
8049
|
+
const from = lab.kernel.length;
|
|
8050
|
+
const lostBefore = droppedSoFar$1(lab.kernel);
|
|
8051
|
+
const common = [
|
|
8052
|
+
"--topic",
|
|
8053
|
+
topic,
|
|
8054
|
+
"--group",
|
|
8055
|
+
group,
|
|
8056
|
+
"--protocol",
|
|
8057
|
+
pause.protocol,
|
|
8058
|
+
"--max-s",
|
|
8059
|
+
String(Math.ceil(hold + 180)),
|
|
8060
|
+
...Object.entries(cell.driverOptions ?? {}).flatMap(([name, value]) => ["--set", `${name}=${String(value)}`])
|
|
8061
|
+
];
|
|
8062
|
+
const members = [];
|
|
8063
|
+
let fault = null;
|
|
8064
|
+
let heal = null;
|
|
8065
|
+
let otherEvents = [];
|
|
8066
|
+
let firstEvents = [];
|
|
8067
|
+
try {
|
|
8068
|
+
const first = await lab.consumer([
|
|
8069
|
+
...common,
|
|
8070
|
+
"--process-ms",
|
|
8071
|
+
String(valueIn(pause.process, "ms")),
|
|
8072
|
+
"--after-pause",
|
|
8073
|
+
pause.afterPause
|
|
8074
|
+
]);
|
|
8075
|
+
members.push(first);
|
|
8076
|
+
const other = await lab.consumer([
|
|
8077
|
+
...common,
|
|
8078
|
+
"--process-ms",
|
|
8079
|
+
"0",
|
|
8080
|
+
"--after-pause",
|
|
8081
|
+
"commit"
|
|
8082
|
+
]);
|
|
8083
|
+
members.push(other);
|
|
8084
|
+
firstEvents = first.events;
|
|
8085
|
+
otherEvents = other.events;
|
|
8086
|
+
const begun = Date.now();
|
|
8087
|
+
while (Date.now() - begun < PAUSE_READY_MS && !pauseReady(first.events, other.events)) await sleep$1(100);
|
|
8088
|
+
if (pauseReady(first.events, other.events)) {
|
|
8089
|
+
const seen = first.events.at(-1)?.t ?? 0;
|
|
8090
|
+
const batch = await waitFor(first, (event) => event.ev === "processing" && event.t > seen, PAUSE_BATCH_MS);
|
|
8091
|
+
if (batch !== void 0) {
|
|
8092
|
+
fault = await first.signal("STOP", batch.t + valueIn(pause.stopAfter, "s") * 1e9);
|
|
8093
|
+
heal = await first.signal("CONT", fault.t0 + hold * 1e9);
|
|
8094
|
+
await sleep$1(KAFKA_TAIL_MS);
|
|
8095
|
+
}
|
|
8096
|
+
}
|
|
8097
|
+
} finally {
|
|
8098
|
+
for (const member of members) await member.stop();
|
|
8099
|
+
}
|
|
8100
|
+
const settled = await lab.settle();
|
|
8101
|
+
return {
|
|
8102
|
+
environment,
|
|
8103
|
+
index,
|
|
8104
|
+
events: [...firstEvents],
|
|
8105
|
+
fault,
|
|
8106
|
+
...heal === null ? {} : { heal },
|
|
8107
|
+
kernel: lab.kernel.slice(from),
|
|
8108
|
+
netns: lab.netns.client,
|
|
8109
|
+
targetNetns: lab.members[leader].netns,
|
|
8110
|
+
lost: settled === null ? null : settled - lostBefore,
|
|
8111
|
+
pause: {
|
|
8112
|
+
other: [...otherEvents],
|
|
8113
|
+
fences: lab.fences.filter((line) => namesGroup(line, group)),
|
|
8114
|
+
group
|
|
8115
|
+
}
|
|
8116
|
+
};
|
|
8117
|
+
};
|
|
7345
8118
|
//#endregion
|
|
7346
8119
|
//#region src/run.ts
|
|
7347
8120
|
/**
|
|
@@ -7433,6 +8206,8 @@ const probeArgs = (cell, lab) => {
|
|
|
7433
8206
|
}
|
|
7434
8207
|
if (probe.keepalive) args.push("--keepalive");
|
|
7435
8208
|
if (probe.userTimeout !== void 0) args.push("--user-timeout-ms", String(valueIn(probe.userTimeout, "ms")));
|
|
8209
|
+
if (probe.plan !== void 0) args.push("--schedule", SCHEDULE_PATH);
|
|
8210
|
+
if (probe.streams !== void 0) args.push("--plan", LOAD_PATH);
|
|
7436
8211
|
args.push("--max-s", String(Math.ceil(longest * 2 + 10)));
|
|
7437
8212
|
return args;
|
|
7438
8213
|
};
|
|
@@ -7615,7 +8390,16 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
7615
8390
|
const middleboxFrom = lab.middlebox.length;
|
|
7616
8391
|
const serverFrom = lab.server.length;
|
|
7617
8392
|
const lostBefore = droppedSoFar(lab.kernel);
|
|
7618
|
-
const
|
|
8393
|
+
const plan = cell.probe.plan === void 0 ? void 0 : schedule(cell.probe.plan.profile, cell.probe.plan.options);
|
|
8394
|
+
const load = cell.probe.streams === void 0 ? void 0 : loadPlanOf(cell.probe.streams);
|
|
8395
|
+
const files = [...plan === void 0 ? [] : [{
|
|
8396
|
+
content: JSON.stringify(plan),
|
|
8397
|
+
target: SCHEDULE_PATH
|
|
8398
|
+
}], ...load === void 0 ? [] : [{
|
|
8399
|
+
content: load.content,
|
|
8400
|
+
target: LOAD_PATH
|
|
8401
|
+
}]];
|
|
8402
|
+
const probe = await lab.probe(probeArgs(cell, lab), files.length === 0 ? void 0 : files);
|
|
7619
8403
|
const server = cell.observer === "server";
|
|
7620
8404
|
const peer = server ? {
|
|
7621
8405
|
freeze: () => probe.freeze(),
|
|
@@ -7628,7 +8412,11 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
7628
8412
|
const last = probe.events.slice(-3).map((event) => JSON.stringify(event));
|
|
7629
8413
|
throw new Error(`${error.message}; the client's last lines: ${last.join(" ")}`);
|
|
7630
8414
|
});
|
|
7631
|
-
const placed =
|
|
8415
|
+
const placed = {
|
|
8416
|
+
...lab.topology === null ? {} : { topology: lab.topology },
|
|
8417
|
+
...plan === void 0 ? {} : { schedule: plan },
|
|
8418
|
+
...load === void 0 ? {} : { streams: load.streams }
|
|
8419
|
+
};
|
|
7632
8420
|
const fabricOf = () => {
|
|
7633
8421
|
if (lab.fabric === void 0) throw new Error(`${cell.name} silences a router of a lab with no fabric`);
|
|
7634
8422
|
return lab.fabric;
|
|
@@ -7773,7 +8561,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
7773
8561
|
fault = await apply();
|
|
7774
8562
|
try {
|
|
7775
8563
|
if (cell.idleBefore !== void 0 && cell.probe.mode !== "idle") await sleep(valueIn(cell.idleBefore, "ms"));
|
|
7776
|
-
if ((cell.probe.mode === "inflight" || cell.probe.mode === "stream") && !replyInFlight) await go();
|
|
8564
|
+
if ((cell.probe.mode === "inflight" || cell.probe.mode === "stream" || cell.probe.mode === "schedule") && !replyInFlight) await go();
|
|
7777
8565
|
const { prediction } = cell;
|
|
7778
8566
|
const http = cell.probe.http !== void 0;
|
|
7779
8567
|
if (prediction.detects !== null && prediction.driver !== void 0) {
|
|
@@ -7838,7 +8626,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
7838
8626
|
await lift();
|
|
7839
8627
|
if (toxicFirst) await lab.toxic(null);
|
|
7840
8628
|
}
|
|
7841
|
-
if (cell.fault === "freeze-process" && !server && cell.probe.mode !== "idle") await probe.until(["data"], 5e3).catch(() => void 0);
|
|
8629
|
+
if (cell.fault === "freeze-process" && !server && cell.probe.mode !== "idle" && cell.probe.mode !== "schedule") await probe.until(["data"], 5e3).catch(() => void 0);
|
|
7842
8630
|
const expected = cell.prediction.detects !== false ? cell.prediction.reconnects : void 0;
|
|
7843
8631
|
if (expected !== void 0) {
|
|
7844
8632
|
const waits = expected.waits.reduce((sum, wait) => sum + valueIn(wait.high, "s"), 0);
|
|
@@ -7918,9 +8706,11 @@ const inEnvironment = async (cell, environment, count, say) => {
|
|
|
7918
8706
|
if (latency !== null) await lab.netem(latency, "target");
|
|
7919
8707
|
await lab.warm();
|
|
7920
8708
|
const manifest = await lab.manifest(await lab.sysctl(cell.sysctls), settings, targetSysctls);
|
|
8709
|
+
const missing = await lab.namespaceSetup(cell.namespaceSetup ?? []).then(() => lab.hostSysctl(cell.hostSysctls ?? [])).then(() => void 0, (error) => error instanceof Error ? error.message : String(error));
|
|
7921
8710
|
const timed = (run) => ({
|
|
7922
8711
|
...run,
|
|
7923
|
-
jitter: jitterOf(cell, run)
|
|
8712
|
+
jitter: jitterOf(cell, run),
|
|
8713
|
+
...missing === void 0 ? {} : { missing }
|
|
7924
8714
|
});
|
|
7925
8715
|
const warmup = {
|
|
7926
8716
|
...timed(await withSteal(() => once(lab, cell, environment, -1, endsEnvironment(cell)))),
|
|
@@ -7933,8 +8723,12 @@ const inEnvironment = async (cell, environment, count, say) => {
|
|
|
7933
8723
|
runs.push(run);
|
|
7934
8724
|
say(line(cell, run, measure(cell, run)));
|
|
7935
8725
|
}
|
|
8726
|
+
const host = await lab.restoreHost();
|
|
7936
8727
|
return {
|
|
7937
|
-
manifest
|
|
8728
|
+
manifest: host.length === 0 ? manifest : {
|
|
8729
|
+
...manifest,
|
|
8730
|
+
host
|
|
8731
|
+
},
|
|
7938
8732
|
runs,
|
|
7939
8733
|
warmup
|
|
7940
8734
|
};
|
|
@@ -8064,16 +8858,17 @@ const inClusterEnvironment = async (cell, environment, count, say) => {
|
|
|
8064
8858
|
*/
|
|
8065
8859
|
const inKafkaEnvironment = async (cell, environment, count, say) => {
|
|
8066
8860
|
const lab = await openKafkaLab(cell.kafka);
|
|
8861
|
+
const once = cell.kafka.pause === void 0 ? kafkaOnce : kafkaPauseOnce;
|
|
8067
8862
|
try {
|
|
8068
8863
|
const manifest = await lab.manifest(await lab.sysctl(cell.sysctls));
|
|
8069
8864
|
const warmup = {
|
|
8070
|
-
...await
|
|
8865
|
+
...await once(lab, cell, environment, -1),
|
|
8071
8866
|
warmup: true
|
|
8072
8867
|
};
|
|
8073
8868
|
say(line(cell, warmup, measure(cell, warmup)));
|
|
8074
8869
|
const runs = [];
|
|
8075
8870
|
for (let index = 0; index < count; index += 1) {
|
|
8076
|
-
const run = await
|
|
8871
|
+
const run = await once(lab, cell, environment, index);
|
|
8077
8872
|
runs.push(run);
|
|
8078
8873
|
say(line(cell, run, measure(cell, run)));
|
|
8079
8874
|
}
|
|
@@ -8140,6 +8935,6 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
8140
8935
|
}
|
|
8141
8936
|
};
|
|
8142
8937
|
//#endregion
|
|
8143
|
-
export { AWS_FROM, CLIENT_NAMES, CLUSTER_STAGES, CONVERGE_MS, DAEMONS, DEBUG, DIRTY, DROP_HANDLING, FABRIC_CLIENT, FABRIC_DEVICE, FABRIC_TARGET, FRR_IMAGE, GIVES_AWAY, HASH_SCRIPT, INJECT, INSERT, KAFKA_TAIL_MS, KEPT, LINGER_MS, LISTING, LOCK_NAMES, LOSS_MARGIN, MEMBER_MARKS, MONITOR_DOWN, NODE_MARKS, NOISE, PINNED, PUMP, PUMP_SLACK, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, REPOSITORY, RESTART, ROUTERS, SEED_DOCUMENT, SHIPPED_CLIENTS, SHIPPED_NAMES, STAGES, STANDBY_READY, STATUS_SCRIPT, STEPS, STEP_DOWN, amqpClientImage, basesOf, bootstrapOf, brokerEnv, buildOf, buildRecordOf, callOf, cancelsOf, cargoVersions, clockOffsetOf, closeOrExit, cloudOf, clusterArgs, clusterConf, clusterCutOf, clusterHostsOf, clusterOnce, clusterStagesOf, clusterViewOf, commitProblems, comparable, controllerEnv, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, drillOnce, drillSpan, dropFinding, dropOf, endpointAfter, envOf, errorsOf, exitOf, fieldOf, fieldsOf, firstUnanswered, frrConf, goHttpImage, goVersions, groupOnce, groupVerdictOf, grpcClientImage, harnessImage, harnessRecord, harnessVersion, hashOf, highestHeld, hikariClientImage, hzOf, imageOf, imageProblems, inContext, infoOf, initiateScript, isHopPlace, jarVersions, jitterOf, jitterOfLines, judge, kafkaArgs, kafkaClientImage, kafkaCutOf, kafkaOnce, kafkaViewOf, lagsOf, laterMove, latestEndOf, leafLacks, leftAfter, locksOf, logNameOf, manifestProblems, matches, measure, measureDriver, measureGroup, measurePorts, measureQueue, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, monotonicAt, monotonicOf, nodeClientImage, nodeCommand, nodeHttpImage, nodeIdOf, nodeLineOf, nodeNameOf, nodesOnMonotonic, noiseOf, npmVersions, numbersOf, onMonotonic, openClusterLab, openFabric, openGroupLab, openKafkaLab, openLab, openReplicaSetLab, openStandby, pairArgs, pairOnce, pairingOf, pairingsOf, peersOf, pgClientImage, pingsOf, plainOf, planOf, programLines, programOf, quantileOf, readCpu, readHost, readOplog, reconnectFinding, reconnectOf, recordProblems, recoveryFindings, recoveryOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, restamped, routeLine, routerImage, routesOf, runCell, seedScript, sentinelConfig, sessionsOf, shippedClientRecord, shippedSource, sliceQuery, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, standaloneCommand, standbyCommand, statusViewOf, stealShare, stopScript, subjectClient, subjectProblems, subscriptionsOf, substrateHere, substrateKey, substrateOf, substrateOn, targetImageOf, targetSourceOf, templated, toolJsonOf, topicOf, topologyOf, toxicBody, unlockedManifests, unpinnedBases, unpoolable, verdictOf, versionsOf, viewOf, writesOf };
|
|
8938
|
+
export { AWS_FROM, CLIENT_CAPABILITIES, CLIENT_NAMES, CLUSTER_STAGES, CONVERGE_MS, DAEMONS, DEBUG, DIRTY, DROP_HANDLING, FABRIC_CLIENT, FABRIC_DEVICE, FABRIC_TARGET, FRR_IMAGE, GIVES_AWAY, HASH_SCRIPT, INJECT, INSERT, KAFKA_TAIL_MS, KEPT, LINGER_MS, LISTING, LOAD_FORMAT, LOAD_PATH, LOCK_NAMES, LOSS_MARGIN, MEMBER_MARKS, MONITOR_DOWN, NODE_MARKS, NOISE, PINNED, PUMP, PUMP_SLACK, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, REPOSITORY, RESTART, ROUTERS, SCHEDULE_PATH, SEED_DOCUMENT, SHIPPED_CLIENTS, SHIPPED_NAMES, STAGES, STANDBY_READY, STATUS_SCRIPT, STEPS, STEP_DOWN, amqpClientImage, basesOf, bootstrapOf, brokerEnv, buildOf, buildRecordOf, callOf, cancelsOf, cargoVersions, clockOffsetOf, closeOrExit, cloudOf, clusterArgs, clusterConf, clusterCutOf, clusterHostsOf, clusterOnce, clusterStagesOf, clusterViewOf, commitProblems, comparable, controllerEnv, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, drillOnce, drillSpan, dropFinding, dropOf, endpointAfter, envOf, errorsOf, exitOf, fieldOf, fieldsOf, firstUnanswered, frrConf, goHttpImage, goVersions, groupOnce, groupVerdictOf, grpcClientImage, harnessImage, harnessRecord, harnessVersion, hashOf, highestHeld, hikariClientImage, hzOf, imageOf, imageProblems, inContext, infoOf, initiateScript, isFenceLine, isHopPlace, jarVersions, jitterOf, jitterOfLines, judge, kafkaArgs, kafkaClientImage, kafkaCutOf, kafkaOnce, kafkaPauseOnce, kafkaViewOf, lagsOf, laterMove, latestEndOf, leafLacks, leftAfter, loadOf, loadPlanOf, loadVerdict, locksOf, logNameOf, logTimeOf, manifestProblems, matches, measure, measureConntrack, measureDriver, measureGroup, measurePause, measurePorts, measureQueue, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, monotonicAt, monotonicOf, namesGroup, nodeClientImage, nodeCommand, nodeHttpImage, nodeIdOf, nodeLineOf, nodeNameOf, nodesOnMonotonic, noiseOf, npmVersions, numbersOf, onMonotonic, openClusterLab, openFabric, openGroupLab, openKafkaLab, openLab, openReplicaSetLab, openStandby, pairArgs, pairOnce, pairingOf, pairingsOf, pauseReady, peersOf, pgClientImage, pingsOf, plainOf, planOf, programLines, programOf, quantileOf, readCpu, readHost, readOplog, reconnectFinding, reconnectOf, recordProblems, recoveryFindings, recoveryOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, restamped, routeLine, routerImage, routesOf, runCell, seedScript, sentinelConfig, sessionsOf, shippedClientRecord, shippedSource, sliceQuery, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, standaloneCommand, standbyCommand, statusViewOf, stealShare, stopScript, streamsLoadOf, subjectClient, subjectProblems, subscriptionsOf, substrateHere, substrateKey, substrateOf, substrateOn, targetImageOf, targetSourceOf, templated, toolJsonOf, topicOf, topologyOf, toxicBody, unlockedManifests, unpinnedBases, unpoolable, verdictOf, versionsOf, viewOf, writesOf };
|
|
8144
8939
|
|
|
8145
8940
|
//# sourceMappingURL=index.js.map
|