@mark1russell7/harness 0.13.0 → 0.14.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/build/index.d.ts CHANGED
@@ -530,14 +530,18 @@ interface GroupVerdict {
530
530
  }>;
531
531
  readonly lags?: Spread | null;
532
532
  }
533
- /** This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). */
533
+ /**
534
+ * This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). It is
535
+ * `quantileSorted` of @mark1russell7/rigor-statistics with the method `"nearest-rank"`.
536
+ */
534
537
  export declare const quantileOf: (sorted: readonly number[], p: number) => number;
535
538
  /** This function gives the spread of a set of times, or null for none. */
536
539
  export declare const spreadOf: (times: readonly Quantity[]) => Spread | null;
537
540
  /**
538
541
  * This function gives the distribution-free interval of a median: [x_(k), x_(n+1−k)], for the
539
542
  * largest k whose two tails each hold 2.5 % or less. For eleven runs, it gives [x_(2), x_(10)],
540
- * at 98.8 %.
543
+ * at 98.8 %. It is `medianInterval` of @mark1russell7/rigor-statistics at the level 0.95. An
544
+ * empty list throws a RangeError.
541
545
  */
542
546
  export declare const medianInterval: (sorted: readonly number[]) => [number, number];
543
547
  /**
package/build/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { C as targetImageOf, E as unpinnedBases, S as subjectProblems, T as unlockedManifests, _ as pgClientImage, a as SHIPPED_NAMES, b as routerImage, c as grpcClientImage, d as imageOf, f as imageProblems, g as nodeHttpImage, h as nodeClientImage, i as SHIPPED_CLIENTS, l as harnessImage, m as mongoClientImage, n as PINNED, o as amqpClientImage, p as inContext, r as REPOSITORY, s as goHttpImage, t as CLIENT_NAMES, u as hikariClientImage, v as planOf, w as targetSourceOf, x as subjectClient, y as programOf } from "./image-Lg8LL6C3.js";
2
2
  import { quantity, valueIn } from "@mark1russell7/quantity";
3
+ import { median, medianInterval as medianInterval$1, quantileSorted } from "@mark1russell7/rigor-statistics";
3
4
  import { createHash } from "node:crypto";
4
5
  //#region src/cell.ts
5
6
  /** This function tells if a fault place is a hop of a topology. */
@@ -86,15 +87,18 @@ const STEPS = [
86
87
  ];
87
88
  const s = (value) => quantity(Math.round(value * 1e6) / 1e6, "s");
88
89
  const name = (run) => `env ${run.environment} run ${run.index}`;
89
- /** This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). */
90
- const quantileOf = (sorted, p) => sorted[Math.min(sorted.length, Math.max(1, Math.ceil(p * sorted.length))) - 1];
90
+ /**
91
+ * This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). It is
92
+ * `quantileSorted` of @mark1russell7/rigor-statistics with the method `"nearest-rank"`.
93
+ */
94
+ const quantileOf = (sorted, p) => quantileSorted(sorted, p, "nearest-rank");
91
95
  /** This function gives the spread of a set of times, or null for none. */
92
96
  const spreadOf = (times) => {
93
97
  if (times.length === 0) return null;
94
98
  const sorted = times.map((time) => valueIn(time, "s")).sort((a, b) => a - b);
95
99
  return {
96
100
  count: sorted.length,
97
- median: s(middle(sorted)),
101
+ median: s(median(sorted)),
98
102
  p99: s(quantileOf(sorted, .99)),
99
103
  p999: s(quantileOf(sorted, .999)),
100
104
  max: s(sorted.at(-1))
@@ -135,30 +139,15 @@ const spreadWords = (spread) => {
135
139
  const ms = (time) => Math.round(valueIn(time, "s") * 1e6) / 1e3;
136
140
  return `median ${ms(spread.median)} ms, p99 ${ms(spread.p99)} ms, p99.9 ${ms(spread.p999)} ms, max ${ms(spread.max)} ms of ${spread.count}`;
137
141
  };
138
- /** This function gives P(X ≤ j) for X ~ Binomial(n, 1/2), exactly. */
139
- const below = (n, j) => {
140
- let term = 1;
141
- let sum = 0;
142
- for (let i = 0; i <= j; i += 1) {
143
- sum += term;
144
- term = term * (n - i) / (i + 1);
145
- }
146
- return sum / 2 ** n;
147
- };
148
142
  /**
149
143
  * This function gives the distribution-free interval of a median: [x_(k), x_(n+1−k)], for the
150
144
  * largest k whose two tails each hold 2.5 % or less. For eleven runs, it gives [x_(2), x_(10)],
151
- * at 98.8 %.
145
+ * at 98.8 %. It is `medianInterval` of @mark1russell7/rigor-statistics at the level 0.95. An
146
+ * empty list throws a RangeError.
152
147
  */
153
148
  const medianInterval = (sorted) => {
154
- const n = sorted.length;
155
- let k = 0;
156
- while (k + 1 <= n / 2 && below(n, k) <= .025) k += 1;
157
- return k === 0 ? [sorted[0], sorted[n - 1]] : [sorted[k - 1], sorted[n - k]];
158
- };
159
- const middle = (sorted) => {
160
- const half = Math.floor(sorted.length / 2);
161
- return sorted.length % 2 === 1 ? sorted[half] : (sorted[half - 1] + sorted[half]) / 2;
149
+ const interval = medianInterval$1(sorted, .95);
150
+ return [interval.low, interval.high];
162
151
  };
163
152
  /**
164
153
  * This function gives the verdict of a cell, from two tests:
@@ -241,7 +230,7 @@ const withRequests = (verdict, counted) => {
241
230
  };
242
231
  };
243
232
  /** This function gives the median of some counts, or null for none. */
244
- const medianCount = (values) => values.length === 0 ? null : middle([...values].sort((a, b) => a - b));
233
+ const medianCount = (values) => values.length === 0 ? null : median([...values].sort((a, b) => a - b));
245
234
  /** This function sums what the group of a group cell did in its counted runs, and tests each stage that has a band. */
246
235
  const groupVerdictOf = (prediction, counted, delta = NOISE) => {
247
236
  const noise = valueIn(delta, "s");
@@ -274,7 +263,7 @@ const groupVerdictOf = (prediction, counted, delta = NOISE) => {
274
263
  return {
275
264
  stage: band.stage,
276
265
  band: interval,
277
- median: values.length === 0 ? null : s(middle(values)),
266
+ median: values.length === 0 ? null : s(median(values)),
278
267
  outside,
279
268
  decision: outside.length === 0 && values.length > 0 ? "holds" : "fails"
280
269
  };
@@ -286,7 +275,7 @@ const groupVerdictOf = (prediction, counted, delta = NOISE) => {
286
275
  const at = run.group?.stages[stage];
287
276
  return at === void 0 ? [] : [valueIn(at, "s")];
288
277
  });
289
- medians[stage] = s(middle(values.sort((a, b) => a - b)));
278
+ medians[stage] = s(median(values.sort((a, b) => a - b)));
290
279
  }
291
280
  const lostCounts = counted.flatMap((run) => run.group?.writes.lost === null || run.group === void 0 ? [] : [run.group.writes.lost.length]);
292
281
  const oldCounts = counted.flatMap((run) => run.group === void 0 ? [] : [run.group.writes.old]);
@@ -314,7 +303,7 @@ const groupVerdictOf = (prediction, counted, delta = NOISE) => {
314
303
  errors: [...errors].map(([error, firsts]) => ({
315
304
  name: error,
316
305
  runs: firsts.length,
317
- first: s(middle(firsts.sort((a, b) => a - b)))
306
+ first: s(median(firsts.sort((a, b) => a - b)))
318
307
  })),
319
308
  ...lags.length === 0 ? {} : { lags: spreadOf(lags) }
320
309
  };
@@ -368,7 +357,7 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
368
357
  kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
369
358
  }
370
359
  const outcomes = [...kinds].map(([kind, n]) => `${n} ${kind}`).join(", ");
371
- const timed = times.length === 0 ? "no time" : `median ${s(middle(times)).value} s from ${prediction.from}, range [${s(times[0]).value}, ${s(times.at(-1)).value}] s`;
360
+ const timed = times.length === 0 ? "no time" : `median ${s(median(times)).value} s from ${prediction.from}, range [${s(times[0]).value}, ${s(times.at(-1)).value}] s`;
372
361
  const missed = outside.filter((each) => counted.some((run) => name(run) === each.run && run.reconnect !== void 0));
373
362
  const late = missed.length === 0 ? "" : ` ${missed.length} runs had a wait of the reconnect outside its band.`;
374
363
  return {
@@ -376,7 +365,7 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
376
365
  decision: "measured",
377
366
  band: null,
378
367
  outside: missed,
379
- median: times.length === 0 ? null : s(middle(times)),
368
+ median: times.length === 0 ? null : s(median(times)),
380
369
  why: `${counted.length} runs measured: ${outcomes}; ${timed}.${late} ${prediction.why}`
381
370
  };
382
371
  }
@@ -393,13 +382,13 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
393
382
  reordering.fast,
394
383
  reordering.seen
395
384
  ].some((count) => (count ?? 0) > 0) ? ` Reordering: ${reorderingWords(reordering)}.` : "";
396
- const why = samples.length === 0 ? `${counted.length} runs measured, with no reply: ${prediction.why}` : `${counted.length} runs, ${samples.length} replies: median ${s(middle(samples)).value} s, p99 ${s(quantileOf(samples, .99)).value} s, slowest ${s(samples.at(-1)).value} s, ${retransmissions} retransmissions, ${ended.length} connections ended.${reordered} ${prediction.why}`;
385
+ const why = samples.length === 0 ? `${counted.length} runs measured, with no reply: ${prediction.why}` : `${counted.length} runs, ${samples.length} replies: median ${s(median(samples)).value} s, p99 ${s(quantileOf(samples, .99)).value} s, slowest ${s(samples.at(-1)).value} s, ${retransmissions} retransmissions, ${ended.length} connections ended.${reordered} ${prediction.why}`;
397
386
  return {
398
387
  ...empty,
399
388
  decision: "measured",
400
389
  band: null,
401
390
  outside: [],
402
- median: samples.length === 0 ? null : s(middle(samples)),
391
+ median: samples.length === 0 ? null : s(median(samples)),
403
392
  ...reordering === void 0 ? {} : { reordering },
404
393
  why
405
394
  };
@@ -442,7 +431,7 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
442
431
  why: "no run measured its time"
443
432
  };
444
433
  const [from, to] = medianInterval(values);
445
- const median = s(middle(values));
434
+ const median$1 = s(median(values));
446
435
  const interval = {
447
436
  low: s(from),
448
437
  high: s(to)
@@ -455,13 +444,13 @@ const verdictOfBand = (prediction, runs, delta = NOISE) => {
455
444
  const disjoint = to < wide.low || from > wide.high;
456
445
  const off = new Set(outside.map((each) => each.run)).size;
457
446
  const counts = `${counted.length - off} of ${counted.length} runs inside [${band.low.value}, ${band.high.value}] s`;
458
- const centre = `median ${median.value} s, its interval [${interval.low.value}, ${interval.high.value}] s`;
447
+ const centre = `median ${median$1.value} s, its interval [${interval.low.value}, ${interval.high.value}] s`;
459
448
  const result = {
460
449
  counted: counted.length,
461
450
  voided,
462
451
  band,
463
452
  outside,
464
- median,
453
+ median: median$1,
465
454
  medianInterval: interval
466
455
  };
467
456
  if (outside.length > 0 || disjoint) return {