next-leak 0.10.1 → 0.11.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/dist/{chunk-5ZBIS5WM.js → chunk-4FYSLLSX.js} +23 -13
- package/dist/{chunk-OIJE43WX.js → chunk-JHOE2RCM.js} +95 -8
- package/dist/{chunk-SV47LIER.js → chunk-YLHE4N5G.js} +1 -1
- package/dist/cli-args.d.ts +9 -0
- package/dist/cli.js +3 -2
- package/dist/confidence.d.ts +10 -1
- package/dist/{html-report-YWIK5EDQ.js → html-report-O7ILFOF5.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/runner.d.ts +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,6 +115,7 @@ Every report prints the gate it used.
|
|
|
115
115
|
|---|---|---|
|
|
116
116
|
| `--routes <list>` | all | Only measure these routes (comma-separated templates or prefixes) |
|
|
117
117
|
| `--cycles <n>` | 4 | Load cycles per route (min 3). The first is dropped as warm-up, so the verdict sees `n − 1` deltas — at 3 it sees two |
|
|
118
|
+
| `--repeat <n>` | 1 | Measure each route `n` times, each from a fresh server, and judge it on all of them (max 10). The run takes `n` times as long. Routes whose repetitions disagree are reported `inconclusive`, and the spread of growth rates is printed either way. More cycles watch one process for longer; repetitions watch different ones, which is where the variance lives — three runs of one Next build measured 602, 826 and 875 MB retained, and a fourth measured 39 MB |
|
|
118
119
|
| `--requests <n>` | 5000 | Requests per cycle. Raises sensitivity as well as duration: the growth gate scales with it, down to a noise floor around 5000 |
|
|
119
120
|
| `--connections <n>` | 100 | Concurrent connections |
|
|
120
121
|
| `--idle <seconds>` | 30 | **Maximum** wait before each sample; the run continues as soon as the heap settles |
|
|
@@ -31,23 +31,32 @@ function isStepwiseGrowth(samples, deltas, growingCycles, mean, minGrowth) {
|
|
|
31
31
|
}
|
|
32
32
|
return maxDrawdown(samples) <= netGrowth * STEPWISE_MAX_DRAWDOWN_RATIO;
|
|
33
33
|
}
|
|
34
|
-
function isSaturating(deltas) {
|
|
34
|
+
function isSaturating(deltas, minGrowth) {
|
|
35
35
|
if (deltas.length < SATURATION_MIN_CYCLES) {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
const first = deltas[0];
|
|
39
39
|
const last = deltas[deltas.length - 1];
|
|
40
|
-
if (first === void 0 || last === void 0 || first
|
|
40
|
+
if (first === void 0 || last === void 0 || first < minGrowth) {
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const previous = deltas[i - 1];
|
|
46
|
-
if (current === void 0 || previous === void 0 || current >= previous) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
43
|
+
if (!deltas.every((delta) => delta > 0)) {
|
|
44
|
+
return false;
|
|
49
45
|
}
|
|
50
|
-
|
|
46
|
+
if (last > first * SATURATION_MAX_FINAL_RATIO) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return isHeadingDown(deltas);
|
|
50
|
+
}
|
|
51
|
+
function isHeadingDown(deltas) {
|
|
52
|
+
const midpoint = Math.floor(deltas.length / 2);
|
|
53
|
+
const head = deltas.slice(0, midpoint);
|
|
54
|
+
const tail = deltas.slice(midpoint);
|
|
55
|
+
if (head.length === 0 || tail.length === 0) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
59
|
+
return mean(tail) < mean(head);
|
|
51
60
|
}
|
|
52
61
|
function isTooLargeToAcquit(deltas, mean, minGrowth) {
|
|
53
62
|
const netGrowth = deltas.reduce((sum, delta) => sum + delta, 0);
|
|
@@ -71,10 +80,10 @@ function classifyShape(samples, options) {
|
|
|
71
80
|
const allGrow = deltas.every((d) => d >= minGrowth);
|
|
72
81
|
const anyFlatOrDown = deltas.some((d) => d <= 0);
|
|
73
82
|
const growingCycles = deltas.filter((d) => d >= minGrowth).length;
|
|
83
|
+
if (isSaturating(deltas, minGrowth)) {
|
|
84
|
+
return { verdict: "saturating", growthPerCycle: mean, deltas, source: "heap" };
|
|
85
|
+
}
|
|
74
86
|
if (allGrow) {
|
|
75
|
-
if (isSaturating(deltas)) {
|
|
76
|
-
return { verdict: "saturating", growthPerCycle: mean, deltas, source: "heap" };
|
|
77
|
-
}
|
|
78
87
|
return { verdict: "leak", growthPerCycle: mean, deltas, source: "heap" };
|
|
79
88
|
}
|
|
80
89
|
if (isStepwiseGrowth(samples, deltas, growingCycles, mean, minGrowth)) {
|
|
@@ -115,7 +124,8 @@ function effectiveVerdict(report) {
|
|
|
115
124
|
var VERDICT_WEAKENING = /* @__PURE__ */ new Set([
|
|
116
125
|
"near-threshold",
|
|
117
126
|
"spiky-growth",
|
|
118
|
-
"thin-evidence"
|
|
127
|
+
"thin-evidence",
|
|
128
|
+
"repetitions-disagree"
|
|
119
129
|
]);
|
|
120
130
|
function warrantsIssueDraft(report) {
|
|
121
131
|
return effectiveVerdict(report) === "leak" && !report.confidence.warnings.some((warning) => VERDICT_WEAKENING.has(warning.code));
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
minGrowthFor,
|
|
8
8
|
resolveCycles,
|
|
9
9
|
warrantsIssueDraft
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4FYSLLSX.js";
|
|
11
11
|
import {
|
|
12
12
|
assessPeakPressure,
|
|
13
13
|
describePeakPressure
|
|
@@ -92939,6 +92939,7 @@ var BUILD_COMMAND = "build";
|
|
|
92939
92939
|
var RUN_ONLY_FLAGS = [
|
|
92940
92940
|
["routes", "--routes"],
|
|
92941
92941
|
["cycles", "--cycles"],
|
|
92942
|
+
["repeat", "--repeat"],
|
|
92942
92943
|
["requests", "--requests"],
|
|
92943
92944
|
["connections", "--connections"],
|
|
92944
92945
|
["idleSeconds", "--idle"],
|
|
@@ -92967,6 +92968,12 @@ var FLAGS = [
|
|
|
92967
92968
|
help: "Only measure these routes \u2014 comma-separated templates or prefixes (e.g. /api,/dashboard)"
|
|
92968
92969
|
},
|
|
92969
92970
|
{ flag: "--cycles", value: "int", argName: "<n>", help: "Load cycles per route (default 4, minimum 3)" },
|
|
92971
|
+
{
|
|
92972
|
+
flag: "--repeat",
|
|
92973
|
+
value: "int",
|
|
92974
|
+
argName: "<n>",
|
|
92975
|
+
help: "Measure each route n times from a fresh server (default 1) \u2014 the run takes n times as long, and routes whose repetitions disagree are reported inconclusive"
|
|
92976
|
+
},
|
|
92970
92977
|
{ flag: "--requests", value: "int", argName: "<n>", help: "Requests per cycle (default 5000)" },
|
|
92971
92978
|
{ flag: "--connections", value: "int", argName: "<n>", help: "Concurrent connections (default 100)" },
|
|
92972
92979
|
{ flag: "--idle", value: "int", argName: "<seconds>", help: "Idle seconds before each sample (default 30)" },
|
|
@@ -93048,6 +93055,7 @@ interrupted, 1 on errors.
|
|
|
93048
93055
|
}
|
|
93049
93056
|
var LIMITS = {
|
|
93050
93057
|
"--cycles": 100,
|
|
93058
|
+
"--repeat": 10,
|
|
93051
93059
|
"--requests": 1e6,
|
|
93052
93060
|
"--connections": 1e4,
|
|
93053
93061
|
"--idle": 3600,
|
|
@@ -93088,6 +93096,7 @@ function applyNumericFlag(flag, value, options) {
|
|
|
93088
93096
|
);
|
|
93089
93097
|
}
|
|
93090
93098
|
if (flag === "--cycles") options.cycles = parsed;
|
|
93099
|
+
if (flag === "--repeat") options.repeat = parsed;
|
|
93091
93100
|
if (flag === "--requests") options.requests = parsed;
|
|
93092
93101
|
if (flag === "--connections") options.connections = parsed;
|
|
93093
93102
|
if (flag === "--idle") options.idleSeconds = parsed;
|
|
@@ -93100,6 +93109,7 @@ function applyFlag(spec, value, options) {
|
|
|
93100
93109
|
case "--routes":
|
|
93101
93110
|
return applyRoutesFlag(value, options);
|
|
93102
93111
|
case "--cycles":
|
|
93112
|
+
case "--repeat":
|
|
93103
93113
|
case "--requests":
|
|
93104
93114
|
case "--connections":
|
|
93105
93115
|
case "--idle":
|
|
@@ -93171,6 +93181,7 @@ function parseCliArgs(argv) {
|
|
|
93171
93181
|
appDir: "",
|
|
93172
93182
|
routes: null,
|
|
93173
93183
|
cycles: null,
|
|
93184
|
+
repeat: null,
|
|
93174
93185
|
requests: null,
|
|
93175
93186
|
connections: null,
|
|
93176
93187
|
idleSeconds: null,
|
|
@@ -94242,6 +94253,19 @@ function revalidationLines(route) {
|
|
|
94242
94253
|
` driven through ISR revalidation (revalidates every ${route.revalidatedEverySeconds}s; without it the load would serve the cache)`
|
|
94243
94254
|
];
|
|
94244
94255
|
}
|
|
94256
|
+
function repetitionLines(route) {
|
|
94257
|
+
const repetitions = route.repetitions;
|
|
94258
|
+
if (repetitions === void 0 || repetitions.length < 2) {
|
|
94259
|
+
return [];
|
|
94260
|
+
}
|
|
94261
|
+
const rates = repetitions.map((entry) => entry.growthPer1000Requests);
|
|
94262
|
+
const low = Math.min(...rates);
|
|
94263
|
+
const high = Math.max(...rates);
|
|
94264
|
+
const verdicts = [...new Set(repetitions.map((entry) => entry.verdict))];
|
|
94265
|
+
const agreement = verdicts.length === 1 ? `all ${repetitions.length} agreed on ${verdicts[0]}` : `they disagreed (${verdicts.join(", ")}), so no single verdict is reported`;
|
|
94266
|
+
const range = `${low >= 0 ? "+" : ""}${(low / MB2).toFixed(2)} to ${formatGrowth(high)}`;
|
|
94267
|
+
return [` across ${repetitions.length} repetitions: ${range} \u2014 ${agreement}`];
|
|
94268
|
+
}
|
|
94245
94269
|
function cacheLines(route) {
|
|
94246
94270
|
const lines = [];
|
|
94247
94271
|
if (route.trend.verdict === "saturating") {
|
|
@@ -94363,6 +94387,7 @@ function routeLines(route, parameters) {
|
|
|
94363
94387
|
` ${VERDICT_ICON[verdict]} ${route.route} ${verdict} (${formatGrowth(
|
|
94364
94388
|
route.growthPer1000Requests
|
|
94365
94389
|
)}) heap ${curve}${resolved}`,
|
|
94390
|
+
...repetitionLines(route),
|
|
94366
94391
|
...revalidationLines(route),
|
|
94367
94392
|
...cacheLines(route),
|
|
94368
94393
|
...abandonLines(route),
|
|
@@ -94752,7 +94777,7 @@ async function measureRoute(context, route, requestPath, index, pass = void 0) {
|
|
|
94752
94777
|
...options.warmupRequests !== void 0 && { warmupRequests: options.warmupRequests },
|
|
94753
94778
|
...options.loadRequests !== void 0 && { loadRequests: options.loadRequests },
|
|
94754
94779
|
...options.connections !== void 0 && { connections: options.connections },
|
|
94755
|
-
...pass !== void 0 ? { cycles: pass.cycles } : options.cycles !== void 0 && { cycles: options.cycles },
|
|
94780
|
+
...pass?.cycles !== void 0 ? { cycles: pass.cycles } : options.cycles !== void 0 && { cycles: options.cycles },
|
|
94756
94781
|
...options.idleMs !== void 0 && { idleMs: options.idleMs },
|
|
94757
94782
|
...options.maxOldSpaceMb !== void 0 && { maxOldSpaceMb: options.maxOldSpaceMb },
|
|
94758
94783
|
...headers !== void 0 && { headers },
|
|
@@ -94810,7 +94835,7 @@ async function measureRoute(context, route, requestPath, index, pass = void 0) {
|
|
|
94810
94835
|
route: route.path,
|
|
94811
94836
|
status: "measured",
|
|
94812
94837
|
requestPath,
|
|
94813
|
-
...pass !== void 0 && { resolvedWithCycles: pass.cycles },
|
|
94838
|
+
...pass?.cycles !== void 0 && { resolvedWithCycles: pass.cycles },
|
|
94814
94839
|
samples: result.samples,
|
|
94815
94840
|
memorySamples: result.memorySamples,
|
|
94816
94841
|
peaks: result.peaks,
|
|
@@ -94841,7 +94866,7 @@ async function measureRoute(context, route, requestPath, index, pass = void 0) {
|
|
|
94841
94866
|
};
|
|
94842
94867
|
}
|
|
94843
94868
|
async function writeEvidenceBundle(report, workDir) {
|
|
94844
|
-
const { renderHtmlReport } = await import("./html-report-
|
|
94869
|
+
const { renderHtmlReport } = await import("./html-report-O7ILFOF5.js");
|
|
94845
94870
|
const { renderIssueMarkdown } = await import("./issue-report-FPVOZ6HU.js");
|
|
94846
94871
|
for (const route of report.routes) {
|
|
94847
94872
|
if (route.status === "measured" && warrantsIssueDraft(route)) {
|
|
@@ -94879,7 +94904,7 @@ async function routeReportFor(context, route, index, total) {
|
|
|
94879
94904
|
progress(`not measuring ${label}: ${plan.reason}`);
|
|
94880
94905
|
return { route: route.path, status: "not-exercised", reason: plan.reason };
|
|
94881
94906
|
}
|
|
94882
|
-
return
|
|
94907
|
+
return measureRepeatedly(context, route, requestPath, index, label);
|
|
94883
94908
|
}
|
|
94884
94909
|
function reasonToResolve(verdict) {
|
|
94885
94910
|
switch (verdict) {
|
|
@@ -94892,12 +94917,74 @@ function reasonToResolve(verdict) {
|
|
|
94892
94917
|
return null;
|
|
94893
94918
|
}
|
|
94894
94919
|
}
|
|
94895
|
-
async function
|
|
94920
|
+
async function measureRepeatedly(context, route, requestPath, index, label) {
|
|
94921
|
+
const repeat = context.options.repeat ?? 1;
|
|
94922
|
+
if (repeat <= 1) {
|
|
94923
|
+
return measureWithResolution(context, route, requestPath, index, label);
|
|
94924
|
+
}
|
|
94925
|
+
const passes = [];
|
|
94926
|
+
for (let attempt = 1; attempt <= repeat; attempt += 1) {
|
|
94927
|
+
context.progress(`repetition ${attempt}/${repeat} of ${label}`);
|
|
94928
|
+
passes.push(
|
|
94929
|
+
await measureWithResolution(context, route, requestPath, index, label, `-rep${attempt}`)
|
|
94930
|
+
);
|
|
94931
|
+
if (context.options.signal?.aborted === true) {
|
|
94932
|
+
break;
|
|
94933
|
+
}
|
|
94934
|
+
}
|
|
94935
|
+
return aggregateRepetitions(passes);
|
|
94936
|
+
}
|
|
94937
|
+
function aggregateRepetitions(passes) {
|
|
94938
|
+
const first = passes[0];
|
|
94939
|
+
if (first === void 0) {
|
|
94940
|
+
return { route: "", status: "failed", reason: "no repetition produced a result" };
|
|
94941
|
+
}
|
|
94942
|
+
const measured = passes.filter((pass) => pass.status === "measured");
|
|
94943
|
+
if (measured.length !== passes.length || measured.length === 0) {
|
|
94944
|
+
return passes.find((pass) => pass.status !== "measured") ?? first;
|
|
94945
|
+
}
|
|
94946
|
+
const repetitions = measured.map((pass) => ({
|
|
94947
|
+
verdict: pass.trend.verdict,
|
|
94948
|
+
growthPer1000Requests: pass.trend.growthPerCycle / pass.requestsPerCycle * 1e3
|
|
94949
|
+
}));
|
|
94950
|
+
const verdicts = [...new Set(repetitions.map((entry) => entry.verdict))];
|
|
94951
|
+
const winner = measured[0];
|
|
94952
|
+
if (winner === void 0) {
|
|
94953
|
+
return first;
|
|
94954
|
+
}
|
|
94955
|
+
if (verdicts.length === 1) {
|
|
94956
|
+
return { ...winner, repetitions };
|
|
94957
|
+
}
|
|
94958
|
+
const observed = verdicts.join(", ");
|
|
94959
|
+
return {
|
|
94960
|
+
...winner,
|
|
94961
|
+
repetitions,
|
|
94962
|
+
confidence: {
|
|
94963
|
+
...winner.confidence,
|
|
94964
|
+
level: "low",
|
|
94965
|
+
warnings: [
|
|
94966
|
+
...winner.confidence.warnings,
|
|
94967
|
+
{
|
|
94968
|
+
code: "repetitions-disagree",
|
|
94969
|
+
detail: `${repetitions.length} repetitions of this route disagreed (${observed}) \u2014 the measurement did not settle, so no single verdict is reported`
|
|
94970
|
+
}
|
|
94971
|
+
],
|
|
94972
|
+
supersededVerdict: "inconclusive"
|
|
94973
|
+
}
|
|
94974
|
+
};
|
|
94975
|
+
}
|
|
94976
|
+
async function measureWithResolution(context, route, requestPath, index, label, repetitionSuffix = "") {
|
|
94896
94977
|
const { progress } = context;
|
|
94897
94978
|
try {
|
|
94898
94979
|
const asPath = requestPath === route.path ? "" : ` as ${requestPath}`;
|
|
94899
94980
|
progress(`measuring ${label}${asPath}`);
|
|
94900
|
-
const first = await measureRoute(
|
|
94981
|
+
const first = await measureRoute(
|
|
94982
|
+
context,
|
|
94983
|
+
route,
|
|
94984
|
+
requestPath,
|
|
94985
|
+
index,
|
|
94986
|
+
repetitionSuffix === "" ? void 0 : { dirSuffix: repetitionSuffix }
|
|
94987
|
+
);
|
|
94901
94988
|
const reason = first.status === "measured" ? reasonToResolve(effectiveVerdict(first)) : null;
|
|
94902
94989
|
if (first.status !== "measured" || context.options.resolveInconclusive === false || reason === null) {
|
|
94903
94990
|
return first;
|
|
@@ -94910,7 +94997,7 @@ async function measureWithResolution(context, route, requestPath, index, label)
|
|
|
94910
94997
|
try {
|
|
94911
94998
|
return await measureRoute(context, route, requestPath, index, {
|
|
94912
94999
|
cycles,
|
|
94913
|
-
dirSuffix:
|
|
95000
|
+
dirSuffix: `${repetitionSuffix}-resolve`
|
|
94914
95001
|
});
|
|
94915
95002
|
} catch (cause) {
|
|
94916
95003
|
progress(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
|
|
2
2
|
import {
|
|
3
3
|
effectiveVerdict
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4FYSLLSX.js";
|
|
5
5
|
import {
|
|
6
6
|
assessPeakPressure,
|
|
7
7
|
describePeakPressure
|
package/dist/cli-args.d.ts
CHANGED
|
@@ -2,6 +2,15 @@ export type CliRunOptions = {
|
|
|
2
2
|
appDir: string;
|
|
3
3
|
routes: string[] | null;
|
|
4
4
|
cycles: number | null;
|
|
5
|
+
/**
|
|
6
|
+
* How many times to measure each route, from a fresh server each time.
|
|
7
|
+
*
|
|
8
|
+
* More cycles watch one process for longer; repetitions watch different
|
|
9
|
+
* processes. The spreads that make a verdict unpublishable live between
|
|
10
|
+
* processes: vercel/next.js#84648 gave 602, 826 and 875 MB on three runs of
|
|
11
|
+
* one build and 39 MB on a fourth.
|
|
12
|
+
*/
|
|
13
|
+
repeat: number | null;
|
|
5
14
|
requests: number | null;
|
|
6
15
|
connections: number | null;
|
|
7
16
|
idleSeconds: number | null;
|
package/dist/cli.js
CHANGED
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
runSelfCheck,
|
|
23
23
|
unregisterChild,
|
|
24
24
|
validateTarget
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-JHOE2RCM.js";
|
|
26
26
|
import {
|
|
27
27
|
classifyTrend
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-4FYSLLSX.js";
|
|
29
29
|
import "./chunk-XHPUAMJG.js";
|
|
30
30
|
import "./chunk-6XYFBOL2.js";
|
|
31
31
|
|
|
@@ -927,6 +927,7 @@ async function main() {
|
|
|
927
927
|
...quickPreset,
|
|
928
928
|
...options.routes !== null && { routeFilter: options.routes },
|
|
929
929
|
...options.cycles !== null && { cycles: options.cycles },
|
|
930
|
+
...options.repeat !== null && { repeat: options.repeat },
|
|
930
931
|
...options.requests !== null && { loadRequests: options.requests },
|
|
931
932
|
...options.connections !== null && { connections: options.connections },
|
|
932
933
|
...options.idleSeconds !== null && { idleMs: options.idleSeconds * 1e3 },
|
package/dist/confidence.d.ts
CHANGED
|
@@ -20,7 +20,16 @@ import { type TrendResult, type TrendVerdict } from "./trend.js";
|
|
|
20
20
|
* settled at, so it carries warm-up's own retention rather than the app's
|
|
21
21
|
* resting size
|
|
22
22
|
*/
|
|
23
|
-
export type WarningCode = "unsettled" | "settle-unverified" | "load-incomplete" | "abandon-ineffective" | "abandon-before-response" | "spiky-growth" | "near-threshold" | "thin-evidence" | "near-heap-ceiling" | "warm-up-baseline"
|
|
23
|
+
export type WarningCode = "unsettled" | "settle-unverified" | "load-incomplete" | "abandon-ineffective" | "abandon-before-response" | "spiky-growth" | "near-threshold" | "thin-evidence" | "near-heap-ceiling" | "warm-up-baseline"
|
|
24
|
+
/**
|
|
25
|
+
* Repeated measurements of the same route did not agree.
|
|
26
|
+
*
|
|
27
|
+
* More cycles watch one process for longer; repetitions watch different
|
|
28
|
+
* ones, and that is where the spread lives — vercel/next.js#84648 gave 602,
|
|
29
|
+
* 826 and 875 MB on three runs of one build and 39 MB on a fourth. A verdict
|
|
30
|
+
* a second run contradicts is not a verdict.
|
|
31
|
+
*/
|
|
32
|
+
| "repetitions-disagree";
|
|
24
33
|
export type MeasurementWarning = {
|
|
25
34
|
code: WarningCode;
|
|
26
35
|
detail: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
|
|
2
2
|
import {
|
|
3
3
|
renderHtmlReport
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-YLHE4N5G.js";
|
|
5
|
+
import "./chunk-4FYSLLSX.js";
|
|
6
6
|
import "./chunk-XHPUAMJG.js";
|
|
7
7
|
import "./chunk-6XYFBOL2.js";
|
|
8
8
|
export {
|
package/dist/index.js
CHANGED
|
@@ -41,13 +41,13 @@ import {
|
|
|
41
41
|
sourceIndexAt,
|
|
42
42
|
summarizeBaseline,
|
|
43
43
|
validateTarget
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-JHOE2RCM.js";
|
|
45
45
|
import {
|
|
46
46
|
renderHtmlReport
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-YLHE4N5G.js";
|
|
48
48
|
import {
|
|
49
49
|
classifyTrend
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-4FYSLLSX.js";
|
|
51
51
|
import {
|
|
52
52
|
renderIssueMarkdown
|
|
53
53
|
} from "./chunk-AUUMRTZZ.js";
|
package/dist/runner.d.ts
CHANGED
|
@@ -8,7 +8,13 @@ import { type PrerenderManifest } from "./manifests.js";
|
|
|
8
8
|
import { extractModuleRegistry } from "./module-registry.js";
|
|
9
9
|
import { runRitual, type LoadOutcome, type PeakSample, type PhaseTiming, type SettleOutcome } from "./ritual.js";
|
|
10
10
|
import { readNextVersion, type MatchedSignature } from "./signatures.js";
|
|
11
|
-
import { type TrendResult } from "./trend.js";
|
|
11
|
+
import { type TrendResult, type TrendVerdict } from "./trend.js";
|
|
12
|
+
/** What one repetition of a route concluded, for disclosing the spread. */
|
|
13
|
+
export type RepetitionSummary = {
|
|
14
|
+
verdict: TrendVerdict;
|
|
15
|
+
/** Growth normalized by traffic, so repetitions stay comparable. */
|
|
16
|
+
growthPer1000Requests: number;
|
|
17
|
+
};
|
|
12
18
|
export type RouteReport = {
|
|
13
19
|
route: string;
|
|
14
20
|
status: "skipped";
|
|
@@ -79,6 +85,12 @@ export type RouteReport = {
|
|
|
79
85
|
unreclaimedTrend: TrendResult;
|
|
80
86
|
/** Requests each cycle served — what the growth rates normalize by. */
|
|
81
87
|
requestsPerCycle: number;
|
|
88
|
+
/**
|
|
89
|
+
* One entry per repetition when `repeat` was greater than 1, in the
|
|
90
|
+
* order they ran. Absent for a single measurement, so `run.json` keeps
|
|
91
|
+
* its existing shape unless repetition was asked for.
|
|
92
|
+
*/
|
|
93
|
+
repetitions?: RepetitionSummary[];
|
|
82
94
|
/**
|
|
83
95
|
* The early-disconnect regime, when the route asked for one. Recorded
|
|
84
96
|
* because a curve measured with cuts landing mid-stream and one measured
|
|
@@ -227,6 +239,13 @@ export type RunOptions = {
|
|
|
227
239
|
* call it. Default true: the run should answer the question it was asked.
|
|
228
240
|
*/
|
|
229
241
|
resolveInconclusive?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* How many times to measure each route, each from a fresh server. Default 1.
|
|
244
|
+
*
|
|
245
|
+
* Distinct from `cycles`, which watches a single process for longer. The
|
|
246
|
+
* variance that makes a number unpublishable is between processes.
|
|
247
|
+
*/
|
|
248
|
+
repeat?: number;
|
|
230
249
|
/**
|
|
231
250
|
* Growth the self-check measured on its planted leak, when one ran before
|
|
232
251
|
* this measurement. Its presence is what lets the report say a `stable`
|
|
@@ -265,6 +284,17 @@ export type RunnerDeps = {
|
|
|
265
284
|
};
|
|
266
285
|
export declare function freePort(): Promise<number>;
|
|
267
286
|
export declare function routeSlug(route: string): string;
|
|
287
|
+
/**
|
|
288
|
+
* Combines repeated measurements of one route into the verdict it earned.
|
|
289
|
+
*
|
|
290
|
+
* Unanimity or nothing. Taking the most severe would let one noisy repetition
|
|
291
|
+
* in five accuse a healthy route, and a false accusation is the expensive
|
|
292
|
+
* error; taking the majority would discard exactly the disagreement the
|
|
293
|
+
* repetitions were run to find. When they disagree the honest report is that
|
|
294
|
+
* the measurement did not settle, which is what `inconclusive` means and what
|
|
295
|
+
* already routes to re-measurement and away from issue drafts.
|
|
296
|
+
*/
|
|
297
|
+
export declare function aggregateRepetitions(passes: readonly RouteReport[]): RouteReport;
|
|
268
298
|
/**
|
|
269
299
|
* Full measurement run: validate the target, discover routes, run the ritual
|
|
270
300
|
* per route in a fresh process, diff snapshots for non-stable verdicts, and
|