@mark1russell7/kind 0.12.1 → 0.12.3
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/build/index.d.ts +2 -2
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/{judge-BdykXjF2.js → judge-2EZajeXa.js} +147 -10
- package/build/judge-2EZajeXa.js.map +1 -0
- package/build/{judge-Cnn42gih.d.ts → judge-C8cajM_k.d.ts} +79 -2
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/package.json +1 -1
- package/build/judge-BdykXjF2.js.map +0 -1
|
@@ -23,6 +23,33 @@ const showBand = (band) => {
|
|
|
23
23
|
const { lo, hi } = secondsOf(band);
|
|
24
24
|
return `[${lo}, ${hi}] s`;
|
|
25
25
|
};
|
|
26
|
+
/** This function rounds a time to the microsecond, thus a band has no float residue. */
|
|
27
|
+
const us = (value) => Math.round(value * 1e6) / 1e6;
|
|
28
|
+
/**
|
|
29
|
+
* This function gives the end band of a window with the backlog term (`BacklogTerm`). It adds n × d
|
|
30
|
+
* to each end of `end`, the end band without the term. The value n is the sessions on the target.
|
|
31
|
+
*/
|
|
32
|
+
const backlogEnd = (end, term, sessions) => {
|
|
33
|
+
const ends = secondsOf(end);
|
|
34
|
+
const drain = secondsOf(term.drain);
|
|
35
|
+
return bandOf(us(ends.lo + sessions * drain.lo), us(ends.hi + sessions * drain.hi));
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* This function gives the failed requests that the backlog term predicts (`BacklogTerm`). Each of
|
|
39
|
+
* the n sessions on the target gets a request each turn. The turn is the measured mean period of a
|
|
40
|
+
* client (`mean`) times the sessions of the client (`connections`). The requests of a session fail
|
|
41
|
+
* from the start of the window to the band end (`span`). That span holds `span / turn` requests, one
|
|
42
|
+
* less at its low edge and one more at its high edge. Each session fails r more requests in its drain time.
|
|
43
|
+
*/
|
|
44
|
+
const requestBand = (term, span, mean, connections, sessions) => {
|
|
45
|
+
const turn = valueIn(mean, "s") * connections;
|
|
46
|
+
if (!(turn > 0)) throw new RangeError(`a turn of ${turn} s cannot count requests`);
|
|
47
|
+
const each = Math.round(valueIn(span, "s") / turn * 1e6) / 1e6;
|
|
48
|
+
return {
|
|
49
|
+
lo: sessions * (Math.floor(each) - 1 + term.requests.lo),
|
|
50
|
+
hi: sessions * (Math.ceil(each) + 1 + term.requests.hi)
|
|
51
|
+
};
|
|
52
|
+
};
|
|
26
53
|
//#endregion
|
|
27
54
|
//#region src/names.ts
|
|
28
55
|
/** These are the ports of the app: its server, and its health server for the probes. */
|
|
@@ -97,6 +124,48 @@ const sessionsAt = (lines, at, target, connections) => {
|
|
|
97
124
|
byClient
|
|
98
125
|
};
|
|
99
126
|
};
|
|
127
|
+
const medianOf = (sorted) => {
|
|
128
|
+
const middle = Math.floor(sorted.length / 2);
|
|
129
|
+
return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* This function gives the measured period of the clients from their request lines that start from
|
|
133
|
+
* `from` to `to`. The timer of a client is late by a different amount at each tick. Thus the median
|
|
134
|
+
* stays near the setting of the client (`every`), but the lateness adds up in the mean.
|
|
135
|
+
*
|
|
136
|
+
* The mean is the span of the requests of each client over its intervals. A band can count the requests of a client in a window from the
|
|
137
|
+
* mean. A client with fewer than 2 requests gives no interval. A run with no interval gives null.
|
|
138
|
+
*/
|
|
139
|
+
const periodOf = (lines, from, to) => {
|
|
140
|
+
const starts = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const request of requestsOf(lines)) {
|
|
142
|
+
if (request.t < from || request.t > to) continue;
|
|
143
|
+
const mine = starts.get(request.p) ?? [];
|
|
144
|
+
mine.push(request.t);
|
|
145
|
+
starts.set(request.p, mine);
|
|
146
|
+
}
|
|
147
|
+
const all = [];
|
|
148
|
+
const byClient = {};
|
|
149
|
+
let span = 0;
|
|
150
|
+
for (const client of [...starts.keys()].sort()) {
|
|
151
|
+
const mine = starts.get(client).sort((a, b) => a - b);
|
|
152
|
+
if (mine.length < 2) continue;
|
|
153
|
+
const intervals = mine.slice(1).map((t, at) => t - mine[at]);
|
|
154
|
+
span += mine.at(-1) - mine[0];
|
|
155
|
+
all.push(...intervals);
|
|
156
|
+
byClient[client] = medianOf(intervals.sort((a, b) => a - b));
|
|
157
|
+
}
|
|
158
|
+
if (all.length === 0) return null;
|
|
159
|
+
all.sort((a, b) => a - b);
|
|
160
|
+
return {
|
|
161
|
+
median: medianOf(all),
|
|
162
|
+
mean: span / all.length,
|
|
163
|
+
low: all[0],
|
|
164
|
+
high: all.at(-1),
|
|
165
|
+
intervals: all.length,
|
|
166
|
+
byClient
|
|
167
|
+
};
|
|
168
|
+
};
|
|
100
169
|
/** This function gives the requests of each node, by the name of the node. */
|
|
101
170
|
const byNode = (requests) => {
|
|
102
171
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -385,7 +454,8 @@ const windowOf = (prediction) => prediction.kind === "window" ? prediction : pre
|
|
|
385
454
|
const observationAfter = (cell) => {
|
|
386
455
|
const window = windowOf(cell.prediction);
|
|
387
456
|
if (window === null || window.end === null) return null;
|
|
388
|
-
|
|
457
|
+
const drained = window.backlog === void 0 ? 0 : window.backlog.most * valueIn(window.backlog.drain.hi, "ms");
|
|
458
|
+
return seconds(sec(valueIn(window.end.band.hi, "ms") + drained + valueIn(cell.workload.timeout, "ms") + NOISE));
|
|
389
459
|
};
|
|
390
460
|
/**
|
|
391
461
|
* This function gives the earliest end of the observation of a run, as a stamp, or null when its
|
|
@@ -424,6 +494,7 @@ const factsOf = (cell, run) => {
|
|
|
424
494
|
const intake = referenceOf(run, "intake");
|
|
425
495
|
const crash = cell.trigger.kind === "crash" ? run.acts.fault : null;
|
|
426
496
|
const from = deletion ?? crash;
|
|
497
|
+
const period = periodOf(lines, run.start, run.end);
|
|
427
498
|
return {
|
|
428
499
|
failures: failures.length,
|
|
429
500
|
outcomes: outcomesOf(failures),
|
|
@@ -443,9 +514,21 @@ const factsOf = (cell, run) => {
|
|
|
443
514
|
healUpdates: updatesOf(run, run.acts.heal)
|
|
444
515
|
} : {},
|
|
445
516
|
...cell.workload.connections !== void 0 && run.target !== null && run.acts.fault !== null ? { sessions: sessionsAt(lines, run.acts.fault, run.target.pod, cell.workload.connections) } : {},
|
|
446
|
-
...cell.trigger.kind === "app-stall" ? { stall: stallFactOf(run) } : {}
|
|
517
|
+
...cell.trigger.kind === "app-stall" ? { stall: stallFactOf(run) } : {},
|
|
518
|
+
...period === null ? {} : { period: periodFact(period) }
|
|
447
519
|
};
|
|
448
520
|
};
|
|
521
|
+
/** This function gives a time in milliseconds in seconds, to the microsecond. A period of a client is approximately 10 ms, thus a whole millisecond is too coarse. */
|
|
522
|
+
const fine = (ms) => seconds(Math.round(ms * 1e3) / 1e6);
|
|
523
|
+
/** This function gives the period of the clients (`periodOf`) with each time in seconds. */
|
|
524
|
+
const periodFact = (period) => ({
|
|
525
|
+
median: fine(period.median),
|
|
526
|
+
mean: fine(period.mean),
|
|
527
|
+
low: fine(period.low),
|
|
528
|
+
high: fine(period.high),
|
|
529
|
+
intervals: period.intervals,
|
|
530
|
+
byClient: Object.fromEntries(Object.entries(period.byClient).map(([client, ms]) => [client, fine(ms)]))
|
|
531
|
+
});
|
|
449
532
|
/**
|
|
450
533
|
* This function gives the stall of the target in a run. It is the start of its line `stall` after the
|
|
451
534
|
* fault, and the time that the app gives in its end line (`held`). A time that the run does not have is null.
|
|
@@ -530,6 +613,27 @@ const removalOfCycles = (cell, run, prediction, done) => {
|
|
|
530
613
|
tail: inner.tail
|
|
531
614
|
};
|
|
532
615
|
};
|
|
616
|
+
/**
|
|
617
|
+
* This function counts the failed requests of a run with the backlog term (`requestBand`). The span
|
|
618
|
+
* is from the start of the window to the end band without the term. The turn of a session comes
|
|
619
|
+
* from the measured mean period of the clients. A run with no measured period cannot be counted.
|
|
620
|
+
*/
|
|
621
|
+
const countOf = (cell, window, term, onTarget, period, failed) => {
|
|
622
|
+
if (period === void 0) return {
|
|
623
|
+
ok: false,
|
|
624
|
+
why: "the run has no measured period, thus the backlog term cannot count its failed requests"
|
|
625
|
+
};
|
|
626
|
+
const span = seconds(Math.round((valueIn(window.end.band.hi, "s") - valueIn(window.start.band.lo, "s")) * 1e6) / 1e6);
|
|
627
|
+
const band = requestBand(term, span, period.mean, cell.workload.connections ?? 1, onTarget);
|
|
628
|
+
const at = `the count [${band.lo}, ${band.hi}] of ${onTarget} session${onTarget === 1 ? "" : "s"} on the target at a mean period of ${Math.round(valueIn(period.mean, "ms") * 1e3) / 1e3} ms`;
|
|
629
|
+
return failed >= band.lo && failed <= band.hi ? {
|
|
630
|
+
ok: true,
|
|
631
|
+
why: `in ${at}`
|
|
632
|
+
} : {
|
|
633
|
+
ok: false,
|
|
634
|
+
why: `${failed} failed requests, outside ${at}`
|
|
635
|
+
};
|
|
636
|
+
};
|
|
533
637
|
/** This function judges one run against the prediction of its cell. */
|
|
534
638
|
const checkRun = (cell, run) => {
|
|
535
639
|
const lines = run.lines;
|
|
@@ -559,7 +663,10 @@ const checkRun = (cell, run) => {
|
|
|
559
663
|
case "window": {
|
|
560
664
|
const each = prediction.each === true;
|
|
561
665
|
const opens = secondsOf(prediction.start.band);
|
|
562
|
-
const
|
|
666
|
+
const backlog = prediction.backlog;
|
|
667
|
+
const onTarget = facts.sessions?.onTarget;
|
|
668
|
+
if (backlog !== void 0 && (prediction.end === null || prediction.end.from !== prediction.start.from || onTarget === void 0)) return done(false, onTarget === void 0 ? "the run has no sessions on the target at the fault, thus the backlog term has no n" : "a window with the backlog term has an end with the reference of its start", null);
|
|
669
|
+
const closes = prediction.end === null ? null : secondsOf(backlog === void 0 ? prediction.end.band : backlogEnd(prediction.end.band, backlog, onTarget));
|
|
563
670
|
const droppedAfter = (origin, propagation) => (node) => {
|
|
564
671
|
const propagated = propagation[node];
|
|
565
672
|
return origin === null || propagated === void 0 || propagated === null ? null : origin + valueIn(propagated.by, "ms");
|
|
@@ -653,6 +760,8 @@ const checkRun = (cell, run) => {
|
|
|
653
760
|
if (others.length > 0) problems.push(`${others.length} failed requests on nodes whose kube-proxy did not pause`);
|
|
654
761
|
if (wrong.length > 0) problems.push(`${wrong.length} failed requests in the window ended ${JSON.stringify(outcomesOf(wrong))}, not ${allowed.join(", ")}`);
|
|
655
762
|
if (prediction.certain && relevant.length === 0) problems.push("no request failed in a window that the law opens");
|
|
763
|
+
const counted = backlog === void 0 ? null : countOf(cell, prediction, backlog, onTarget, facts.period, relevant.length);
|
|
764
|
+
if (counted !== null && !counted.ok) problems.push(counted.why);
|
|
656
765
|
const final = windows.reduce((a, b) => (b.end ?? Number.NEGATIVE_INFINITY) > (a.end ?? Number.NEGATIVE_INFINITY) ? b : a);
|
|
657
766
|
if (flaps && prediction.certain && relevant.length > 0) windows.forEach((window, k) => {
|
|
658
767
|
const own = relevant.filter((request) => request.t >= window.lo && request.t <= window.hi);
|
|
@@ -670,7 +779,7 @@ const checkRun = (cell, run) => {
|
|
|
670
779
|
const inside = relevant.filter((request) => !late.includes(request));
|
|
671
780
|
const span = inside.length === 0 ? 0 : sec(inside.at(-1).t - inside[0].t);
|
|
672
781
|
const where = each ? `the windows of ${windows.length} deleted replicas` : flaps ? `the windows of ${windows.length} cycles` : "the window";
|
|
673
|
-
const agrees = reused.length === 0 ? `${relevant.length} failed requests, all in ${where} (span ${span} s)` : `${inside.length} failed requests in ${where} (span ${span} s), and ${reused.length} on a port that a reset closed
|
|
782
|
+
const agrees = (reused.length === 0 ? `${relevant.length} failed requests, all in ${where} (span ${span} s)` : `${inside.length} failed requests in ${where} (span ${span} s), and ${reused.length} on a port that a reset closed`) + (counted === null ? "" : `, ${counted.why}`);
|
|
674
783
|
const checked = done(problems.length === 0, problems.length === 0 ? agrees : problems.join("; "), relevant.length);
|
|
675
784
|
if (problems.length > 0) return checked;
|
|
676
785
|
const notes = [];
|
|
@@ -1100,7 +1209,7 @@ const upgradeTrigger = (trigger) => {
|
|
|
1100
1209
|
};
|
|
1101
1210
|
};
|
|
1102
1211
|
const upgradeWindow = (window) => {
|
|
1103
|
-
const { afterReset, propagation, ...rest } = window;
|
|
1212
|
+
const { afterReset, propagation, backlog, ...rest } = window;
|
|
1104
1213
|
return {
|
|
1105
1214
|
...rest,
|
|
1106
1215
|
start: {
|
|
@@ -1115,7 +1224,11 @@ const upgradeWindow = (window) => {
|
|
|
1115
1224
|
life: seconds(afterReset.life),
|
|
1116
1225
|
outcome: afterReset.outcome
|
|
1117
1226
|
} },
|
|
1118
|
-
...propagation === void 0 ? {} : { propagation: band(propagation) }
|
|
1227
|
+
...propagation === void 0 ? {} : { propagation: band(propagation) },
|
|
1228
|
+
...backlog === void 0 ? {} : { backlog: {
|
|
1229
|
+
...backlog,
|
|
1230
|
+
drain: band(backlog.drain)
|
|
1231
|
+
} }
|
|
1119
1232
|
};
|
|
1120
1233
|
};
|
|
1121
1234
|
const upgradePrediction = (prediction) => {
|
|
@@ -1165,7 +1278,7 @@ const upgradeFacts = (facts) => {
|
|
|
1165
1278
|
by: seconds(old.by)
|
|
1166
1279
|
};
|
|
1167
1280
|
const byKey = (record) => Object.fromEntries(Object.entries(record).map(([key, old]) => [key, propagated(old)]));
|
|
1168
|
-
const { sliceEmpty, rules, left, signals, updates, healUpdates, closes, cycles, stall, ...rest } = facts;
|
|
1281
|
+
const { sliceEmpty, rules, left, signals, updates, healUpdates, closes, cycles, stall, period, ...rest } = facts;
|
|
1169
1282
|
return {
|
|
1170
1283
|
...rest,
|
|
1171
1284
|
intakeAfterDeletion: time$1(facts.intakeAfterDeletion),
|
|
@@ -1185,6 +1298,14 @@ const upgradeFacts = (facts) => {
|
|
|
1185
1298
|
...stall === void 0 ? {} : { stall: {
|
|
1186
1299
|
start: time$1(stall.start),
|
|
1187
1300
|
held: time$1(stall.held)
|
|
1301
|
+
} },
|
|
1302
|
+
...period === void 0 ? {} : { period: {
|
|
1303
|
+
...period,
|
|
1304
|
+
median: seconds(period.median),
|
|
1305
|
+
mean: seconds(period.mean),
|
|
1306
|
+
low: seconds(period.low),
|
|
1307
|
+
high: seconds(period.high),
|
|
1308
|
+
byClient: Object.fromEntries(Object.entries(period.byClient).map(([client, old]) => [client, seconds(old)]))
|
|
1188
1309
|
} }
|
|
1189
1310
|
};
|
|
1190
1311
|
};
|
|
@@ -1255,7 +1376,7 @@ const atBand = (from, band) => {
|
|
|
1255
1376
|
const predictionText = (prediction) => {
|
|
1256
1377
|
switch (prediction.kind) {
|
|
1257
1378
|
case "none": return prediction.outcomes === void 0 ? "no failure" : `no ${prediction.outcomes.join(", ")}`;
|
|
1258
|
-
case "window": return `failures only from ${atBand(prediction.start.from, prediction.start.band)}${prediction.end === null ? "" : ` to ${atBand(prediction.end.from, prediction.end.band)}`}${prediction.each === true ? ", of each deleted replica" : ""}${prediction.flaps === true ? ", in each cycle" : ""}${prediction.nodes === "paused" ? ", on the paused node" : ""}${prediction.certain ? ", at least one" : ""}${prediction.outcomes === void 0 ? "" : `, each one ${prediction.outcomes.join(" or ")}`}${prediction.afterReset === void 0 ? "" : `, or a ${prediction.afterReset.outcome} up to ${format(prediction.afterReset.life)} after a reset on its port`}${prediction.propagation === void 0 ? "" : `, the rules drop the replica in ${showBand(prediction.propagation)}`}`;
|
|
1379
|
+
case "window": return `failures only from ${atBand(prediction.start.from, prediction.start.band)}${prediction.end === null ? "" : ` to ${atBand(prediction.end.from, prediction.end.band)}`}${prediction.each === true ? ", of each deleted replica" : ""}${prediction.flaps === true ? ", in each cycle" : ""}${prediction.nodes === "paused" ? ", on the paused node" : ""}${prediction.certain ? ", at least one" : ""}${prediction.outcomes === void 0 ? "" : `, each one ${prediction.outcomes.join(" or ")}`}${prediction.afterReset === void 0 ? "" : `, or a ${prediction.afterReset.outcome} up to ${format(prediction.afterReset.life)} after a reset on its port`}${prediction.propagation === void 0 ? "" : `, the rules drop the replica in ${showBand(prediction.propagation)}`}${prediction.backlog === void 0 ? "" : `, the end n × ${showBand(prediction.backlog.drain)} later and ${prediction.backlog.requests.lo}–${prediction.backlog.requests.hi} more failures for each of the n sessions on the target, counted at the measured period`}`;
|
|
1259
1380
|
case "removal": return `Service empty in ${showBand(prediction.band)}${prediction.flaps === true ? " after each fault, and ready again before the next" : ""}${prediction.window === void 0 ? "" : `; ${predictionText(prediction.window)}`}`;
|
|
1260
1381
|
case "leaves": return `the replica leaves in ${showBand(prediction.band)}${prediction.window === void 0 ? "" : `; ${predictionText(prediction.window)}`}`;
|
|
1261
1382
|
case "restarts": return prediction.band === null ? "no restart" : `each replica SIGTERM in ${showBand(prediction.band)}`;
|
|
@@ -1555,6 +1676,14 @@ const recordOf = (a, main) => {
|
|
|
1555
1676
|
propagation: band.optional(),
|
|
1556
1677
|
flaps: z.boolean().optional(),
|
|
1557
1678
|
outcomes: z.array(z.string()).optional(),
|
|
1679
|
+
backlog: z.object({
|
|
1680
|
+
drain: band,
|
|
1681
|
+
requests: z.object({
|
|
1682
|
+
lo: count,
|
|
1683
|
+
hi: count
|
|
1684
|
+
}),
|
|
1685
|
+
most: count
|
|
1686
|
+
}).optional(),
|
|
1558
1687
|
why: z.string()
|
|
1559
1688
|
});
|
|
1560
1689
|
const none = z.object({
|
|
@@ -1710,6 +1839,14 @@ const recordOf = (a, main) => {
|
|
|
1710
1839
|
stall: z.object({
|
|
1711
1840
|
start: a.nullable(),
|
|
1712
1841
|
held: a.nullable()
|
|
1842
|
+
}).optional(),
|
|
1843
|
+
period: z.object({
|
|
1844
|
+
median: a,
|
|
1845
|
+
mean: a,
|
|
1846
|
+
low: a,
|
|
1847
|
+
high: a,
|
|
1848
|
+
intervals: count,
|
|
1849
|
+
byClient: z.record(z.string(), a)
|
|
1713
1850
|
}).optional()
|
|
1714
1851
|
});
|
|
1715
1852
|
return {
|
|
@@ -1772,6 +1909,6 @@ const readRecord = (data) => {
|
|
|
1772
1909
|
throw new Error(`a kind cell record has the format ${FORMAT} or ${FORMAT_V1}, not ${String(format)}`);
|
|
1773
1910
|
};
|
|
1774
1911
|
//#endregion
|
|
1775
|
-
export {
|
|
1912
|
+
export { restartsAt as $, RELATIVE as A, dropOf as B, observationEnd as C, requestBand as Ct, reusesReset as D, referenceOf as E, showBand as Et, TCP_STATE as F, kernelCounts as G, failuresOf as H, backendsAt as I, podsAt as J, outcomesOf as K, byClients as L, middle as M, quantileOf as N, unitOf as O, HBONE_PORT as P, restartOf as Q, byNode as R, observationAfter as S, bandOf as St, propagationSource as T, secondsOf as Tt, intakeOf as U, emptyOf as V, isTunnelState as W, recordsOf as X, podsOf as Y, requestsOf as Z, checkRun as _, LOG_LABEL as _t, calibrationOf as a, sliceEmptyOf as at, factsOf as b, SERVER as bt, predictionText as c, slicesOf as ct, FORMAT_V1 as d, transitionOf as dt, rulesOf as et, judge as f, tunnelsOf as ft, LAST_SLACK as g, HEALTH_PORT as gt, KIND_NOISE as h, CLIENT as ht, readRecord as i, sliceDropOf as it, medianInterval as j, updatesOf as k, timeSpread as l, socketsOf as lt, upgradeCell as m, ztunnelReplaced as mt, kindCellRecordSchema as n, signalOf as nt, cellsTable as o, sliceReadyAt as ot, upgrade as p, ztunnelPodsOf as pt, periodOf as q, kindCellRecordV1Schema as r, signalsOf as rt, medianText as s, sliceReadyOf as st, STAGES as t, sessionsAt as tt, FORMAT as u, stoppingOf as ut, cyclesOf as v, PORT as vt, propagationOf as w, seconds as wt, judgeKind as x, backlogEnd as xt, deletionsOf as y, REDIS as yt, deletionOf as z };
|
|
1776
1913
|
|
|
1777
|
-
//# sourceMappingURL=judge-
|
|
1914
|
+
//# sourceMappingURL=judge-2EZajeXa.js.map
|