next-leak 0.4.2 → 0.5.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/dist/{chunk-YDMJVVWG.js → chunk-IDSNTUKC.js} +78 -29
- package/dist/cli.js +1 -1
- package/dist/control-client.d.ts +12 -0
- package/dist/index.js +1 -1
- package/dist/load.d.ts +0 -6
- package/dist/ritual.d.ts +3 -0
- package/package.json +1 -1
|
@@ -3657,10 +3657,10 @@ var require_ansi_styles = __commonJS({
|
|
|
3657
3657
|
};
|
|
3658
3658
|
var ansi2ansi = (n) => n;
|
|
3659
3659
|
var rgb2rgb = (r, g, b) => [r, g, b];
|
|
3660
|
-
var setLazyProperty = (object, property,
|
|
3660
|
+
var setLazyProperty = (object, property, get2) => {
|
|
3661
3661
|
Object.defineProperty(object, property, {
|
|
3662
3662
|
get: () => {
|
|
3663
|
-
const value =
|
|
3663
|
+
const value = get2();
|
|
3664
3664
|
Object.defineProperty(object, property, {
|
|
3665
3665
|
value,
|
|
3666
3666
|
enumerable: true,
|
|
@@ -36043,18 +36043,18 @@ var require_gen_mapping_umd = __commonJS({
|
|
|
36043
36043
|
function cast(set) {
|
|
36044
36044
|
return set;
|
|
36045
36045
|
}
|
|
36046
|
-
function
|
|
36046
|
+
function get2(setarr, key) {
|
|
36047
36047
|
return cast(setarr)._indexes[key];
|
|
36048
36048
|
}
|
|
36049
36049
|
function put(setarr, key) {
|
|
36050
|
-
const index =
|
|
36050
|
+
const index = get2(setarr, key);
|
|
36051
36051
|
if (index !== void 0) return index;
|
|
36052
36052
|
const { array, _indexes: indexes } = cast(setarr);
|
|
36053
36053
|
const length = array.push(key);
|
|
36054
36054
|
return indexes[key] = length - 1;
|
|
36055
36055
|
}
|
|
36056
36056
|
function remove(setarr, key) {
|
|
36057
|
-
const index =
|
|
36057
|
+
const index = get2(setarr, key);
|
|
36058
36058
|
if (index === void 0) return;
|
|
36059
36059
|
const { array, _indexes: indexes } = cast(setarr);
|
|
36060
36060
|
for (let i = index + 1; i < array.length; i++) {
|
|
@@ -60212,7 +60212,7 @@ var require_family = __commonJS({
|
|
|
60212
60212
|
});
|
|
60213
60213
|
exports2._getKey = _getKey;
|
|
60214
60214
|
exports2._getPattern = _getPattern;
|
|
60215
|
-
exports2.get =
|
|
60215
|
+
exports2.get = get2;
|
|
60216
60216
|
exports2.getAllNextSiblings = getAllNextSiblings;
|
|
60217
60217
|
exports2.getAllPrevSiblings = getAllPrevSiblings;
|
|
60218
60218
|
exports2.getAssignmentIdentifiers = getAssignmentIdentifiers;
|
|
@@ -60443,7 +60443,7 @@ var require_family = __commonJS({
|
|
|
60443
60443
|
}
|
|
60444
60444
|
return siblings;
|
|
60445
60445
|
}
|
|
60446
|
-
function
|
|
60446
|
+
function get2(key, context = true) {
|
|
60447
60447
|
if (context === true) context = this.context;
|
|
60448
60448
|
const parts = key.split(".");
|
|
60449
60449
|
if (parts.length === 1) {
|
|
@@ -65957,7 +65957,7 @@ var require_dist5 = __commonJS({
|
|
|
65957
65957
|
var pathVar = getPathVar(env, platform);
|
|
65958
65958
|
var separator = getSeparator(platform);
|
|
65959
65959
|
var originalPath = env[pathVar];
|
|
65960
|
-
return { push, unshift, get, restore };
|
|
65960
|
+
return { push, unshift, get: get2, restore };
|
|
65961
65961
|
function push() {
|
|
65962
65962
|
for (var _len = arguments.length, paths = Array(_len), _key = 0; _key < _len; _key++) {
|
|
65963
65963
|
paths[_key] = arguments[_key];
|
|
@@ -65970,12 +65970,12 @@ var require_dist5 = __commonJS({
|
|
|
65970
65970
|
}
|
|
65971
65971
|
return change(false, paths);
|
|
65972
65972
|
}
|
|
65973
|
-
function
|
|
65973
|
+
function get2() {
|
|
65974
65974
|
return env[pathVar];
|
|
65975
65975
|
}
|
|
65976
65976
|
function restore() {
|
|
65977
65977
|
env[pathVar] = originalPath;
|
|
65978
|
-
return
|
|
65978
|
+
return get2();
|
|
65979
65979
|
}
|
|
65980
65980
|
function change(append, paths) {
|
|
65981
65981
|
if (!append) {
|
|
@@ -65986,7 +65986,7 @@ var require_dist5 = __commonJS({
|
|
|
65986
65986
|
addExistingPath(pathArray, env[pathVar], append);
|
|
65987
65987
|
env[pathVar] = pathArray.join(separator);
|
|
65988
65988
|
});
|
|
65989
|
-
return
|
|
65989
|
+
return get2();
|
|
65990
65990
|
}
|
|
65991
65991
|
}
|
|
65992
65992
|
function getPathArray(pathToAdd) {
|
|
@@ -86350,7 +86350,7 @@ var require_form_data = __commonJS({
|
|
|
86350
86350
|
var CombinedStream = require_combined_stream();
|
|
86351
86351
|
var util = __require("util");
|
|
86352
86352
|
var path7 = __require("path");
|
|
86353
|
-
var
|
|
86353
|
+
var http2 = __require("http");
|
|
86354
86354
|
var https = __require("https");
|
|
86355
86355
|
var parseUrl = __require("url").parse;
|
|
86356
86356
|
var fs = __require("fs");
|
|
@@ -86625,7 +86625,7 @@ var require_form_data = __commonJS({
|
|
|
86625
86625
|
if (options.protocol === "https:") {
|
|
86626
86626
|
request2 = https.request(options);
|
|
86627
86627
|
} else {
|
|
86628
|
-
request2 =
|
|
86628
|
+
request2 = http2.request(options);
|
|
86629
86629
|
}
|
|
86630
86630
|
this.getLength(function(err, length) {
|
|
86631
86631
|
if (err && err !== "Unknown stream") {
|
|
@@ -93208,9 +93208,6 @@ async function runLoadPhase(options) {
|
|
|
93208
93208
|
amount: options.amount,
|
|
93209
93209
|
connections: options.connections,
|
|
93210
93210
|
...options.headers !== void 0 && { headers: options.headers },
|
|
93211
|
-
...options.abandonAfterMs !== void 0 && {
|
|
93212
|
-
timeout: Math.max(Math.ceil(options.abandonAfterMs / 1e3), 1)
|
|
93213
|
-
},
|
|
93214
93211
|
...unique && { idReplacement: true }
|
|
93215
93212
|
});
|
|
93216
93213
|
const result = {
|
|
@@ -93221,8 +93218,7 @@ async function runLoadPhase(options) {
|
|
|
93221
93218
|
timeouts: raw.timeouts,
|
|
93222
93219
|
durationSeconds: raw.duration
|
|
93223
93220
|
};
|
|
93224
|
-
const
|
|
93225
|
-
const failures = Math.max(options.amount - result.ok2xx - expected, 0);
|
|
93221
|
+
const failures = Math.max(options.amount - result.ok2xx, 0);
|
|
93226
93222
|
const ratio = options.amount === 0 ? 0 : failures / options.amount;
|
|
93227
93223
|
if (ratio > (options.maxErrorRatio ?? 0.01)) {
|
|
93228
93224
|
const unanswered = failures - result.non2xx - result.errors - result.timeouts;
|
|
@@ -93239,6 +93235,7 @@ async function runLoadPhase(options) {
|
|
|
93239
93235
|
import { mkdir } from "fs/promises";
|
|
93240
93236
|
|
|
93241
93237
|
// src/control-client.ts
|
|
93238
|
+
import http from "http";
|
|
93242
93239
|
import { z as z5 } from "zod";
|
|
93243
93240
|
var sampleSchema = z5.object({
|
|
93244
93241
|
gcExposed: z5.boolean(),
|
|
@@ -93254,19 +93251,70 @@ var ControlError = class extends Error {
|
|
|
93254
93251
|
this.name = "ControlError";
|
|
93255
93252
|
}
|
|
93256
93253
|
};
|
|
93257
|
-
|
|
93258
|
-
|
|
93259
|
-
|
|
93260
|
-
|
|
93261
|
-
|
|
93262
|
-
|
|
93263
|
-
|
|
93254
|
+
var DEADLINES_MS = {
|
|
93255
|
+
"/mem": 3e4,
|
|
93256
|
+
"/gc": 18e4,
|
|
93257
|
+
"/snapshot": 18e5
|
|
93258
|
+
};
|
|
93259
|
+
var DEFAULT_DEADLINE_MS = 3e4;
|
|
93260
|
+
function deadlineFor(pathname) {
|
|
93261
|
+
const operation = pathname.split("?")[0] ?? pathname;
|
|
93262
|
+
return DEADLINES_MS[operation] ?? DEFAULT_DEADLINE_MS;
|
|
93263
|
+
}
|
|
93264
|
+
function get(port, pathname, deadlineMs) {
|
|
93265
|
+
return new Promise((resolve, reject) => {
|
|
93266
|
+
let settled = false;
|
|
93267
|
+
const settle = (outcome) => {
|
|
93268
|
+
if (settled) {
|
|
93269
|
+
return;
|
|
93270
|
+
}
|
|
93271
|
+
settled = true;
|
|
93272
|
+
clearTimeout(timer);
|
|
93273
|
+
if ("ok" in outcome) {
|
|
93274
|
+
resolve(outcome.ok);
|
|
93275
|
+
} else {
|
|
93276
|
+
reject(outcome.error);
|
|
93277
|
+
}
|
|
93278
|
+
};
|
|
93279
|
+
const request2 = http.get({ host: "127.0.0.1", port, path: pathname }, (response) => {
|
|
93280
|
+
const chunks = [];
|
|
93281
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
93282
|
+
response.once(
|
|
93283
|
+
"end",
|
|
93284
|
+
() => settle({
|
|
93285
|
+
ok: { status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") }
|
|
93286
|
+
})
|
|
93287
|
+
);
|
|
93288
|
+
});
|
|
93289
|
+
const timer = setTimeout(() => {
|
|
93290
|
+
settle({
|
|
93291
|
+
error: new ControlError(
|
|
93292
|
+
`control channel ${pathname} on port ${port} did not answer within ${Math.round(deadlineMs / 1e3)}s \u2014 the measured process is wedged, or this snapshot is larger than anything this tool has been validated on`
|
|
93293
|
+
)
|
|
93294
|
+
});
|
|
93295
|
+
request2.destroy();
|
|
93296
|
+
}, deadlineMs);
|
|
93297
|
+
timer.unref();
|
|
93298
|
+
request2.once(
|
|
93299
|
+
"error",
|
|
93300
|
+
(cause) => settle({
|
|
93301
|
+
error: new ControlError(
|
|
93302
|
+
`control channel ${pathname} on port ${port} did not answer (${cause.message}) \u2014 the measured process is gone or its event loop is blocked`
|
|
93303
|
+
)
|
|
93304
|
+
})
|
|
93264
93305
|
);
|
|
93265
|
-
}
|
|
93266
|
-
|
|
93306
|
+
});
|
|
93307
|
+
}
|
|
93308
|
+
async function request(port, pathname, deadlineMs = deadlineFor(pathname)) {
|
|
93309
|
+
const response = await get(port, pathname, deadlineMs);
|
|
93310
|
+
if (response.status !== 200) {
|
|
93267
93311
|
throw new ControlError(`control channel ${pathname} responded ${response.status}`);
|
|
93268
93312
|
}
|
|
93269
|
-
|
|
93313
|
+
try {
|
|
93314
|
+
return JSON.parse(response.body);
|
|
93315
|
+
} catch {
|
|
93316
|
+
throw new ControlError(`control channel ${pathname} answered with malformed JSON`);
|
|
93317
|
+
}
|
|
93270
93318
|
}
|
|
93271
93319
|
async function requestGc(port) {
|
|
93272
93320
|
const sample = sampleSchema.parse(await request(port, "/gc"));
|
|
@@ -93444,6 +93492,7 @@ async function waitUntilSettled(controlPort, maxIdleMs, deps) {
|
|
|
93444
93492
|
var defaultDeps = {
|
|
93445
93493
|
launch: launchInstrumented,
|
|
93446
93494
|
load: runLoadPhase,
|
|
93495
|
+
abandon: runAbandonPhase,
|
|
93447
93496
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
93448
93497
|
readMemory: requestMemory
|
|
93449
93498
|
};
|
|
@@ -93502,7 +93551,7 @@ async function runRitual(options, deps = defaultDeps) {
|
|
|
93502
93551
|
});
|
|
93503
93552
|
return;
|
|
93504
93553
|
}
|
|
93505
|
-
const outcome = await
|
|
93554
|
+
const outcome = await deps.abandon({
|
|
93506
93555
|
url: `http://127.0.0.1:${app.appPort}${options.route}`,
|
|
93507
93556
|
amount,
|
|
93508
93557
|
connections,
|
package/dist/cli.js
CHANGED
package/dist/control-client.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { HeapSample } from "./control-server.js";
|
|
2
|
+
declare function deadlineFor(pathname: string): number;
|
|
3
|
+
declare function request(port: number, pathname: string, deadlineMs?: number): Promise<unknown>;
|
|
2
4
|
/** Forces GC in the measured process and returns a settled memory sample. */
|
|
3
5
|
export declare function requestGc(port: number): Promise<HeapSample>;
|
|
4
6
|
/**
|
|
@@ -11,3 +13,13 @@ export declare function requestSnapshot(port: number, name: string): Promise<{
|
|
|
11
13
|
file: string;
|
|
12
14
|
sample: HeapSample;
|
|
13
15
|
}>;
|
|
16
|
+
/** Test seam: the production deadlines would make a hung-server test take minutes. */
|
|
17
|
+
export declare const requestWithDeadline: typeof request;
|
|
18
|
+
/**
|
|
19
|
+
* Test seam for the deadline table itself. A mutation run showed the mapping
|
|
20
|
+
* could quietly send `/snapshot?name=x` to the 30-second default instead of
|
|
21
|
+
* its 30-minute bound — reinstating, from the inside, the exact ceiling this
|
|
22
|
+
* client exists to remove.
|
|
23
|
+
*/
|
|
24
|
+
export declare const deadlineForOperation: typeof deadlineFor;
|
|
25
|
+
export {};
|
package/dist/index.js
CHANGED
package/dist/load.d.ts
CHANGED
|
@@ -5,12 +5,6 @@ export type LoadPhaseOptions = {
|
|
|
5
5
|
connections: number;
|
|
6
6
|
/** Sent with every request (compression, cookies, auth). */
|
|
7
7
|
headers?: Record<string, string>;
|
|
8
|
-
/**
|
|
9
|
-
* Give up on each request after this many milliseconds, emulating clients
|
|
10
|
-
* that disconnect before the response arrives. Abandoned requests are
|
|
11
|
-
* expected, so they do not count against the error budget.
|
|
12
|
-
*/
|
|
13
|
-
abandonAfterMs?: number;
|
|
14
8
|
/**
|
|
15
9
|
* Maximum tolerated ratio of non-2xx responses plus socket errors before
|
|
16
10
|
* the phase fails. A route that errors under load must fail the run, not
|
package/dist/ritual.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HeapSample } from "./control-server.js";
|
|
2
2
|
import { launchInstrumented } from "./launcher.js";
|
|
3
|
+
import { runAbandonPhase } from "./abandon-load.js";
|
|
3
4
|
import { runLoadPhase } from "./load.js";
|
|
4
5
|
import { type TrendResult } from "./trend.js";
|
|
5
6
|
export type RitualOptions = {
|
|
@@ -103,6 +104,8 @@ export type RitualResult = {
|
|
|
103
104
|
export type RitualDeps = {
|
|
104
105
|
launch: typeof launchInstrumented;
|
|
105
106
|
load: typeof runLoadPhase;
|
|
107
|
+
/** Early-disconnect load; a seam like `load`, for the same reasons. */
|
|
108
|
+
abandon: typeof runAbandonPhase;
|
|
106
109
|
sleep: (ms: number) => Promise<void>;
|
|
107
110
|
/** GC-free read, polled while the app is under load. */
|
|
108
111
|
readMemory: (port: number) => Promise<HeapSample>;
|