@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.
@@ -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
- return seconds(sec(valueIn(window.end.band.hi, "ms") + valueIn(cell.workload.timeout, "ms") + NOISE));
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 closes = prediction.end === null ? null : secondsOf(prediction.end.band);
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 { rulesOf as $, RELATIVE as A, dropOf as B, observationEnd as C, showBand as Ct, reusesReset as D, referenceOf as E, TCP_STATE as F, kernelCounts as G, failuresOf as H, backendsAt as I, podsOf as J, outcomesOf as K, byClients as L, middle as M, quantileOf as N, unitOf as O, HBONE_PORT as P, restartsAt as Q, byNode as R, observationAfter as S, secondsOf as St, propagationSource as T, intakeOf as U, emptyOf as V, isTunnelState as W, requestsOf as X, recordsOf as Y, restartOf as Z, checkRun as _, PORT as _t, calibrationOf as a, sliceReadyAt as at, factsOf as b, bandOf as bt, predictionText as c, socketsOf as ct, FORMAT_V1 as d, tunnelsOf as dt, sessionsAt as et, judge as f, ztunnelPodsOf as ft, LAST_SLACK as g, LOG_LABEL as gt, KIND_NOISE as h, HEALTH_PORT as ht, readRecord as i, sliceEmptyOf as it, medianInterval as j, updatesOf as k, timeSpread as l, stoppingOf as lt, upgradeCell as m, CLIENT as mt, kindCellRecordSchema as n, signalsOf as nt, cellsTable as o, sliceReadyOf as ot, upgrade as p, ztunnelReplaced as pt, podsAt as q, kindCellRecordV1Schema as r, sliceDropOf as rt, medianText as s, slicesOf as st, STAGES as t, signalOf as tt, FORMAT as u, transitionOf as ut, cyclesOf as v, REDIS as vt, propagationOf as w, judgeKind as x, seconds as xt, deletionsOf as y, SERVER as yt, deletionOf as z };
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-BdykXjF2.js.map
1914
+ //# sourceMappingURL=judge-2EZajeXa.js.map