@mark1russell7/kind 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/build/index.d.ts +5 -4
- package/build/index.js +60 -5
- package/build/index.js.map +1 -1
- package/build/{judge-BS9pk96o.d.ts → judge-B9kVpFE-.d.ts} +66 -29
- package/build/{judge-Bkbs7VVG.js → judge-DNCI5vvt.js} +170 -57
- package/build/judge-DNCI5vvt.js.map +1 -0
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/image/Dockerfile +1 -1
- package/image/client-grpc.mjs +9 -0
- package/package.json +2 -1
- package/src/client-lines.ts +60 -0
- package/build/judge-Bkbs7VVG.js.map +0 -1
|
@@ -118,16 +118,17 @@ type Stack = "service" | "grpc";
|
|
|
118
118
|
* - `rollout` is a rolling update while Redis is down.
|
|
119
119
|
* - `cpu-throttle` sets `cpu.max` in the cgroup of one replica.
|
|
120
120
|
* - `authorization-policy` applies an AuthorizationPolicy of Istio that denies the clients, for the hold (T15).
|
|
121
|
+
* - `idle` stops the calls of each client with SIGUSR1, and starts them again with SIGUSR2 after the hold: a gap (T15).
|
|
121
122
|
* - `none` injects nothing: a calibration measures the path itself.
|
|
122
123
|
*/
|
|
123
|
-
type TriggerKind = "pod-deletion" | "rolling-update" | "node-drain" | "scale-in" | "crash" | "liveness-restart" | "freeze-process" | "rst" | "blackhole" | "peer-gone-without-close" | "app-stall" | "rollout" | "cpu-throttle" | "authorization-policy";
|
|
124
|
+
type TriggerKind = "pod-deletion" | "rolling-update" | "node-drain" | "scale-in" | "crash" | "liveness-restart" | "freeze-process" | "rst" | "blackhole" | "peer-gone-without-close" | "app-stall" | "rollout" | "cpu-throttle" | "authorization-policy" | "idle";
|
|
124
125
|
/**
|
|
125
126
|
* A trigger is its kind and the settings of its fault. The parameter `A` is the form of an amount:
|
|
126
127
|
* a quantity in the format `/2`, a number in the format `/1`.
|
|
127
128
|
*/
|
|
128
129
|
interface Trigger<A = Quantity> {
|
|
129
130
|
readonly kind: TriggerKind;
|
|
130
|
-
/** This time is how long a fault holds before it ends: a stall, a pause of kube-proxy, a black hole
|
|
131
|
+
/** This time is how long a fault holds before it ends: a stall, a pause of kube-proxy, a black hole, or a gap. */
|
|
131
132
|
readonly hold?: Quantity | undefined;
|
|
132
133
|
/** This time is how long before the deletion a pause of kube-proxy starts (T07 `freeze-process`). */
|
|
133
134
|
readonly lead?: Quantity | undefined;
|
|
@@ -245,6 +246,9 @@ interface ResetReuse<A = Quantity> {
|
|
|
245
246
|
* - `restarts`: each replica receives SIGTERM in the band, or no replica does.
|
|
246
247
|
* - `stays`: no endpoint leaves the Service, and no request is refused.
|
|
247
248
|
* - `stalls`: the rollout does not continue while Redis is down, and it ends after Redis is back.
|
|
249
|
+
* - `ends`: the socket of each client ends in the band after the reference `from`. With no band, no
|
|
250
|
+
* socket of a client ends in the run. Then the first request of each client after the heal goes on
|
|
251
|
+
* the socket of its last request before the fault. With `window`, the failed requests also start in that window.
|
|
248
252
|
* - `measured`: no document bounds the outcome. The run keeps the measurement.
|
|
249
253
|
*/
|
|
250
254
|
type Prediction<A = Quantity> = {
|
|
@@ -252,7 +256,42 @@ type Prediction<A = Quantity> = {
|
|
|
252
256
|
/** These outcomes are the failures that the prediction excludes. Without them, it excludes each outcome but `ok`. */
|
|
253
257
|
readonly outcomes?: readonly string[] | undefined;
|
|
254
258
|
readonly why: string;
|
|
259
|
+
} | WindowPrediction<A> | {
|
|
260
|
+
readonly kind: "removal";
|
|
261
|
+
readonly band: Band<A>;
|
|
262
|
+
readonly why: string;
|
|
263
|
+
} | {
|
|
264
|
+
readonly kind: "leaves";
|
|
265
|
+
readonly band: Band<A>;
|
|
266
|
+
readonly why: string;
|
|
267
|
+
} | {
|
|
268
|
+
readonly kind: "restarts";
|
|
269
|
+
readonly band: Band<A> | null;
|
|
270
|
+
readonly why: string;
|
|
271
|
+
} | {
|
|
272
|
+
readonly kind: "stays";
|
|
273
|
+
readonly why: string;
|
|
255
274
|
} | {
|
|
275
|
+
readonly kind: "stalls";
|
|
276
|
+
readonly why: string;
|
|
277
|
+
} | {
|
|
278
|
+
readonly kind: "ends";
|
|
279
|
+
/** This reference is where the band of each close starts. */
|
|
280
|
+
readonly from: Reference;
|
|
281
|
+
/** This band is the close of the socket of each client after `from`, or null when no socket of a client ends. */
|
|
282
|
+
readonly band: Band<A> | null;
|
|
283
|
+
/** This window is where the failed requests start, or none when the prediction does not test them. */
|
|
284
|
+
readonly window?: WindowPrediction<A> | undefined;
|
|
285
|
+
readonly why: string;
|
|
286
|
+
} | {
|
|
287
|
+
readonly kind: "measured";
|
|
288
|
+
readonly why: string;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* A window prediction is the requests that fail in a window. The window starts at `start` and ends in
|
|
292
|
+
* `end`. With `certain`, at least one request fails.
|
|
293
|
+
*/
|
|
294
|
+
interface WindowPrediction<A = Quantity> {
|
|
256
295
|
readonly kind: "window";
|
|
257
296
|
readonly nodes: "all" | "paused";
|
|
258
297
|
readonly start: {
|
|
@@ -284,28 +323,7 @@ type Prediction<A = Quantity> = {
|
|
|
284
323
|
/** These outcomes are the only failures that the window permits. A failure with another outcome fails the run. Without them, each outcome but `ok` can fail. */
|
|
285
324
|
readonly outcomes?: readonly string[] | undefined;
|
|
286
325
|
readonly why: string;
|
|
287
|
-
}
|
|
288
|
-
readonly kind: "removal";
|
|
289
|
-
readonly band: Band<A>;
|
|
290
|
-
readonly why: string;
|
|
291
|
-
} | {
|
|
292
|
-
readonly kind: "leaves";
|
|
293
|
-
readonly band: Band<A>;
|
|
294
|
-
readonly why: string;
|
|
295
|
-
} | {
|
|
296
|
-
readonly kind: "restarts";
|
|
297
|
-
readonly band: Band<A> | null;
|
|
298
|
-
readonly why: string;
|
|
299
|
-
} | {
|
|
300
|
-
readonly kind: "stays";
|
|
301
|
-
readonly why: string;
|
|
302
|
-
} | {
|
|
303
|
-
readonly kind: "stalls";
|
|
304
|
-
readonly why: string;
|
|
305
|
-
} | {
|
|
306
|
-
readonly kind: "measured";
|
|
307
|
-
readonly why: string;
|
|
308
|
-
};
|
|
326
|
+
}
|
|
309
327
|
/** A replica is one pod of the service at the start of a run. */
|
|
310
328
|
interface Replica {
|
|
311
329
|
readonly pod: string;
|
|
@@ -498,11 +516,25 @@ interface RecordLine {
|
|
|
498
516
|
readonly on: string;
|
|
499
517
|
readonly d: string | null;
|
|
500
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* A socket line is the connect or the close of a socket of the channel of a gRPC client
|
|
521
|
+
* (`client-lines.ts`). It gives the client (`p`) and its node (`n`), and the local port of the socket.
|
|
522
|
+
* A close also gives the code of an error of the socket (`err`), or null for a close with no error.
|
|
523
|
+
*/
|
|
524
|
+
interface SocketLine {
|
|
525
|
+
readonly t: number;
|
|
526
|
+
readonly what: "connect" | "close";
|
|
527
|
+
readonly p: string;
|
|
528
|
+
readonly n: string;
|
|
529
|
+
readonly lp: number | null;
|
|
530
|
+
readonly err?: string | null;
|
|
531
|
+
}
|
|
501
532
|
declare const requestsOf: (lines: readonly Line[]) => Request[];
|
|
502
533
|
declare const podsOf: (lines: readonly Line[]) => PodLine[];
|
|
503
534
|
declare const slicesOf: (lines: readonly Line[], svc: string) => SliceLine[];
|
|
504
535
|
declare const rulesOf: (lines: readonly Line[], source?: PropagationSource) => RulesLine[];
|
|
505
536
|
declare const recordsOf: (lines: readonly Line[]) => RecordLine[];
|
|
537
|
+
declare const socketsOf: (lines: readonly Line[]) => SocketLine[];
|
|
506
538
|
/** This function gives the requests that did not succeed, in sequence of their start. */
|
|
507
539
|
declare const failuresOf: (lines: readonly Line[]) => Request[];
|
|
508
540
|
/** This function counts the outcomes of the requests, by outcome. */
|
|
@@ -610,6 +642,8 @@ interface RunFacts<A = Quantity> {
|
|
|
610
642
|
readonly updates?: Readonly<Record<string, Propagated<A> | null>> | undefined;
|
|
611
643
|
/** These are the first LDS updates of the sidecar of each server pod after the heal, by pod. */
|
|
612
644
|
readonly healUpdates?: Readonly<Record<string, Propagated<A> | null>> | undefined;
|
|
645
|
+
/** These are the first close of the socket of each client after the reference (`ends`), by client, or null for none. */
|
|
646
|
+
readonly closes?: Readonly<Record<string, A | null>> | undefined;
|
|
613
647
|
}
|
|
614
648
|
/** A deleted replica is a replica of a run that got a deletion. It gives its pod, and the stamps of its intake (or null) and of its deletion. */
|
|
615
649
|
interface Deleted {
|
|
@@ -648,9 +682,10 @@ interface KindVerdict<A = Quantity> {
|
|
|
648
682
|
}
|
|
649
683
|
/**
|
|
650
684
|
* This function gives the unit of the main value of a cell, from its prediction:
|
|
651
|
-
* - `s` for a time: the removal, the departure or the restart of a band,
|
|
685
|
+
* - `s` for a time: the removal, the departure or the restart of a band, the end of a stall, or a close.
|
|
652
686
|
* - `{request}` for the failed requests: no failure, a window, a fleet that stays, and a measurement.
|
|
653
687
|
* - `{replica}` for the replicas that received SIGTERM, when no replica must restart.
|
|
688
|
+
* - `{socket}` for the sockets of the clients that ended, when no socket must end.
|
|
654
689
|
*/
|
|
655
690
|
declare const unitOf: (prediction: {
|
|
656
691
|
readonly kind: Prediction["kind"];
|
|
@@ -697,7 +732,7 @@ declare const deletionsOf: (run: KindRun) => Deleted[];
|
|
|
697
732
|
* This function gives how long a run observes after the end reference of its last window, or null
|
|
698
733
|
* when its prediction sets no such time. The judge closes a window `end.band.hi` + δ after its end
|
|
699
734
|
* reference. A request that starts by then ends at most the timeout of the workload later: the
|
|
700
|
-
* client cancels it then. A prediction
|
|
735
|
+
* client cancels it then. A prediction with no window, or a window with no end, sets none.
|
|
701
736
|
*/
|
|
702
737
|
declare const observationAfter: (cell: Pick<KindCell, "prediction" | "workload">) => Quantity | null;
|
|
703
738
|
/**
|
|
@@ -707,6 +742,8 @@ declare const observationAfter: (cell: Pick<KindCell, "prediction" | "workload">
|
|
|
707
742
|
* it can miss its own failures. In a rolling update of gRPC, a call waits in the queue of its channel
|
|
708
743
|
* for a stalled connect. It fails only at its timeout (kind run 40128267098). A reference that is not
|
|
709
744
|
* in the lines yet does not count.
|
|
745
|
+
*
|
|
746
|
+
* The band of `ends` also sets an end: its reference, the end of the band and δ.
|
|
710
747
|
*/
|
|
711
748
|
declare const observationEnd: (cell: KindCell, run: Pick<KindRun, "start" | "acts" | "target" | "replicas" | "lines">) => number | null;
|
|
712
749
|
/** This function gives the facts of a run that each table reads: its failures by outcome and node, and the times of its target. */
|
|
@@ -818,7 +855,7 @@ interface TimeSpread {
|
|
|
818
855
|
}
|
|
819
856
|
/** This function gives the spread of a set of times, or null for none. The percentile is by nearest rank. */
|
|
820
857
|
declare const timeSpread: (times: readonly Quantity[]) => TimeSpread | null;
|
|
821
|
-
/** This function gives the prediction of a cell in a short form for a table. */
|
|
858
|
+
/** This function gives the prediction of a cell in a short form for a table. A prediction `ends` gives its window after its band. */
|
|
822
859
|
declare const predictionText: (prediction: Prediction) => string;
|
|
823
860
|
/**
|
|
824
861
|
* This function gives the main measurement of a cell in a short form: its median and what it
|
|
@@ -876,5 +913,5 @@ declare const quantileOf: (sorted: readonly number[], p: number) => number;
|
|
|
876
913
|
*/
|
|
877
914
|
declare const medianInterval: (sorted: readonly number[]) => [number, number];
|
|
878
915
|
//#endregion
|
|
879
|
-
export { updatesOf as $,
|
|
880
|
-
//# sourceMappingURL=judge-
|
|
916
|
+
export { updatesOf as $, RedisDeclaration as $t, TriggerV1 as A, socketsOf as At, RunCheck as B, seconds as Bt, KindCellResult as C, restartsAt as Ct, PredictionV1 as D, sliceDropOf as Dt, LoadSample as E, signalsOf as Et, KIND_NOISE as F, LOG_LABEL as Ft, judgeKind as G, KindCell as Gt, checkRun as H, showBand as Ht, KindDecision as I, PORT as It, propagationOf as J, Mesh as Jt, observationAfter as K, KindRun as Kt, KindVerdict as L, REDIS as Lt, upgrade as M, transitionOf as Mt, upgradeCell as N, CLIENT as Nt, RunCheckV1 as O, sliceEmptyOf as Ot, Deleted as P, HEALTH_PORT as Pt, unitOf as Q, Prediction as Qt, LAST_SLACK as R, SERVER as Rt, KindCellRecordV1 as S, scenarioProblems as Sn, requestsOf as St, KindVerdictV1 as T, signalOf as Tt, deletionsOf as U, Band as Ut, RunFacts as V, secondsOf as Vt, factsOf as W, Declaration as Wt, referenceOf as X, MeshMode as Xt, propagationSource as Y, MeshFlavor as Yt, reusesReset as Z, PodSettings as Zt, ClusterManifest as _, goDuration as _n, kernelCounts as _t, STAGES as a, Trigger as an, Request as at, FORMAT_V1 as b, nodeNames as bn, podsOf as bt, readRecord as c, Workload as cn, SocketLine as ct, calibrationOf as d, KindScenario as dn, byNode as dt, Reference as en, Endpoint as et, cellsTable as f, KubeProxyMode as fn, deletionOf as ft, BandV1 as g, cidrRange as gn, intakeOf as gt, timeSpread as h, cidrOverlap as hn, failuresOf as ht, quantileOf as i, Template as in, RecordLine as it, judge as j, stoppingOf as jt, RunFactsV1 as k, slicesOf as kt, Judged as l, KUBE_PROXY_MODES as ln, backendsAt as lt, predictionText as m, NodeImage as mn, emptyOf as mt, medianInterval as n, ResetReuse as nn, PodLine as nt, kindCellRecordSchema as o, TriggerKind as on, RulesLine as ot, medianText as p, NODE_IMAGES as pn, dropOf as pt, observationEnd as q, KubeletProbe as qt, middle as r, Stack as rn, PropagationSource as rt, kindCellRecordV1Schema as s, WindowPrediction as sn, SliceLine as st, RELATIVE as t, Replica as tn, Line as tt, TimeSpread as u, KindConfig as un, byClients as ut, EnvironmentManifest as v, kindConfig as vn, outcomesOf as vt, KindCellV1 as w, rulesOf as wt, KindCellRecord as x, scenario as xn, recordsOf as xt, FORMAT as y, kubeProxyPatch as yn, podsAt as yt, Propagated as z, bandOf as zt };
|
|
917
|
+
//# sourceMappingURL=judge-B9kVpFE-.d.ts.map
|
|
@@ -42,6 +42,7 @@ const podsOf = (lines) => of(lines, "pod").sort((a, b) => a.t - b.t);
|
|
|
42
42
|
const slicesOf = (lines, svc) => of(lines, "slice").filter((line) => line.svc === svc);
|
|
43
43
|
const rulesOf = (lines, source = "rules") => of(lines, source).sort((a, b) => a.t - b.t);
|
|
44
44
|
const recordsOf = (lines) => of(lines, "rec");
|
|
45
|
+
const socketsOf = (lines) => of(lines, "sock").sort((a, b) => a.t - b.t);
|
|
45
46
|
/** This function gives the requests that did not succeed, in sequence of their start. */
|
|
46
47
|
const failuresOf = (lines) => requestsOf(lines).filter((request) => request.o !== "ok").sort((a, b) => a.t - b.t);
|
|
47
48
|
/** This function counts the outcomes of the requests, by outcome. */
|
|
@@ -192,9 +193,10 @@ const NOISE = valueIn(KIND_NOISE, "ms");
|
|
|
192
193
|
const SLACK = valueIn(LAST_SLACK, "ms");
|
|
193
194
|
/**
|
|
194
195
|
* This function gives the unit of the main value of a cell, from its prediction:
|
|
195
|
-
* - `s` for a time: the removal, the departure or the restart of a band,
|
|
196
|
+
* - `s` for a time: the removal, the departure or the restart of a band, the end of a stall, or a close.
|
|
196
197
|
* - `{request}` for the failed requests: no failure, a window, a fleet that stays, and a measurement.
|
|
197
198
|
* - `{replica}` for the replicas that received SIGTERM, when no replica must restart.
|
|
199
|
+
* - `{socket}` for the sockets of the clients that ended, when no socket must end.
|
|
198
200
|
*/
|
|
199
201
|
const unitOf = (prediction) => {
|
|
200
202
|
switch (prediction.kind) {
|
|
@@ -202,6 +204,7 @@ const unitOf = (prediction) => {
|
|
|
202
204
|
case "leaves":
|
|
203
205
|
case "stalls": return "s";
|
|
204
206
|
case "restarts": return prediction.band === null ? "{replica}" : "s";
|
|
207
|
+
case "ends": return prediction.band === null ? "{socket}" : "s";
|
|
205
208
|
default: return "{request}";
|
|
206
209
|
}
|
|
207
210
|
};
|
|
@@ -296,16 +299,18 @@ const deletionsOf = (run) => run.replicas.flatMap((replica) => {
|
|
|
296
299
|
}).sort((a, b) => a.deletion - b.deletion);
|
|
297
300
|
/** This function tells if a prediction is a window for each deleted replica. */
|
|
298
301
|
const eachOf = (prediction) => prediction.kind === "window" && prediction.each === true;
|
|
302
|
+
/** This function gives the window of the failed requests of a prediction: a window, the window of `ends`, or null. */
|
|
303
|
+
const windowOf = (prediction) => prediction.kind === "window" ? prediction : prediction.kind === "ends" ? prediction.window ?? null : null;
|
|
299
304
|
/**
|
|
300
305
|
* This function gives how long a run observes after the end reference of its last window, or null
|
|
301
306
|
* when its prediction sets no such time. The judge closes a window `end.band.hi` + δ after its end
|
|
302
307
|
* reference. A request that starts by then ends at most the timeout of the workload later: the
|
|
303
|
-
* client cancels it then. A prediction
|
|
308
|
+
* client cancels it then. A prediction with no window, or a window with no end, sets none.
|
|
304
309
|
*/
|
|
305
310
|
const observationAfter = (cell) => {
|
|
306
|
-
const
|
|
307
|
-
if (
|
|
308
|
-
return seconds(sec(valueIn(
|
|
311
|
+
const window = windowOf(cell.prediction);
|
|
312
|
+
if (window === null || window.end === null) return null;
|
|
313
|
+
return seconds(sec(valueIn(window.end.band.hi, "ms") + valueIn(cell.workload.timeout, "ms") + NOISE));
|
|
309
314
|
};
|
|
310
315
|
/**
|
|
311
316
|
* This function gives the earliest end of the observation of a run, as a stamp, or null when its
|
|
@@ -314,17 +319,27 @@ const observationAfter = (cell) => {
|
|
|
314
319
|
* it can miss its own failures. In a rolling update of gRPC, a call waits in the queue of its channel
|
|
315
320
|
* for a stalled connect. It fails only at its timeout (kind run 40128267098). A reference that is not
|
|
316
321
|
* in the lines yet does not count.
|
|
322
|
+
*
|
|
323
|
+
* The band of `ends` also sets an end: its reference, the end of the band and δ.
|
|
317
324
|
*/
|
|
318
325
|
const observationEnd = (cell, run) => {
|
|
319
|
-
const
|
|
326
|
+
const ends = [];
|
|
320
327
|
const prediction = cell.prediction;
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
if (prediction.kind === "ends" && prediction.band !== null) {
|
|
329
|
+
const at = referenceOf(run, prediction.from);
|
|
330
|
+
if (at !== null) ends.push(at + valueIn(prediction.band.hi, "ms") + NOISE);
|
|
331
|
+
}
|
|
332
|
+
const after = observationAfter(cell);
|
|
333
|
+
const window = windowOf(prediction);
|
|
334
|
+
if (after !== null && window !== null && window.end !== null) {
|
|
335
|
+
const from = window.end.from;
|
|
336
|
+
const references = (window.each === true ? run.replicas.map((replica) => replica.pod) : [run.target?.pod]).flatMap((pod) => {
|
|
337
|
+
const at = referenceOf(run, from, pod);
|
|
338
|
+
return at === null ? [] : [at];
|
|
339
|
+
});
|
|
340
|
+
if (references.length > 0) ends.push(Math.max(...references) + valueIn(after, "ms"));
|
|
341
|
+
}
|
|
342
|
+
return ends.length === 0 ? null : Math.max(...ends);
|
|
328
343
|
};
|
|
329
344
|
/** This function gives the facts of a run that each table reads: its failures by outcome and node, and the times of its target. */
|
|
330
345
|
const factsOf = (cell, run) => {
|
|
@@ -599,9 +614,83 @@ const checkRun = (cell, run) => {
|
|
|
599
614
|
if (problems.length === 0) return done(true, `the rollout waited for Redis, and its first new pod was ready ${sec(firstReady.t - heal)} s after Redis came back`, sec(firstReady.t - heal));
|
|
600
615
|
return done(false, problems.join("; "), null);
|
|
601
616
|
}
|
|
617
|
+
case "ends": return checkEnds(cell, run, prediction, done);
|
|
602
618
|
case "measured": return done(true, "measured", facts.failures);
|
|
603
619
|
}
|
|
604
620
|
};
|
|
621
|
+
/**
|
|
622
|
+
* This function judges a run against a prediction `ends`. Each client with an answer before the
|
|
623
|
+
* reference is a client of the run. Its socket is the port of that answer (`lp`).
|
|
624
|
+
* - With a band, the first close of a socket of each client after the reference must be in the band
|
|
625
|
+
* ± δ. It must be the close of that socket. The main value is the middle of these times.
|
|
626
|
+
* - With no band, no socket of a client can close after the reference. The first request of each client
|
|
627
|
+
* after the heal must be `ok` on that socket. The main value is the count of the closes.
|
|
628
|
+
*
|
|
629
|
+
* With a window, the failed requests of the run must also agree with that window (`checkRun`).
|
|
630
|
+
*/
|
|
631
|
+
const checkEnds = (cell, run, prediction, done) => {
|
|
632
|
+
const from = referenceOf(run, prediction.from);
|
|
633
|
+
if (from === null) return done(false, `the run has no ${prediction.from} reference`, null);
|
|
634
|
+
const lines = run.lines;
|
|
635
|
+
const calls = requestsOf(lines).filter((request) => request.t >= run.start && request.t <= run.end).sort((a, b) => a.t - b.t);
|
|
636
|
+
const last = /* @__PURE__ */ new Map();
|
|
637
|
+
for (const request of calls) if (request.t < from && request.o === "ok") last.set(request.p, request);
|
|
638
|
+
if (last.size === 0) return done(false, `no client had an answer before the ${prediction.from}`, null);
|
|
639
|
+
const clients = [...last.keys()].sort();
|
|
640
|
+
const shut = socketsOf(lines).filter((line) => line.what === "close" && line.t >= from && line.t <= run.end);
|
|
641
|
+
const closes = {};
|
|
642
|
+
const times = [];
|
|
643
|
+
const problems = [];
|
|
644
|
+
for (const client of clients) {
|
|
645
|
+
const close = shut.find((line) => line.p === client);
|
|
646
|
+
closes[client] = close === void 0 ? null : seconds(sec(close.t - from));
|
|
647
|
+
if (close !== void 0) times.push(sec(close.t - from));
|
|
648
|
+
}
|
|
649
|
+
let agrees;
|
|
650
|
+
let value;
|
|
651
|
+
if (prediction.band !== null) {
|
|
652
|
+
const band = secondsOf(prediction.band);
|
|
653
|
+
for (const client of clients) {
|
|
654
|
+
const close = shut.find((line) => line.p === client);
|
|
655
|
+
const port = last.get(client).lp ?? null;
|
|
656
|
+
if (close === void 0) problems.push(`the socket of ${client} did not end`);
|
|
657
|
+
else if (port !== null && close.lp !== null && close.lp !== port) problems.push(`the first socket of ${client} to end had the port ${close.lp}, not the port ${port} of its last answer`);
|
|
658
|
+
else if (!inBand(sec(close.t - from), band)) problems.push(`the socket of ${client} ended at ${sec(close.t - from)} s`);
|
|
659
|
+
}
|
|
660
|
+
const sorted = [...times].sort((a, b) => a - b);
|
|
661
|
+
value = sorted.length === 0 ? null : middle(sorted);
|
|
662
|
+
agrees = `the socket of each of ${clients.length} clients ended, at ${sorted.join(", ")} s`;
|
|
663
|
+
} else {
|
|
664
|
+
for (const client of clients) {
|
|
665
|
+
const close = shut.find((line) => line.p === client);
|
|
666
|
+
if (close !== void 0) problems.push(`the socket of ${client} ended ${sec(close.t - from)} s after the ${prediction.from}`);
|
|
667
|
+
}
|
|
668
|
+
const resumed = run.acts.heal ?? from;
|
|
669
|
+
for (const client of clients) {
|
|
670
|
+
const port = last.get(client).lp ?? null;
|
|
671
|
+
const next = calls.find((request) => request.p === client && request.t >= resumed);
|
|
672
|
+
if (next === void 0) problems.push(`${client} sent no call after the heal`);
|
|
673
|
+
else if (next.o !== "ok") problems.push(`the first call of ${client} after the heal ended ${next.o}`);
|
|
674
|
+
else if ((next.lp ?? null) !== port) problems.push(`the first call of ${client} after the heal went on the port ${next.lp ?? "none"}, not ${port ?? "none"}`);
|
|
675
|
+
}
|
|
676
|
+
value = times.length;
|
|
677
|
+
agrees = `no socket of ${clients.length} clients ended, and the first call of each one after the heal went on its socket`;
|
|
678
|
+
}
|
|
679
|
+
const inner = prediction.window === void 0 ? null : checkRun({
|
|
680
|
+
...cell,
|
|
681
|
+
prediction: prediction.window
|
|
682
|
+
}, run);
|
|
683
|
+
if (inner !== null && !inner.ok) problems.push(inner.why);
|
|
684
|
+
const why = problems.length > 0 ? problems.join("; ") : inner === null ? agrees : `${agrees}; ${inner.why}`;
|
|
685
|
+
const checked = done(problems.length === 0, why, value, {
|
|
686
|
+
...inner === null ? {} : inner.facts,
|
|
687
|
+
closes
|
|
688
|
+
});
|
|
689
|
+
return inner?.tail === void 0 ? checked : {
|
|
690
|
+
...checked,
|
|
691
|
+
tail: inner.tail
|
|
692
|
+
};
|
|
693
|
+
};
|
|
605
694
|
/** This function judges a cell on its runs: each counted run must agree, and a median in a band must be equivalent to it. */
|
|
606
695
|
const judgeKind = (cell, runs) => {
|
|
607
696
|
const counted = runs.filter((run) => !run.warmup && run.void === null);
|
|
@@ -641,7 +730,7 @@ const judgeKind = (cell, runs) => {
|
|
|
641
730
|
checks
|
|
642
731
|
};
|
|
643
732
|
const prediction = cell.prediction;
|
|
644
|
-
const band = prediction.kind === "removal" || prediction.kind === "leaves" || prediction.kind === "restarts" ? prediction.band : null;
|
|
733
|
+
const band = prediction.kind === "removal" || prediction.kind === "leaves" || prediction.kind === "restarts" || prediction.kind === "ends" ? prediction.band : null;
|
|
645
734
|
if (failed > 0) {
|
|
646
735
|
const first = checks.find((check) => !check.ok);
|
|
647
736
|
return {
|
|
@@ -731,25 +820,34 @@ const upgradeTrigger = (trigger) => {
|
|
|
731
820
|
}
|
|
732
821
|
};
|
|
733
822
|
};
|
|
823
|
+
const upgradeWindow = (window) => {
|
|
824
|
+
const { afterReset, propagation, ...rest } = window;
|
|
825
|
+
return {
|
|
826
|
+
...rest,
|
|
827
|
+
start: {
|
|
828
|
+
from: window.start.from,
|
|
829
|
+
band: band(window.start.band)
|
|
830
|
+
},
|
|
831
|
+
end: window.end === null ? null : {
|
|
832
|
+
from: window.end.from,
|
|
833
|
+
band: band(window.end.band)
|
|
834
|
+
},
|
|
835
|
+
...afterReset === void 0 ? {} : { afterReset: {
|
|
836
|
+
life: seconds(afterReset.life),
|
|
837
|
+
outcome: afterReset.outcome
|
|
838
|
+
} },
|
|
839
|
+
...propagation === void 0 ? {} : { propagation: band(propagation) }
|
|
840
|
+
};
|
|
841
|
+
};
|
|
734
842
|
const upgradePrediction = (prediction) => {
|
|
735
843
|
switch (prediction.kind) {
|
|
736
|
-
case "window":
|
|
737
|
-
|
|
844
|
+
case "window": return upgradeWindow(prediction);
|
|
845
|
+
case "ends": {
|
|
846
|
+
const { window, ...rest } = prediction;
|
|
738
847
|
return {
|
|
739
848
|
...rest,
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
band: band(prediction.start.band)
|
|
743
|
-
},
|
|
744
|
-
end: prediction.end === null ? null : {
|
|
745
|
-
from: prediction.end.from,
|
|
746
|
-
band: band(prediction.end.band)
|
|
747
|
-
},
|
|
748
|
-
...afterReset === void 0 ? {} : { afterReset: {
|
|
749
|
-
life: seconds(afterReset.life),
|
|
750
|
-
outcome: afterReset.outcome
|
|
751
|
-
} },
|
|
752
|
-
...propagation === void 0 ? {} : { propagation: band(propagation) }
|
|
849
|
+
band: prediction.band === null ? null : band(prediction.band),
|
|
850
|
+
...window === void 0 ? {} : { window: upgradeWindow(window) }
|
|
753
851
|
};
|
|
754
852
|
}
|
|
755
853
|
case "removal":
|
|
@@ -776,7 +874,7 @@ const upgradeFacts = (facts) => {
|
|
|
776
874
|
by: seconds(old.by)
|
|
777
875
|
};
|
|
778
876
|
const byKey = (record) => Object.fromEntries(Object.entries(record).map(([key, old]) => [key, propagated(old)]));
|
|
779
|
-
const { sliceEmpty, rules, left, signals, updates, healUpdates, ...rest } = facts;
|
|
877
|
+
const { sliceEmpty, rules, left, signals, updates, healUpdates, closes, ...rest } = facts;
|
|
780
878
|
return {
|
|
781
879
|
...rest,
|
|
782
880
|
intakeAfterDeletion: time$1(facts.intakeAfterDeletion),
|
|
@@ -787,7 +885,8 @@ const upgradeFacts = (facts) => {
|
|
|
787
885
|
...sliceEmpty === void 0 ? {} : { sliceEmpty: seconds(sliceEmpty) },
|
|
788
886
|
...rules === void 0 ? {} : { rules: Object.fromEntries(Object.entries(rules).map(([node, old]) => [node, time$1(old)])) },
|
|
789
887
|
...left === void 0 ? {} : { left: seconds(left) },
|
|
790
|
-
...signals === void 0 ? {} : { signals: signals.map((old) => time$1(old)) }
|
|
888
|
+
...signals === void 0 ? {} : { signals: signals.map((old) => time$1(old)) },
|
|
889
|
+
...closes === void 0 ? {} : { closes: Object.fromEntries(Object.entries(closes).map(([client, old]) => [client, time$1(old)])) }
|
|
791
890
|
};
|
|
792
891
|
};
|
|
793
892
|
/**
|
|
@@ -853,7 +952,7 @@ const atBand = (from, band) => {
|
|
|
853
952
|
const { lo, hi } = secondsOf(band);
|
|
854
953
|
return lo === 0 && hi === 0 ? `the ${from}` : `the ${from} + ${showBand(band)}`;
|
|
855
954
|
};
|
|
856
|
-
/** This function gives the prediction of a cell in a short form for a table. */
|
|
955
|
+
/** This function gives the prediction of a cell in a short form for a table. A prediction `ends` gives its window after its band. */
|
|
857
956
|
const predictionText = (prediction) => {
|
|
858
957
|
switch (prediction.kind) {
|
|
859
958
|
case "none": return prediction.outcomes === void 0 ? "no failure" : `no ${prediction.outcomes.join(", ")}`;
|
|
@@ -863,6 +962,10 @@ const predictionText = (prediction) => {
|
|
|
863
962
|
case "restarts": return prediction.band === null ? "no restart" : `each replica SIGTERM in ${showBand(prediction.band)}`;
|
|
864
963
|
case "stays": return "stays ready, no refusal";
|
|
865
964
|
case "stalls": return "the rollout waits for Redis";
|
|
965
|
+
case "ends": {
|
|
966
|
+
const closes = prediction.band === null ? "no socket of a client ends, and each one calls again on its socket" : `the socket of each client ends in ${atBand(prediction.from, prediction.band)}`;
|
|
967
|
+
return prediction.window === void 0 ? closes : `${closes}; ${predictionText(prediction.window)}`;
|
|
968
|
+
}
|
|
866
969
|
case "measured": return "measured";
|
|
867
970
|
}
|
|
868
971
|
};
|
|
@@ -964,7 +1067,8 @@ const TRIGGERS = [
|
|
|
964
1067
|
"app-stall",
|
|
965
1068
|
"rollout",
|
|
966
1069
|
"cpu-throttle",
|
|
967
|
-
"authorization-policy"
|
|
1070
|
+
"authorization-policy",
|
|
1071
|
+
"idle"
|
|
968
1072
|
];
|
|
969
1073
|
const MODES = [
|
|
970
1074
|
"iptables",
|
|
@@ -1114,33 +1218,34 @@ const recordOf = (a, main) => {
|
|
|
1114
1218
|
lo: a,
|
|
1115
1219
|
hi: a
|
|
1116
1220
|
});
|
|
1221
|
+
const window = z.object({
|
|
1222
|
+
kind: z.literal("window"),
|
|
1223
|
+
nodes: z.enum(["all", "paused"]),
|
|
1224
|
+
start: z.object({
|
|
1225
|
+
from: reference,
|
|
1226
|
+
band
|
|
1227
|
+
}),
|
|
1228
|
+
end: z.object({
|
|
1229
|
+
from: reference,
|
|
1230
|
+
band
|
|
1231
|
+
}).nullable(),
|
|
1232
|
+
certain: z.boolean(),
|
|
1233
|
+
afterReset: z.object({
|
|
1234
|
+
life: a,
|
|
1235
|
+
outcome: z.string()
|
|
1236
|
+
}).optional(),
|
|
1237
|
+
each: z.boolean().optional(),
|
|
1238
|
+
propagation: band.optional(),
|
|
1239
|
+
outcomes: z.array(z.string()).optional(),
|
|
1240
|
+
why: z.string()
|
|
1241
|
+
});
|
|
1117
1242
|
const prediction = z.discriminatedUnion("kind", [
|
|
1118
1243
|
z.object({
|
|
1119
1244
|
kind: z.literal("none"),
|
|
1120
1245
|
outcomes: z.array(z.string()).optional(),
|
|
1121
1246
|
why: z.string()
|
|
1122
1247
|
}),
|
|
1123
|
-
|
|
1124
|
-
kind: z.literal("window"),
|
|
1125
|
-
nodes: z.enum(["all", "paused"]),
|
|
1126
|
-
start: z.object({
|
|
1127
|
-
from: reference,
|
|
1128
|
-
band
|
|
1129
|
-
}),
|
|
1130
|
-
end: z.object({
|
|
1131
|
-
from: reference,
|
|
1132
|
-
band
|
|
1133
|
-
}).nullable(),
|
|
1134
|
-
certain: z.boolean(),
|
|
1135
|
-
afterReset: z.object({
|
|
1136
|
-
life: a,
|
|
1137
|
-
outcome: z.string()
|
|
1138
|
-
}).optional(),
|
|
1139
|
-
each: z.boolean().optional(),
|
|
1140
|
-
propagation: band.optional(),
|
|
1141
|
-
outcomes: z.array(z.string()).optional(),
|
|
1142
|
-
why: z.string()
|
|
1143
|
-
}),
|
|
1248
|
+
window,
|
|
1144
1249
|
z.object({
|
|
1145
1250
|
kind: z.literal("removal"),
|
|
1146
1251
|
band,
|
|
@@ -1164,6 +1269,13 @@ const recordOf = (a, main) => {
|
|
|
1164
1269
|
kind: z.literal("stalls"),
|
|
1165
1270
|
why: z.string()
|
|
1166
1271
|
}),
|
|
1272
|
+
z.object({
|
|
1273
|
+
kind: z.literal("ends"),
|
|
1274
|
+
from: reference,
|
|
1275
|
+
band: band.nullable(),
|
|
1276
|
+
window: window.optional(),
|
|
1277
|
+
why: z.string()
|
|
1278
|
+
}),
|
|
1167
1279
|
z.object({
|
|
1168
1280
|
kind: z.literal("measured"),
|
|
1169
1281
|
why: z.string()
|
|
@@ -1241,7 +1353,8 @@ const recordOf = (a, main) => {
|
|
|
1241
1353
|
healUpdates: z.record(z.string(), z.object({
|
|
1242
1354
|
after: a.nullable(),
|
|
1243
1355
|
by: a
|
|
1244
|
-
}).nullable()).optional()
|
|
1356
|
+
}).nullable()).optional(),
|
|
1357
|
+
closes: z.record(z.string(), a.nullable()).optional()
|
|
1245
1358
|
});
|
|
1246
1359
|
return {
|
|
1247
1360
|
cell,
|
|
@@ -1303,6 +1416,6 @@ const readRecord = (data) => {
|
|
|
1303
1416
|
throw new Error(`a kind cell record has the format ${FORMAT} or ${FORMAT_V1}, not ${String(format)}`);
|
|
1304
1417
|
};
|
|
1305
1418
|
//#endregion
|
|
1306
|
-
export { slicesOf as $, medianInterval as A, intakeOf as B, propagationOf as C, unitOf as D, reusesReset as E, byNode as F, recordsOf as G, outcomesOf as H, deletionOf as I, rulesOf as J, requestsOf as K, dropOf as L, quantileOf as M, backendsAt as N, updatesOf as O, byClients as P, sliceEmptyOf as Q, emptyOf as R, observationEnd as S, referenceOf as T, podsAt as U, kernelCounts as V, podsOf as W, signalsOf as X, signalOf as Y, sliceDropOf as Z, checkRun as _, calibrationOf as a,
|
|
1419
|
+
export { slicesOf as $, medianInterval as A, intakeOf as B, propagationOf as C, unitOf as D, reusesReset as E, byNode as F, recordsOf as G, outcomesOf as H, deletionOf as I, rulesOf as J, requestsOf as K, dropOf as L, quantileOf as M, backendsAt as N, updatesOf as O, byClients as P, sliceEmptyOf as Q, emptyOf as R, observationEnd as S, referenceOf as T, podsAt as U, kernelCounts as V, podsOf as W, signalsOf as X, signalOf as Y, sliceDropOf as Z, checkRun as _, calibrationOf as a, LOG_LABEL as at, judgeKind as b, predictionText as c, SERVER as ct, FORMAT_V1 as d, secondsOf as dt, socketsOf as et, judge as f, showBand as ft, LAST_SLACK as g, KIND_NOISE as h, readRecord as i, HEALTH_PORT as it, middle as j, RELATIVE as k, timeSpread as l, bandOf as lt, upgradeCell as m, kindCellRecordSchema as n, transitionOf as nt, cellsTable as o, PORT as ot, upgrade as p, restartsAt as q, kindCellRecordV1Schema as r, CLIENT as rt, medianText as s, REDIS as st, STAGES as t, stoppingOf as tt, FORMAT as u, seconds as ut, deletionsOf as v, propagationSource as w, observationAfter as x, factsOf as y, failuresOf as z };
|
|
1307
1420
|
|
|
1308
|
-
//# sourceMappingURL=judge-
|
|
1421
|
+
//# sourceMappingURL=judge-DNCI5vvt.js.map
|