@mark1russell7/rigor-chaos 0.1.6 → 0.2.1
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 +9 -0
- package/build/faults.d.ts +24 -3
- package/build/faults.d.ts.map +1 -1
- package/build/faults.js +34 -4
- package/build/faults.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ A fault plan tells a proxy which faults to put into which connections, and at wh
|
|
|
46
46
|
| `split` | The frame goes out in `pieces` writes, with `gapMs` between two writes. |
|
|
47
47
|
| `reorder` | The next `count` frames go out in the reverse order. After `holdMs`, it sends the frames that it holds. |
|
|
48
48
|
| `duplicate` | The frame goes out `1 + copies` times. |
|
|
49
|
+
| `answer` | It writes an HTTP/1.1 response with `status`, `fields` and `body` to the client, and ends both directions with a FIN. |
|
|
49
50
|
|
|
50
51
|
Each fault names its connection (the number of the connection, from 0), its direction (`up` to the server, `down` to the client) and its point. A point is a time in ms from the start of the connection, a count of bytes, or a count of frames.
|
|
51
52
|
|
|
@@ -59,6 +60,14 @@ const plan = faultPlan({ connections: 20, perConnection: 3, kinds: { split: 2, d
|
|
|
59
60
|
- Each draw is an integer, thus the same seed gives the same plan on each platform.
|
|
60
61
|
- `ranges` sets the interval of each parameter. `DEFAULT_FAULT_RANGES` gives the intervals that a spec does not set.
|
|
61
62
|
- `faultPlanOf` makes a plan from faults that a person wrote. `checkFaultPlan` gives each problem of a plan from a record.
|
|
63
|
+
- A spec does not draw an `answer`, because a person chooses its status and its fields. Write each answer in a plan with `faultPlanOf`.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { faultPlanOf } from "@mark1russell7/rigor-chaos";
|
|
67
|
+
|
|
68
|
+
// The client gets a 429 in place of the response of the server.
|
|
69
|
+
const plan = faultPlanOf([{ connection: 0, direction: "down", at: { frame: 0 }, kind: "answer", status: 429, fields: { "retry-after": "120" } }]);
|
|
70
|
+
```
|
|
62
71
|
|
|
63
72
|
## A closed-loop record
|
|
64
73
|
|
package/build/faults.d.ts
CHANGED
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** This value is the format of a fault plan. */
|
|
9
9
|
export declare const FAULT_PLAN_FORMAT = "rigor-faults/1";
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* These are the kinds of faults that a spec draws, in the order that a weight list of a spec uses. The kind `answer` is
|
|
12
|
+
* not in this list, because its parameters are the decision of a person. Only a written plan has it.
|
|
13
|
+
*/
|
|
11
14
|
export declare const FAULT_KINDS: readonly ["delay", "stall", "cut", "reset", "split", "reorder", "duplicate"];
|
|
12
|
-
/** A kind of fault. */
|
|
15
|
+
/** A kind of fault that a spec draws. */
|
|
13
16
|
export type FaultKind = (typeof FAULT_KINDS)[number];
|
|
17
|
+
/** These field names are not fields of an `answer`, because the proxy writes them. */
|
|
18
|
+
export declare const ANSWER_RESERVED_FIELDS: readonly ["connection", "content-length", "transfer-encoding"];
|
|
14
19
|
/** The direction of a stream. `up` goes from the client to the server, and `down` goes from the server to the client. */
|
|
15
20
|
export type Direction = "up" | "down";
|
|
16
21
|
/**
|
|
@@ -41,6 +46,9 @@ export interface FaultPoint {
|
|
|
41
46
|
* - `split`: the frame goes out in `pieces` writes, with `gapMs` between two writes.
|
|
42
47
|
* - `reorder`: the next `count` frames go out in the reverse order. After `holdMs`, the proxy sends the frames that it holds.
|
|
43
48
|
* - `duplicate`: the frame goes out `1 + copies` times.
|
|
49
|
+
* - `answer`: the proxy writes an HTTP/1.1 response to the client, and ends both directions with a FIN, as a `cut`.
|
|
50
|
+
* The bytes of both directions after the point do not go through. On `up` at frame 0, the server does not get the
|
|
51
|
+
* request. On `down` at frame 0, the answer replaces the response of the server. A spec does not draw an answer.
|
|
44
52
|
*/
|
|
45
53
|
export type Fault = FaultPoint & ({
|
|
46
54
|
readonly kind: "delay";
|
|
@@ -64,7 +72,20 @@ export type Fault = FaultPoint & ({
|
|
|
64
72
|
} | {
|
|
65
73
|
readonly kind: "duplicate";
|
|
66
74
|
readonly copies: number;
|
|
67
|
-
});
|
|
75
|
+
} | AnswerFault);
|
|
76
|
+
/**
|
|
77
|
+
* The parameters of an `answer`. The proxy writes the status line, the fields in their order, `content-length` and
|
|
78
|
+
* `connection: close`. A response with the status 204 or 304 has no body and no `content-length`.
|
|
79
|
+
*/
|
|
80
|
+
export interface AnswerFault {
|
|
81
|
+
readonly kind: "answer";
|
|
82
|
+
/** The status of the response, from 200 to 599. */
|
|
83
|
+
readonly status: number;
|
|
84
|
+
/** The fields of the response, for example `retry-after` or `etag`. The names of `ANSWER_RESERVED_FIELDS` are not fields. */
|
|
85
|
+
readonly fields?: Readonly<Record<string, string>>;
|
|
86
|
+
/** The body of the response, as text. The proxy writes it in UTF-8. */
|
|
87
|
+
readonly body?: string;
|
|
88
|
+
}
|
|
68
89
|
/** A fault plan is data, thus a record can keep the plan that a proxy applied. */
|
|
69
90
|
export interface FaultPlan {
|
|
70
91
|
readonly format: typeof FAULT_PLAN_FORMAT;
|
package/build/faults.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"faults.d.ts","names":[],"sources":["../src/faults.ts"],"mappings":";;;;;;;;qBAWa;;
|
|
1
|
+
{"version":3,"file":"faults.d.ts","names":[],"sources":["../src/faults.ts"],"mappings":";;;;;;;;qBAWa;;;;;qBAMA;;YAGD,oBAAoB;;qBAGnB;;YAGD;;;;;;YAOA;WAAqB;;WAA0B;;WAA4B;;;iBAGtE;WACN;WACA,WAAW;WACX,IAAI;;;;;;;;;;;;;;;;YAiBH,QAAQ;WAEH;WAAwB;WAAqB;;WAC7C;WAAwB;;WACxB;;WACA;;WACA;WAAwB;WAAyB;;WACjD;WAA0B;WAAwB;;WAClD;WAA4B;IACvC;;;;;iBAOW;WACN;;WAEA;;WAEA,SAAS,SAAS;;WAElB;;;iBAIM;WACN,eAAe;;WAEf;;WAEA,iBAAiB;;;YAIhB;;iBAGK;WACN,SAAS;WACT,YAAY;WACZ,SAAS;WACT,aAAa;WACb,YAAY;WACZ,cAAc;WACd,eAAe;WACf,iBAAiB;;;qBAIf,sBAAsB;;iBAYlB;;WAEN;;WAEA;;WAEA,OAAO,QAAQ,SAAS,OAAO;;WAE/B,QAAQ;;WAER,sBAAsB;;WAEtB,SAAS,QAAQ;;;wBAOZ,UAAU,IAAI;WAAqB;WAA2C;;;wBAsD9E,WAAW,OAAO;;wBAmBlB,eAAe,MAAM;;;;;wBAcrB,YAAY,iBAAiB,UAAU;;wBAQvC,SAAS,MAAM,WAAW,8BAA8B;;wBAiBxD,eAAe,MAAM;;;;;;wBAqCrB,UAAU,MAAM,WAAW,wBAAwB"}
|
package/build/faults.js
CHANGED
|
@@ -9,7 +9,10 @@ import { random } from "@mark1russell7/rigor-generators";
|
|
|
9
9
|
*/
|
|
10
10
|
/** This value is the format of a fault plan. */
|
|
11
11
|
const FAULT_PLAN_FORMAT = "rigor-faults/1";
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* These are the kinds of faults that a spec draws, in the order that a weight list of a spec uses. The kind `answer` is
|
|
14
|
+
* not in this list, because its parameters are the decision of a person. Only a written plan has it.
|
|
15
|
+
*/
|
|
13
16
|
const FAULT_KINDS = [
|
|
14
17
|
"delay",
|
|
15
18
|
"stall",
|
|
@@ -19,6 +22,12 @@ const FAULT_KINDS = [
|
|
|
19
22
|
"reorder",
|
|
20
23
|
"duplicate"
|
|
21
24
|
];
|
|
25
|
+
/** These field names are not fields of an `answer`, because the proxy writes them. */
|
|
26
|
+
const ANSWER_RESERVED_FIELDS = [
|
|
27
|
+
"connection",
|
|
28
|
+
"content-length",
|
|
29
|
+
"transfer-encoding"
|
|
30
|
+
];
|
|
22
31
|
/** These intervals are the default parameters of a spec. */
|
|
23
32
|
const DEFAULT_FAULT_RANGES = {
|
|
24
33
|
delayMs: [50, 2e3],
|
|
@@ -89,14 +98,34 @@ const PARAMETERS = {
|
|
|
89
98
|
1
|
|
90
99
|
]]
|
|
91
100
|
};
|
|
101
|
+
/** A token of HTTP: the characters of a field name. */
|
|
102
|
+
const TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
103
|
+
/** The value of a field: visible ASCII characters, spaces and tabs, with no space or tab at the ends. */
|
|
104
|
+
const FIELD_VALUE = /^(?:[\x21-\x7e](?:[\x20\x09\x21-\x7e]*[\x21-\x7e])?)?$/;
|
|
105
|
+
/** This function gives each problem of the parameters of an `answer`. */
|
|
106
|
+
function answerProblems(fault) {
|
|
107
|
+
const out = [];
|
|
108
|
+
if (!isWhole(fault.status, 200) || fault.status > 599) out.push(`The answer field status is a whole number from 200 to 599, not ${String(fault.status)}.`);
|
|
109
|
+
const fields = fault.fields ?? {};
|
|
110
|
+
if (typeof fields !== "object" || fields === null || Array.isArray(fields)) out.push("The answer field fields is an object of names and values.");
|
|
111
|
+
else for (const [name, value] of Object.entries(fields)) {
|
|
112
|
+
if (!TOKEN.test(name)) out.push(`The answer field name ${JSON.stringify(name)} is not a token of HTTP.`);
|
|
113
|
+
else if (ANSWER_RESERVED_FIELDS.includes(name.toLowerCase())) out.push(`The answer does not set the field ${name}, because the proxy writes it.`);
|
|
114
|
+
if (typeof value !== "string" || !FIELD_VALUE.test(value)) out.push(`The value of the answer field ${name} is text with no line end and no space at the ends, not ${JSON.stringify(value)}.`);
|
|
115
|
+
}
|
|
116
|
+
if (fault.body !== void 0 && typeof fault.body !== "string") out.push("The answer field body is text.");
|
|
117
|
+
else if ((fault.status === 204 || fault.status === 304) && (fault.body ?? "") !== "") out.push(`A response with the status ${fault.status} has no body.`);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
92
120
|
/** This function gives each problem of one fault. An empty list means that the fault is valid. */
|
|
93
121
|
function checkFault(fault) {
|
|
94
122
|
const out = [];
|
|
95
123
|
if (!isWhole(fault.connection, 0)) out.push(`The connection is a whole number of 0 or more, not ${String(fault.connection)}.`);
|
|
96
124
|
if (fault.direction !== "up" && fault.direction !== "down") out.push(`The direction is up or down, not ${String(fault.direction)}.`);
|
|
97
125
|
if (triggerOf(fault.at) === void 0) out.push(`The point has one key of ms, byte or frame, with a value of 0 or more, not ${JSON.stringify(fault.at)}.`);
|
|
126
|
+
if (fault.kind === "answer") return [...out, ...answerProblems(fault)];
|
|
98
127
|
if (!FAULT_KINDS.includes(fault.kind)) {
|
|
99
|
-
out.push(`The kind is one of ${FAULT_KINDS.join(", ")}, not ${String(fault.kind)}.`);
|
|
128
|
+
out.push(`The kind is one of ${FAULT_KINDS.join(", ")} or answer, not ${String(fault.kind)}.`);
|
|
100
129
|
return out;
|
|
101
130
|
}
|
|
102
131
|
for (const [name, type, least] of PARAMETERS[fault.kind]) {
|
|
@@ -150,7 +179,8 @@ function checkFaultSpec(spec) {
|
|
|
150
179
|
if (!isWhole(spec.connections, 1)) out.push(`The connections are a whole number of 1 or more, not ${String(spec.connections)}.`);
|
|
151
180
|
if (!isWhole(spec.perConnection, 0)) out.push(`The faults of each connection are a whole number of 0 or more, not ${String(spec.perConnection)}.`);
|
|
152
181
|
const weights = Object.entries(spec.kinds ?? {});
|
|
153
|
-
for (const [kind, weight] of weights) if (
|
|
182
|
+
for (const [kind, weight] of weights) if (kind === "answer") out.push("A spec does not draw an answer. Write each answer in a plan with faultPlanOf.");
|
|
183
|
+
else if (!FAULT_KINDS.includes(kind)) out.push(`The kind ${kind} is not a kind of fault.`);
|
|
154
184
|
else if (!isTime(weight, 0)) out.push(`The weight of ${kind} is a number of 0 or more, not ${String(weight)}.`);
|
|
155
185
|
if (!weights.filter(([kind]) => FAULT_KINDS.includes(kind)).some(([, weight]) => typeof weight === "number" && weight > 0)) out.push("At least one kind of fault has a weight more than 0.");
|
|
156
186
|
const within = triggerOf(spec.within);
|
|
@@ -240,6 +270,6 @@ function faultPlan(spec, seed) {
|
|
|
240
270
|
faults
|
|
241
271
|
};
|
|
242
272
|
}
|
|
243
|
-
export { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf };
|
|
273
|
+
export { ANSWER_RESERVED_FIELDS, DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf };
|
|
244
274
|
|
|
245
275
|
//# sourceMappingURL=faults.js.map
|
package/build/faults.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"faults.js","names":[],"sources":["../src/faults.ts"],"sourcesContent":["import { random } from \"@mark1russell7/rigor-generators\";\n\n/**\n * A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan\n * before the run, as it fixes a schedule. Thus a record keeps the plan, and a later run applies the same faults.\n *\n * The plan is data, and this module has no I/O. The proxy that applies a plan is the package `@mark1russell7/rigor-proxy`.\n * Each draw of a plan is an integer from `mulberry32`, thus the same seed gives the same plan on each platform.\n */\n\n/** This value is the format of a fault plan. */\nexport const FAULT_PLAN_FORMAT = \"rigor-faults/1\";\n\n/** These are the kinds of faults, in the order that a weight list of a spec uses. */\nexport const FAULT_KINDS = [\"delay\", \"stall\", \"cut\", \"reset\", \"split\", \"reorder\", \"duplicate\"] as const;\n\n/** A kind of fault. */\nexport type FaultKind = (typeof FAULT_KINDS)[number];\n\n/** The direction of a stream. `up` goes from the client to the server, and `down` goes from the server to the client. */\nexport type Direction = \"up\" | \"down\";\n\n/**\n * The point of a fault in the stream of its direction. `ms` counts from the start of the connection. `byte` counts the\n * bytes of the direction before the fault. `frame` counts the frames of the direction before the fault. The framer of\n * the proxy sets what a frame is: a WebSocket frame, a part of an HTTP message or a TCP read.\n */\nexport type Trigger = { readonly ms: number } | { readonly byte: number } | { readonly frame: number };\n\n/** These fields tell where a fault occurs. Connection `i` is the connection that the proxy accepts as number `i`, from 0. */\nexport interface FaultPoint {\n readonly connection: number;\n readonly direction: Direction;\n readonly at: Trigger;\n}\n\n/**\n * One fault of a plan.\n *\n * - `delay`: each frame that the proxy reads in the next `forMs` goes out `ms` later. The proxy does not stop its reads.\n * - `stall`: the proxy stops reading the direction for `ms`. Thus the sender sees back pressure.\n * - `cut`: the proxy ends both directions with a FIN, as a clean close of TCP.\n * - `reset`: the proxy destroys both directions with a TCP reset (RST).\n * - `split`: the frame goes out in `pieces` writes, with `gapMs` between two writes.\n * - `reorder`: the next `count` frames go out in the reverse order. After `holdMs`, the proxy sends the frames that it holds.\n * - `duplicate`: the frame goes out `1 + copies` times.\n */\nexport type Fault = FaultPoint &\n (\n | { readonly kind: \"delay\"; readonly ms: number; readonly forMs: number }\n | { readonly kind: \"stall\"; readonly ms: number }\n | { readonly kind: \"cut\" }\n | { readonly kind: \"reset\" }\n | { readonly kind: \"split\"; readonly pieces: number; readonly gapMs: number }\n | { readonly kind: \"reorder\"; readonly count: number; readonly holdMs: number }\n | { readonly kind: \"duplicate\"; readonly copies: number }\n );\n\n/** A fault plan is data, thus a record can keep the plan that a proxy applied. */\nexport interface FaultPlan {\n readonly format: typeof FAULT_PLAN_FORMAT;\n /** The seed of a plan from a spec, or `null` for a plan that a person wrote. */\n readonly seed: number | string | null;\n /** The faults, in the order of their connections. In a plan from a spec, the faults of a connection are in the order of their points. */\n readonly faults: readonly Fault[];\n}\n\n/** An interval of whole numbers. Both ends are included, and the low end comes first. */\nexport type Interval = readonly [number, number];\n\n/** These intervals hold the parameters of the faults of a spec. */\nexport interface FaultRanges {\n readonly delayMs: Interval;\n readonly delayForMs: Interval;\n readonly stallMs: Interval;\n readonly splitPieces: Interval;\n readonly splitGapMs: Interval;\n readonly reorderCount: Interval;\n readonly reorderHoldMs: Interval;\n readonly duplicateCopies: Interval;\n}\n\n/** These intervals are the default parameters of a spec. */\nexport const DEFAULT_FAULT_RANGES: FaultRanges = {\n delayMs: [50, 2000],\n delayForMs: [100, 5000],\n stallMs: [100, 10_000],\n splitPieces: [2, 8],\n splitGapMs: [0, 50],\n reorderCount: [2, 4],\n reorderHoldMs: [100, 1000],\n duplicateCopies: [1, 2],\n};\n\n/** A spec tells `faultPlan` how many faults to draw, of which kinds, and in which part of each stream. */\nexport interface FaultSpec {\n /** The number of connections that the plan covers. */\n readonly connections: number;\n /** The number of faults of each connection. */\n readonly perConnection: number;\n /** The weight of each kind. A kind without a weight, or with the weight 0, does not occur. */\n readonly kinds: Partial<Readonly<Record<FaultKind, number>>>;\n /** Each point is a whole number from 0 to less than this limit, in the measure of the limit. */\n readonly within: Trigger;\n /** The directions to draw from. The default is both directions. */\n readonly directions?: readonly Direction[];\n /** The intervals of the parameters. Each interval that the spec does not give comes from `DEFAULT_FAULT_RANGES`. */\n readonly ranges?: Partial<FaultRanges>;\n}\n\nconst isWhole = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isSafeInteger(value) && value >= least;\nconst isTime = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isFinite(value) && value >= least;\n\n/** This function gives the measure and the value of a point, or `undefined` when the point is not valid. */\nexport function triggerOf(at: Trigger): { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number } | undefined {\n if (typeof at !== \"object\" || at === null) return undefined;\n const keys = Object.keys(at);\n if (keys.length !== 1) return undefined;\n const measure = keys[0];\n const value = (at as Record<string, unknown>)[measure as string];\n if (measure === \"ms\" && isTime(value, 0)) return { measure, value: value as number };\n if ((measure === \"byte\" || measure === \"frame\") && isWhole(value, 0)) return { measure, value: value as number };\n return undefined;\n}\n\nconst PARAMETERS: Readonly<Record<FaultKind, readonly (readonly [string, \"time\" | \"whole\", number])[]>> = {\n delay: [\n [\"ms\", \"time\", 1],\n [\"forMs\", \"time\", 1],\n ],\n stall: [[\"ms\", \"time\", 1]],\n cut: [],\n reset: [],\n split: [\n [\"pieces\", \"whole\", 2],\n [\"gapMs\", \"time\", 0],\n ],\n reorder: [\n [\"count\", \"whole\", 2],\n [\"holdMs\", \"time\", 1],\n ],\n duplicate: [[\"copies\", \"whole\", 1]],\n};\n\n/** This function gives each problem of one fault. An empty list means that the fault is valid. */\nexport function checkFault(fault: Fault): readonly string[] {\n const out: string[] = [];\n if (!isWhole(fault.connection, 0)) out.push(`The connection is a whole number of 0 or more, not ${String(fault.connection)}.`);\n if (fault.direction !== \"up\" && fault.direction !== \"down\") out.push(`The direction is up or down, not ${String(fault.direction)}.`);\n if (triggerOf(fault.at) === undefined) out.push(`The point has one key of ms, byte or frame, with a value of 0 or more, not ${JSON.stringify(fault.at)}.`);\n if (!(FAULT_KINDS as readonly string[]).includes(fault.kind)) {\n out.push(`The kind is one of ${FAULT_KINDS.join(\", \")}, not ${String(fault.kind)}.`);\n return out;\n }\n for (const [name, type, least] of PARAMETERS[fault.kind]) {\n const value = (fault as unknown as Record<string, unknown>)[name];\n const valid = type === \"whole\" ? isWhole(value, least) : isTime(value, least);\n if (!valid) out.push(`The ${fault.kind} field ${name} is a ${type === \"whole\" ? \"whole number\" : \"time\"} of ${least} or more, not ${String(value)}.`);\n }\n return out;\n}\n\n/** This function gives each problem of a plan. An empty list means that the plan is valid. */\nexport function checkFaultPlan(plan: FaultPlan): readonly string[] {\n const out: string[] = [];\n if (plan.format !== FAULT_PLAN_FORMAT) out.push(`The format is ${FAULT_PLAN_FORMAT}, not ${String(plan.format)}.`);\n if (!Array.isArray(plan.faults)) return [...out, \"The faults are a list.\"];\n plan.faults.forEach((fault, index) => {\n for (const problem of checkFault(fault)) out.push(`The fault ${index}: ${problem}`);\n });\n return out;\n}\n\n/**\n * This function makes a plan from faults that a person wrote. The faults go into the order of their connections, and the\n * faults of one connection keep their order. This function throws a `RangeError` for a fault with problems.\n */\nexport function faultPlanOf(faults: readonly Fault[]): FaultPlan {\n const plan: FaultPlan = { format: FAULT_PLAN_FORMAT, seed: null, faults: [...faults].sort((a, b) => a.connection - b.connection) };\n const problems = checkFaultPlan(plan);\n if (problems.length > 0) throw new RangeError(`faultPlanOf: ${problems.join(\" \")}`);\n return plan;\n}\n\n/** This function gives the faults of one connection, in the order of the plan. */\nexport function faultsOf(plan: FaultPlan, connection: number): readonly Fault[] {\n return plan.faults.filter((fault) => fault.connection === connection);\n}\n\n/** The least value of each interval. A split has 2 pieces or more, and a reorder has 2 frames or more. */\nconst RANGE_LEAST: Readonly<Record<keyof FaultRanges, number>> = {\n delayMs: 1,\n delayForMs: 1,\n stallMs: 1,\n splitPieces: 2,\n splitGapMs: 0,\n reorderCount: 2,\n reorderHoldMs: 1,\n duplicateCopies: 1,\n};\n\n/** This function gives each problem of a spec. An empty list means that the spec is valid. */\nexport function checkFaultSpec(spec: FaultSpec): readonly string[] {\n const out: string[] = [];\n if (!isWhole(spec.connections, 1)) out.push(`The connections are a whole number of 1 or more, not ${String(spec.connections)}.`);\n if (!isWhole(spec.perConnection, 0)) out.push(`The faults of each connection are a whole number of 0 or more, not ${String(spec.perConnection)}.`);\n const weights = Object.entries(spec.kinds ?? {});\n for (const [kind, weight] of weights) {\n if (!(FAULT_KINDS as readonly string[]).includes(kind)) out.push(`The kind ${kind} is not a kind of fault.`);\n else if (!isTime(weight, 0)) out.push(`The weight of ${kind} is a number of 0 or more, not ${String(weight)}.`);\n }\n const known = weights.filter(([kind]) => (FAULT_KINDS as readonly string[]).includes(kind));\n if (!known.some(([, weight]) => typeof weight === \"number\" && weight > 0)) out.push(\"At least one kind of fault has a weight more than 0.\");\n const within = triggerOf(spec.within);\n if (within === undefined || !Number.isSafeInteger(within.value) || within.value < 1) {\n out.push(`The limit of the points has one key of ms, byte or frame, with a whole value of 1 or more, not ${JSON.stringify(spec.within)}.`);\n }\n const directions = spec.directions ?? [\"up\", \"down\"];\n if (directions.length === 0 || directions.some((d) => d !== \"up\" && d !== \"down\")) out.push(`The directions are up or down, not ${JSON.stringify(spec.directions)}.`);\n for (const [name, interval] of Object.entries(spec.ranges ?? {})) {\n if (!Object.hasOwn(RANGE_LEAST, name)) {\n out.push(`The range ${name} is not a range of a fault parameter.`);\n continue;\n }\n const [low, high] = Array.isArray(interval) ? interval : [];\n const least = RANGE_LEAST[name as keyof FaultRanges];\n if (!isWhole(low, least) || !isWhole(high, least) || (high as number) < (low as number)) {\n out.push(`The range ${name} has two whole numbers of ${least} or more, the low one first, not ${JSON.stringify(interval)}.`);\n }\n }\n return out;\n}\n\n/**\n * This function draws a fault plan from a spec and a seed. Each connection draws from its own stream, `fork` of the seed.\n * Thus a spec with more connections keeps the faults of the first connections. The faults of a connection are in the\n * order of their points. This function throws a `RangeError` for a spec with problems.\n */\nexport function faultPlan(spec: FaultSpec, seed: number | string): FaultPlan {\n const problems = checkFaultSpec(spec);\n if (problems.length > 0) throw new RangeError(`faultPlan: ${problems.join(\" \")}`);\n const ranges: FaultRanges = { ...DEFAULT_FAULT_RANGES, ...spec.ranges };\n const kinds = FAULT_KINDS.filter((kind) => (spec.kinds[kind] ?? 0) > 0);\n const weights = kinds.map((kind) => spec.kinds[kind] as number);\n const directions = spec.directions ?? ([\"up\", \"down\"] as const);\n const within = triggerOf(spec.within) as { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number };\n const root = random(seed);\n const faults: Fault[] = [];\n for (let connection = 0; connection < spec.connections; connection += 1) {\n const rng = root.fork(`connection ${connection}`);\n const draw = (interval: Interval): number => rng.int(interval[0], interval[1]);\n const drawn: { readonly point: number; readonly fault: Fault }[] = [];\n for (let index = 0; index < spec.perConnection; index += 1) {\n const kind = kinds[rng.weighted(weights)] as FaultKind;\n const direction = rng.pick(directions);\n const point = rng.int(0, within.value - 1);\n const base = { connection, direction, at: { [within.measure]: point } as Trigger };\n const fault: Fault =\n kind === \"delay\"\n ? { ...base, kind, ms: draw(ranges.delayMs), forMs: draw(ranges.delayForMs) }\n : kind === \"stall\"\n ? { ...base, kind, ms: draw(ranges.stallMs) }\n : kind === \"split\"\n ? { ...base, kind, pieces: draw(ranges.splitPieces), gapMs: draw(ranges.splitGapMs) }\n : kind === \"reorder\"\n ? { ...base, kind, count: draw(ranges.reorderCount), holdMs: draw(ranges.reorderHoldMs) }\n : kind === \"duplicate\"\n ? { ...base, kind, copies: draw(ranges.duplicateCopies) }\n : { ...base, kind };\n drawn.push({ point, fault });\n }\n drawn.sort((a, b) => a.point - b.point);\n faults.push(...drawn.map((entry) => entry.fault));\n }\n return { format: FAULT_PLAN_FORMAT, seed, faults };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"faults.js","names":[],"sources":["../src/faults.ts"],"sourcesContent":["import { random } from \"@mark1russell7/rigor-generators\";\n\n/**\n * A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan\n * before the run, as it fixes a schedule. Thus a record keeps the plan, and a later run applies the same faults.\n *\n * The plan is data, and this module has no I/O. The proxy that applies a plan is the package `@mark1russell7/rigor-proxy`.\n * Each draw of a plan is an integer from `mulberry32`, thus the same seed gives the same plan on each platform.\n */\n\n/** This value is the format of a fault plan. */\nexport const FAULT_PLAN_FORMAT = \"rigor-faults/1\";\n\n/**\n * These are the kinds of faults that a spec draws, in the order that a weight list of a spec uses. The kind `answer` is\n * not in this list, because its parameters are the decision of a person. Only a written plan has it.\n */\nexport const FAULT_KINDS = [\"delay\", \"stall\", \"cut\", \"reset\", \"split\", \"reorder\", \"duplicate\"] as const;\n\n/** A kind of fault that a spec draws. */\nexport type FaultKind = (typeof FAULT_KINDS)[number];\n\n/** These field names are not fields of an `answer`, because the proxy writes them. */\nexport const ANSWER_RESERVED_FIELDS = [\"connection\", \"content-length\", \"transfer-encoding\"] as const;\n\n/** The direction of a stream. `up` goes from the client to the server, and `down` goes from the server to the client. */\nexport type Direction = \"up\" | \"down\";\n\n/**\n * The point of a fault in the stream of its direction. `ms` counts from the start of the connection. `byte` counts the\n * bytes of the direction before the fault. `frame` counts the frames of the direction before the fault. The framer of\n * the proxy sets what a frame is: a WebSocket frame, a part of an HTTP message or a TCP read.\n */\nexport type Trigger = { readonly ms: number } | { readonly byte: number } | { readonly frame: number };\n\n/** These fields tell where a fault occurs. Connection `i` is the connection that the proxy accepts as number `i`, from 0. */\nexport interface FaultPoint {\n readonly connection: number;\n readonly direction: Direction;\n readonly at: Trigger;\n}\n\n/**\n * One fault of a plan.\n *\n * - `delay`: each frame that the proxy reads in the next `forMs` goes out `ms` later. The proxy does not stop its reads.\n * - `stall`: the proxy stops reading the direction for `ms`. Thus the sender sees back pressure.\n * - `cut`: the proxy ends both directions with a FIN, as a clean close of TCP.\n * - `reset`: the proxy destroys both directions with a TCP reset (RST).\n * - `split`: the frame goes out in `pieces` writes, with `gapMs` between two writes.\n * - `reorder`: the next `count` frames go out in the reverse order. After `holdMs`, the proxy sends the frames that it holds.\n * - `duplicate`: the frame goes out `1 + copies` times.\n * - `answer`: the proxy writes an HTTP/1.1 response to the client, and ends both directions with a FIN, as a `cut`.\n * The bytes of both directions after the point do not go through. On `up` at frame 0, the server does not get the\n * request. On `down` at frame 0, the answer replaces the response of the server. A spec does not draw an answer.\n */\nexport type Fault = FaultPoint &\n (\n | { readonly kind: \"delay\"; readonly ms: number; readonly forMs: number }\n | { readonly kind: \"stall\"; readonly ms: number }\n | { readonly kind: \"cut\" }\n | { readonly kind: \"reset\" }\n | { readonly kind: \"split\"; readonly pieces: number; readonly gapMs: number }\n | { readonly kind: \"reorder\"; readonly count: number; readonly holdMs: number }\n | { readonly kind: \"duplicate\"; readonly copies: number }\n | AnswerFault\n );\n\n/**\n * The parameters of an `answer`. The proxy writes the status line, the fields in their order, `content-length` and\n * `connection: close`. A response with the status 204 or 304 has no body and no `content-length`.\n */\nexport interface AnswerFault {\n readonly kind: \"answer\";\n /** The status of the response, from 200 to 599. */\n readonly status: number;\n /** The fields of the response, for example `retry-after` or `etag`. The names of `ANSWER_RESERVED_FIELDS` are not fields. */\n readonly fields?: Readonly<Record<string, string>>;\n /** The body of the response, as text. The proxy writes it in UTF-8. */\n readonly body?: string;\n}\n\n/** A fault plan is data, thus a record can keep the plan that a proxy applied. */\nexport interface FaultPlan {\n readonly format: typeof FAULT_PLAN_FORMAT;\n /** The seed of a plan from a spec, or `null` for a plan that a person wrote. */\n readonly seed: number | string | null;\n /** The faults, in the order of their connections. In a plan from a spec, the faults of a connection are in the order of their points. */\n readonly faults: readonly Fault[];\n}\n\n/** An interval of whole numbers. Both ends are included, and the low end comes first. */\nexport type Interval = readonly [number, number];\n\n/** These intervals hold the parameters of the faults of a spec. */\nexport interface FaultRanges {\n readonly delayMs: Interval;\n readonly delayForMs: Interval;\n readonly stallMs: Interval;\n readonly splitPieces: Interval;\n readonly splitGapMs: Interval;\n readonly reorderCount: Interval;\n readonly reorderHoldMs: Interval;\n readonly duplicateCopies: Interval;\n}\n\n/** These intervals are the default parameters of a spec. */\nexport const DEFAULT_FAULT_RANGES: FaultRanges = {\n delayMs: [50, 2000],\n delayForMs: [100, 5000],\n stallMs: [100, 10_000],\n splitPieces: [2, 8],\n splitGapMs: [0, 50],\n reorderCount: [2, 4],\n reorderHoldMs: [100, 1000],\n duplicateCopies: [1, 2],\n};\n\n/** A spec tells `faultPlan` how many faults to draw, of which kinds, and in which part of each stream. */\nexport interface FaultSpec {\n /** The number of connections that the plan covers. */\n readonly connections: number;\n /** The number of faults of each connection. */\n readonly perConnection: number;\n /** The weight of each kind. A kind without a weight, or with the weight 0, does not occur. */\n readonly kinds: Partial<Readonly<Record<FaultKind, number>>>;\n /** Each point is a whole number from 0 to less than this limit, in the measure of the limit. */\n readonly within: Trigger;\n /** The directions to draw from. The default is both directions. */\n readonly directions?: readonly Direction[];\n /** The intervals of the parameters. Each interval that the spec does not give comes from `DEFAULT_FAULT_RANGES`. */\n readonly ranges?: Partial<FaultRanges>;\n}\n\nconst isWhole = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isSafeInteger(value) && value >= least;\nconst isTime = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isFinite(value) && value >= least;\n\n/** This function gives the measure and the value of a point, or `undefined` when the point is not valid. */\nexport function triggerOf(at: Trigger): { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number } | undefined {\n if (typeof at !== \"object\" || at === null) return undefined;\n const keys = Object.keys(at);\n if (keys.length !== 1) return undefined;\n const measure = keys[0];\n const value = (at as Record<string, unknown>)[measure as string];\n if (measure === \"ms\" && isTime(value, 0)) return { measure, value: value as number };\n if ((measure === \"byte\" || measure === \"frame\") && isWhole(value, 0)) return { measure, value: value as number };\n return undefined;\n}\n\nconst PARAMETERS: Readonly<Record<FaultKind, readonly (readonly [string, \"time\" | \"whole\", number])[]>> = {\n delay: [\n [\"ms\", \"time\", 1],\n [\"forMs\", \"time\", 1],\n ],\n stall: [[\"ms\", \"time\", 1]],\n cut: [],\n reset: [],\n split: [\n [\"pieces\", \"whole\", 2],\n [\"gapMs\", \"time\", 0],\n ],\n reorder: [\n [\"count\", \"whole\", 2],\n [\"holdMs\", \"time\", 1],\n ],\n duplicate: [[\"copies\", \"whole\", 1]],\n};\n\n/** A token of HTTP: the characters of a field name. */\nconst TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n/** The value of a field: visible ASCII characters, spaces and tabs, with no space or tab at the ends. */\nconst FIELD_VALUE = /^(?:[\\x21-\\x7e](?:[\\x20\\x09\\x21-\\x7e]*[\\x21-\\x7e])?)?$/;\n\n/** This function gives each problem of the parameters of an `answer`. */\nfunction answerProblems(fault: FaultPoint & AnswerFault): string[] {\n const out: string[] = [];\n if (!isWhole(fault.status, 200) || fault.status > 599) out.push(`The answer field status is a whole number from 200 to 599, not ${String(fault.status)}.`);\n const fields: unknown = fault.fields ?? {};\n if (typeof fields !== \"object\" || fields === null || Array.isArray(fields)) out.push(\"The answer field fields is an object of names and values.\");\n else {\n for (const [name, value] of Object.entries(fields)) {\n if (!TOKEN.test(name)) out.push(`The answer field name ${JSON.stringify(name)} is not a token of HTTP.`);\n else if ((ANSWER_RESERVED_FIELDS as readonly string[]).includes(name.toLowerCase())) out.push(`The answer does not set the field ${name}, because the proxy writes it.`);\n if (typeof value !== \"string\" || !FIELD_VALUE.test(value)) out.push(`The value of the answer field ${name} is text with no line end and no space at the ends, not ${JSON.stringify(value)}.`);\n }\n }\n if (fault.body !== undefined && typeof fault.body !== \"string\") out.push(\"The answer field body is text.\");\n else if ((fault.status === 204 || fault.status === 304) && (fault.body ?? \"\") !== \"\") out.push(`A response with the status ${fault.status} has no body.`);\n return out;\n}\n\n/** This function gives each problem of one fault. An empty list means that the fault is valid. */\nexport function checkFault(fault: Fault): readonly string[] {\n const out: string[] = [];\n if (!isWhole(fault.connection, 0)) out.push(`The connection is a whole number of 0 or more, not ${String(fault.connection)}.`);\n if (fault.direction !== \"up\" && fault.direction !== \"down\") out.push(`The direction is up or down, not ${String(fault.direction)}.`);\n if (triggerOf(fault.at) === undefined) out.push(`The point has one key of ms, byte or frame, with a value of 0 or more, not ${JSON.stringify(fault.at)}.`);\n if (fault.kind === \"answer\") return [...out, ...answerProblems(fault)];\n if (!(FAULT_KINDS as readonly string[]).includes(fault.kind)) {\n out.push(`The kind is one of ${FAULT_KINDS.join(\", \")} or answer, not ${String(fault.kind)}.`);\n return out;\n }\n for (const [name, type, least] of PARAMETERS[fault.kind]) {\n const value = (fault as unknown as Record<string, unknown>)[name];\n const valid = type === \"whole\" ? isWhole(value, least) : isTime(value, least);\n if (!valid) out.push(`The ${fault.kind} field ${name} is a ${type === \"whole\" ? \"whole number\" : \"time\"} of ${least} or more, not ${String(value)}.`);\n }\n return out;\n}\n\n/** This function gives each problem of a plan. An empty list means that the plan is valid. */\nexport function checkFaultPlan(plan: FaultPlan): readonly string[] {\n const out: string[] = [];\n if (plan.format !== FAULT_PLAN_FORMAT) out.push(`The format is ${FAULT_PLAN_FORMAT}, not ${String(plan.format)}.`);\n if (!Array.isArray(plan.faults)) return [...out, \"The faults are a list.\"];\n plan.faults.forEach((fault, index) => {\n for (const problem of checkFault(fault)) out.push(`The fault ${index}: ${problem}`);\n });\n return out;\n}\n\n/**\n * This function makes a plan from faults that a person wrote. The faults go into the order of their connections, and the\n * faults of one connection keep their order. This function throws a `RangeError` for a fault with problems.\n */\nexport function faultPlanOf(faults: readonly Fault[]): FaultPlan {\n const plan: FaultPlan = { format: FAULT_PLAN_FORMAT, seed: null, faults: [...faults].sort((a, b) => a.connection - b.connection) };\n const problems = checkFaultPlan(plan);\n if (problems.length > 0) throw new RangeError(`faultPlanOf: ${problems.join(\" \")}`);\n return plan;\n}\n\n/** This function gives the faults of one connection, in the order of the plan. */\nexport function faultsOf(plan: FaultPlan, connection: number): readonly Fault[] {\n return plan.faults.filter((fault) => fault.connection === connection);\n}\n\n/** The least value of each interval. A split has 2 pieces or more, and a reorder has 2 frames or more. */\nconst RANGE_LEAST: Readonly<Record<keyof FaultRanges, number>> = {\n delayMs: 1,\n delayForMs: 1,\n stallMs: 1,\n splitPieces: 2,\n splitGapMs: 0,\n reorderCount: 2,\n reorderHoldMs: 1,\n duplicateCopies: 1,\n};\n\n/** This function gives each problem of a spec. An empty list means that the spec is valid. */\nexport function checkFaultSpec(spec: FaultSpec): readonly string[] {\n const out: string[] = [];\n if (!isWhole(spec.connections, 1)) out.push(`The connections are a whole number of 1 or more, not ${String(spec.connections)}.`);\n if (!isWhole(spec.perConnection, 0)) out.push(`The faults of each connection are a whole number of 0 or more, not ${String(spec.perConnection)}.`);\n const weights = Object.entries(spec.kinds ?? {});\n for (const [kind, weight] of weights) {\n if (kind === \"answer\") out.push(\"A spec does not draw an answer. Write each answer in a plan with faultPlanOf.\");\n else if (!(FAULT_KINDS as readonly string[]).includes(kind)) out.push(`The kind ${kind} is not a kind of fault.`);\n else if (!isTime(weight, 0)) out.push(`The weight of ${kind} is a number of 0 or more, not ${String(weight)}.`);\n }\n const known = weights.filter(([kind]) => (FAULT_KINDS as readonly string[]).includes(kind));\n if (!known.some(([, weight]) => typeof weight === \"number\" && weight > 0)) out.push(\"At least one kind of fault has a weight more than 0.\");\n const within = triggerOf(spec.within);\n if (within === undefined || !Number.isSafeInteger(within.value) || within.value < 1) {\n out.push(`The limit of the points has one key of ms, byte or frame, with a whole value of 1 or more, not ${JSON.stringify(spec.within)}.`);\n }\n const directions = spec.directions ?? [\"up\", \"down\"];\n if (directions.length === 0 || directions.some((d) => d !== \"up\" && d !== \"down\")) out.push(`The directions are up or down, not ${JSON.stringify(spec.directions)}.`);\n for (const [name, interval] of Object.entries(spec.ranges ?? {})) {\n if (!Object.hasOwn(RANGE_LEAST, name)) {\n out.push(`The range ${name} is not a range of a fault parameter.`);\n continue;\n }\n const [low, high] = Array.isArray(interval) ? interval : [];\n const least = RANGE_LEAST[name as keyof FaultRanges];\n if (!isWhole(low, least) || !isWhole(high, least) || (high as number) < (low as number)) {\n out.push(`The range ${name} has two whole numbers of ${least} or more, the low one first, not ${JSON.stringify(interval)}.`);\n }\n }\n return out;\n}\n\n/**\n * This function draws a fault plan from a spec and a seed. Each connection draws from its own stream, `fork` of the seed.\n * Thus a spec with more connections keeps the faults of the first connections. The faults of a connection are in the\n * order of their points. This function throws a `RangeError` for a spec with problems.\n */\nexport function faultPlan(spec: FaultSpec, seed: number | string): FaultPlan {\n const problems = checkFaultSpec(spec);\n if (problems.length > 0) throw new RangeError(`faultPlan: ${problems.join(\" \")}`);\n const ranges: FaultRanges = { ...DEFAULT_FAULT_RANGES, ...spec.ranges };\n const kinds = FAULT_KINDS.filter((kind) => (spec.kinds[kind] ?? 0) > 0);\n const weights = kinds.map((kind) => spec.kinds[kind] as number);\n const directions = spec.directions ?? ([\"up\", \"down\"] as const);\n const within = triggerOf(spec.within) as { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number };\n const root = random(seed);\n const faults: Fault[] = [];\n for (let connection = 0; connection < spec.connections; connection += 1) {\n const rng = root.fork(`connection ${connection}`);\n const draw = (interval: Interval): number => rng.int(interval[0], interval[1]);\n const drawn: { readonly point: number; readonly fault: Fault }[] = [];\n for (let index = 0; index < spec.perConnection; index += 1) {\n const kind = kinds[rng.weighted(weights)] as FaultKind;\n const direction = rng.pick(directions);\n const point = rng.int(0, within.value - 1);\n const base = { connection, direction, at: { [within.measure]: point } as Trigger };\n const fault: Fault =\n kind === \"delay\"\n ? { ...base, kind, ms: draw(ranges.delayMs), forMs: draw(ranges.delayForMs) }\n : kind === \"stall\"\n ? { ...base, kind, ms: draw(ranges.stallMs) }\n : kind === \"split\"\n ? { ...base, kind, pieces: draw(ranges.splitPieces), gapMs: draw(ranges.splitGapMs) }\n : kind === \"reorder\"\n ? { ...base, kind, count: draw(ranges.reorderCount), holdMs: draw(ranges.reorderHoldMs) }\n : kind === \"duplicate\"\n ? { ...base, kind, copies: draw(ranges.duplicateCopies) }\n : { ...base, kind };\n drawn.push({ point, fault });\n }\n drawn.sort((a, b) => a.point - b.point);\n faults.push(...drawn.map((entry) => entry.fault));\n }\n return { format: FAULT_PLAN_FORMAT, seed, faults };\n}\n"],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClosedLoopOptions, ClosedLoopRun, LoopRequest, closedLoopPlan } from "./closed.js";
|
|
2
|
-
import { DEFAULT_FAULT_RANGES, Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, Fault, FaultKind, FaultPlan, FaultPoint, FaultRanges, FaultSpec, Interval, Trigger, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
|
|
2
|
+
import { ANSWER_RESERVED_FIELDS, AnswerFault, DEFAULT_FAULT_RANGES, Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, Fault, FaultKind, FaultPlan, FaultPoint, FaultRanges, FaultSpec, Interval, Trigger, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
|
|
3
3
|
import { Completion, LoadMeasure, ScheduleCheck, SecondSample, latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
|
|
4
4
|
import { Arrivals, Phase, Profile, SCHEDULE_FORMAT, Schedule, ScheduleOptions, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
|
|
5
|
-
export { type Arrivals, type ClosedLoopOptions, type ClosedLoopRun, type Completion, DEFAULT_FAULT_RANGES, type Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, type Fault, type FaultKind, type FaultPlan, type FaultPoint, type FaultRanges, type FaultSpec, type Interval, type LoadMeasure, type LoopRequest, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, type Trigger, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
|
|
5
|
+
export { ANSWER_RESERVED_FIELDS, type AnswerFault, type Arrivals, type ClosedLoopOptions, type ClosedLoopRun, type Completion, DEFAULT_FAULT_RANGES, type Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, type Fault, type FaultKind, type FaultPlan, type FaultPoint, type FaultRanges, type FaultSpec, type Interval, type LoadMeasure, type LoopRequest, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, type Trigger, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { closedLoopPlan } from "./closed.js";
|
|
2
|
-
import { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
|
|
2
|
+
import { ANSWER_RESERVED_FIELDS, DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
|
|
3
3
|
import { latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
|
|
4
4
|
import { SCHEDULE_FORMAT, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
|
|
5
|
-
export { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, SCHEDULE_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
|
|
5
|
+
export { ANSWER_RESERVED_FIELDS, DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, SCHEDULE_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/rigor-chaos",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "Open-loop load schedules, seeded fault plans for a proxy, and a measure of latency from the planned time with no coordinated omission.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"load",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mark1russell7/rigor-generators": "^0.1.16",
|
|
44
|
-
"@mark1russell7/rigor-statistics": "^0.
|
|
44
|
+
"@mark1russell7/rigor-statistics": "^0.2.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@mark1russell7/cue": "^2.0.0",
|