@mark1russell7/rigor-chaos 0.1.1 → 0.1.2

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 CHANGED
@@ -32,3 +32,24 @@ A schedule is data. A driver reads `times`, and it sends each arrival at its tim
32
32
  `latencyQuantile` reads the latency and the censored values together, by the nearest rank. Thus an arrival with no reply does not leave the measure.
33
33
 
34
34
  `scheduleHeld` tells if the driver kept the schedule. A run that fails this check measures its driver, not its target. An A/A run with no fault examines this first. When a run fails this check, `seconds` tells if a full processor or a held event loop made the sends late.
35
+
36
+ ## A closed-loop record
37
+
38
+ `closedLoopPlan` reads a run of a closed-loop driver that sends each `interval` ms. It puts back the sends that each stall hid, by the rule of `recordValueWithExpectedInterval` of HdrHistogram.
39
+
40
+ ```ts
41
+ import { closedLoopPlan, latencyQuantile, measureLoad } from "@mark1russell7/rigor-chaos";
42
+
43
+ const { plan, completions, driverGaps } = closedLoopPlan(requests, { interval: 10, end, tolerance: 2 });
44
+ const p99 = latencyQuantile(measureLoad(plan, completions, end), 0.99);
45
+ ```
46
+
47
+ - A request with a reply later than one interval hides the planned sends at `sent + j × interval` before that reply.
48
+ - Each hidden send gets the reply that ended the stall. Its lag is its wait, and its service is 0.
49
+ - A pause of the driver itself also hides sends. The driver expects its next send at the later of `sent + interval` and the reply.
50
+ - A send later than that time by more than `tolerance` is a driver gap. Each planned send in the gap is hidden, and it gets the real send and its reply.
51
+ - `driverGaps` and `driverGapTotal` count these gaps and their delay in ms. A pause of the driver thus does not make the estimate better than the open loop.
52
+ - The plan starts again at each real send. The driver does not send faster after a stall, thus a later send is not late.
53
+ - After a request with no reply, each hidden send is not sent, and its age at the end is a lower bound.
54
+
55
+ The result is an estimate of the open loop, not a measure of it. A record of an open-loop driver stays the better measure.
@@ -0,0 +1,56 @@
1
+ import { Schedule } from "./schedule.js";
2
+ import { Completion } from "./measure.js";
3
+ /**
4
+ * A closed-loop driver sends its next request only after the reply, thus a stall hides the sends of
5
+ * its interval. This module puts those hidden sends back, by the rule of `recordValueWithExpectedInterval`
6
+ * of HdrHistogram. A request with a reply later than one interval hides the planned sends at
7
+ * `sent + j × interval` before that reply. Each hidden send gets the reply that ended the stall.
8
+ *
9
+ * A pause of the driver itself also hides sends. The driver expects its next send at the later of
10
+ * `sent + interval` and the reply. A send past that time and the tolerance is a driver gap. Each
11
+ * planned send in the gap is hidden too, and it gets the reply of the real send that ended the gap.
12
+ *
13
+ * The plan starts again at each real send. A global plan of `first + n × interval` is not correct for
14
+ * such a driver. The driver does not send faster after a stall, thus one stall makes each later send late.
15
+ */
16
+ /** A request is what a closed-loop driver sent and saw. Its times are in ms from the start of the run. */
17
+ export interface LoopRequest {
18
+ readonly sent: number;
19
+ /** This time is null for a request with no reply at the end of the run. Only the last request has it. */
20
+ readonly done: number | null;
21
+ }
22
+ /** These options give the plan of the driver and the end of the run, in ms. */
23
+ export interface ClosedLoopOptions {
24
+ /** This time is the planned gap between two sends. */
25
+ readonly interval: number;
26
+ /** This time is the end of the run, from the start of the run. */
27
+ readonly end: number;
28
+ /** A send later than its expected time by more than this tolerance is a driver gap. */
29
+ readonly tolerance: number;
30
+ }
31
+ /** This is a closed-loop run in the terms of `measureLoad`. */
32
+ export interface ClosedLoopRun {
33
+ /** This plan has each real send and each hidden send, in order. Its profile is the rate of the interval. */
34
+ readonly plan: Schedule;
35
+ /**
36
+ * These completions are those of the real sends and of the hidden sends that a reply ended. A hidden
37
+ * send of a stall has the reply of the stall as its send. Thus its lag is its wait, and its service is 0.
38
+ * A hidden send of a driver gap has the real send that ended the gap as its send.
39
+ * A hidden send before a request with no reply, or after it, has no completion: `measureLoad` gives it as not sent.
40
+ */
41
+ readonly completions: readonly Completion[];
42
+ /** This count is the number of hidden sends that the function put back, for stalls and driver gaps. */
43
+ readonly hidden: number;
44
+ /** This count is the number of sends later than their expected time by more than the tolerance. */
45
+ readonly driverGaps: number;
46
+ /** This time is the sum of those delays past the expected time, in ms. */
47
+ readonly driverGapTotal: number;
48
+ }
49
+ /**
50
+ * This function gives the plan and the completions of a closed-loop run, for `measureLoad`. The requests
51
+ * are in the order of their sends. The function throws a `RangeError` for a send before the reply of the
52
+ * request before it. It throws the same error for a missing reply before the last request. A time past
53
+ * the end and a bad option also give this error.
54
+ */
55
+ export declare function closedLoopPlan(requests: readonly LoopRequest[], options: ClosedLoopOptions): ClosedLoopRun;
56
+ //# sourceMappingURL=closed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closed.d.ts","names":[],"sources":["../src/closed.ts"],"mappings":";;;;;;;;;;;;;;;;iBAkBiB;WACN;;WAEA;;;iBAIM;;WAEN;;WAEA;;WAEA;;;iBAIM;;WAEN,MAAM;;;;;;;WAON,sBAAsB;;WAEtB;;WAEA;;WAEA;;;;;;;;wBAwBK,eAAe,mBAAmB,eAAe,SAAS,oBAAoB"}
@@ -0,0 +1,74 @@
1
+ import { SCHEDULE_FORMAT, constant } from "./schedule.js";
2
+ // src/closed.ts
3
+ /** This function throws a `RangeError` for options or a request list that one closed loop cannot give. */
4
+ function check(requests, { interval, end, tolerance }) {
5
+ if (!(interval > 0 && Number.isFinite(interval))) throw new RangeError(`closedLoopPlan: the interval is more than 0, not ${interval}.`);
6
+ if (!(end > 0 && Number.isFinite(end))) throw new RangeError(`closedLoopPlan: the end is more than 0, not ${end}.`);
7
+ if (!(tolerance >= 0 && Number.isFinite(tolerance))) throw new RangeError(`closedLoopPlan: the tolerance is 0 or more, not ${tolerance}.`);
8
+ let free = 0;
9
+ requests.forEach(({ sent, done }, index) => {
10
+ if (!(sent >= free && Number.isFinite(sent))) throw new RangeError(`closedLoopPlan: the request ${index} has a send at ${sent}, before the reply at ${free}.`);
11
+ if (done === null && index !== requests.length - 1) throw new RangeError(`closedLoopPlan: the request ${index} has no reply, and it is not the last request.`);
12
+ const last = done ?? sent;
13
+ if (!(last >= sent && last <= end)) throw new RangeError(`closedLoopPlan: the request ${index} has a send at ${sent} and a reply at ${done}, in a run that stops at ${end}.`);
14
+ free = last;
15
+ });
16
+ }
17
+ /**
18
+ * This function gives the plan and the completions of a closed-loop run, for `measureLoad`. The requests
19
+ * are in the order of their sends. The function throws a `RangeError` for a send before the reply of the
20
+ * request before it. It throws the same error for a missing reply before the last request. A time past
21
+ * the end and a bad option also give this error.
22
+ */
23
+ function closedLoopPlan(requests, options) {
24
+ check(requests, options);
25
+ const { interval, end, tolerance } = options;
26
+ const times = [];
27
+ const completions = [];
28
+ let hidden = 0;
29
+ let driverGaps = 0;
30
+ let driverGapTotal = 0;
31
+ /** This function adds a hidden send planned at `at`. It has a completion only when `done` is a reply. */
32
+ const hide = (at, send, done) => {
33
+ if (done !== null) completions.push({
34
+ index: times.length,
35
+ sent: send,
36
+ done
37
+ });
38
+ times.push(at);
39
+ hidden += 1;
40
+ };
41
+ let expected = null;
42
+ for (const { sent, done } of requests) {
43
+ if (expected !== null && sent - expected > tolerance) {
44
+ driverGaps += 1;
45
+ driverGapTotal += sent - expected;
46
+ for (let step = 0; expected + step * interval < sent; step += 1) hide(expected + step * interval, sent, done);
47
+ }
48
+ completions.push({
49
+ index: times.length,
50
+ sent,
51
+ done
52
+ });
53
+ times.push(sent);
54
+ const stop = done ?? end;
55
+ for (let step = 1; sent + step * interval < stop; step += 1) hide(sent + step * interval, done ?? sent, done);
56
+ expected = done === null ? null : Math.max(sent + interval, done);
57
+ }
58
+ return {
59
+ plan: {
60
+ format: SCHEDULE_FORMAT,
61
+ arrivals: "uniform",
62
+ seed: null,
63
+ profile: constant(1e3 / interval, end),
64
+ times
65
+ },
66
+ completions,
67
+ hidden,
68
+ driverGaps,
69
+ driverGapTotal
70
+ };
71
+ }
72
+ export { closedLoopPlan };
73
+
74
+ //# sourceMappingURL=closed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closed.js","names":[],"sources":["../src/closed.ts"],"sourcesContent":["import type { Completion } from \"./measure.ts\";\nimport { SCHEDULE_FORMAT, constant, type Schedule } from \"./schedule.ts\";\n\n/**\n * A closed-loop driver sends its next request only after the reply, thus a stall hides the sends of\n * its interval. This module puts those hidden sends back, by the rule of `recordValueWithExpectedInterval`\n * of HdrHistogram. A request with a reply later than one interval hides the planned sends at\n * `sent + j × interval` before that reply. Each hidden send gets the reply that ended the stall.\n *\n * A pause of the driver itself also hides sends. The driver expects its next send at the later of\n * `sent + interval` and the reply. A send past that time and the tolerance is a driver gap. Each\n * planned send in the gap is hidden too, and it gets the reply of the real send that ended the gap.\n *\n * The plan starts again at each real send. A global plan of `first + n × interval` is not correct for\n * such a driver. The driver does not send faster after a stall, thus one stall makes each later send late.\n */\n\n/** A request is what a closed-loop driver sent and saw. Its times are in ms from the start of the run. */\nexport interface LoopRequest {\n readonly sent: number;\n /** This time is null for a request with no reply at the end of the run. Only the last request has it. */\n readonly done: number | null;\n}\n\n/** These options give the plan of the driver and the end of the run, in ms. */\nexport interface ClosedLoopOptions {\n /** This time is the planned gap between two sends. */\n readonly interval: number;\n /** This time is the end of the run, from the start of the run. */\n readonly end: number;\n /** A send later than its expected time by more than this tolerance is a driver gap. */\n readonly tolerance: number;\n}\n\n/** This is a closed-loop run in the terms of `measureLoad`. */\nexport interface ClosedLoopRun {\n /** This plan has each real send and each hidden send, in order. Its profile is the rate of the interval. */\n readonly plan: Schedule;\n /**\n * These completions are those of the real sends and of the hidden sends that a reply ended. A hidden\n * send of a stall has the reply of the stall as its send. Thus its lag is its wait, and its service is 0.\n * A hidden send of a driver gap has the real send that ended the gap as its send.\n * A hidden send before a request with no reply, or after it, has no completion: `measureLoad` gives it as not sent.\n */\n readonly completions: readonly Completion[];\n /** This count is the number of hidden sends that the function put back, for stalls and driver gaps. */\n readonly hidden: number;\n /** This count is the number of sends later than their expected time by more than the tolerance. */\n readonly driverGaps: number;\n /** This time is the sum of those delays past the expected time, in ms. */\n readonly driverGapTotal: number;\n}\n\n/** This function throws a `RangeError` for options or a request list that one closed loop cannot give. */\nfunction check(requests: readonly LoopRequest[], { interval, end, tolerance }: ClosedLoopOptions): void {\n if (!(interval > 0 && Number.isFinite(interval))) throw new RangeError(`closedLoopPlan: the interval is more than 0, not ${interval}.`);\n if (!(end > 0 && Number.isFinite(end))) throw new RangeError(`closedLoopPlan: the end is more than 0, not ${end}.`);\n if (!(tolerance >= 0 && Number.isFinite(tolerance))) throw new RangeError(`closedLoopPlan: the tolerance is 0 or more, not ${tolerance}.`);\n let free = 0;\n requests.forEach(({ sent, done }, index) => {\n if (!(sent >= free && Number.isFinite(sent))) throw new RangeError(`closedLoopPlan: the request ${index} has a send at ${sent}, before the reply at ${free}.`);\n if (done === null && index !== requests.length - 1) throw new RangeError(`closedLoopPlan: the request ${index} has no reply, and it is not the last request.`);\n const last = done ?? sent;\n if (!(last >= sent && last <= end)) throw new RangeError(`closedLoopPlan: the request ${index} has a send at ${sent} and a reply at ${done}, in a run that stops at ${end}.`);\n free = last;\n });\n}\n\n/**\n * This function gives the plan and the completions of a closed-loop run, for `measureLoad`. The requests\n * are in the order of their sends. The function throws a `RangeError` for a send before the reply of the\n * request before it. It throws the same error for a missing reply before the last request. A time past\n * the end and a bad option also give this error.\n */\nexport function closedLoopPlan(requests: readonly LoopRequest[], options: ClosedLoopOptions): ClosedLoopRun {\n check(requests, options);\n const { interval, end, tolerance } = options;\n const times: number[] = [];\n const completions: Completion[] = [];\n let hidden = 0;\n let driverGaps = 0;\n let driverGapTotal = 0;\n /** This function adds a hidden send planned at `at`. It has a completion only when `done` is a reply. */\n const hide = (at: number, send: number, done: number | null): void => {\n if (done !== null) completions.push({ index: times.length, sent: send, done });\n times.push(at);\n hidden += 1;\n };\n let expected: number | null = null;\n for (const { sent, done } of requests) {\n if (expected !== null && sent - expected > tolerance) {\n driverGaps += 1;\n driverGapTotal += sent - expected;\n for (let step = 0; expected + step * interval < sent; step += 1) hide(expected + step * interval, sent, done);\n }\n completions.push({ index: times.length, sent, done });\n times.push(sent);\n const stop = done ?? end;\n for (let step = 1; sent + step * interval < stop; step += 1) hide(sent + step * interval, done ?? sent, done);\n expected = done === null ? null : Math.max(sent + interval, done);\n }\n const plan: Schedule = { format: SCHEDULE_FORMAT, arrivals: \"uniform\", seed: null, profile: constant(1000 / interval, end), times };\n return { plan, completions, hidden, driverGaps, driverGapTotal };\n}\n"],"mappings":";;;AAsDA,SAAS,MAAM,UAAkC,EAAE,UAAU,KAAK,aAAsC;CACtG,IAAI,EAAE,WAAW,KAAK,OAAO,SAAS,QAAQ,IAAI,MAAM,IAAI,WAAW,oDAAoD,SAAS,EAAE;CACtI,IAAI,EAAE,MAAM,KAAK,OAAO,SAAS,GAAG,IAAI,MAAM,IAAI,WAAW,+CAA+C,IAAI,EAAE;CAClH,IAAI,EAAE,aAAa,KAAK,OAAO,SAAS,SAAS,IAAI,MAAM,IAAI,WAAW,mDAAmD,UAAU,EAAE;CACzI,IAAI,OAAO;CACX,SAAS,SAAS,EAAE,MAAM,QAAQ,UAAU;EAC1C,IAAI,EAAE,QAAQ,QAAQ,OAAO,SAAS,IAAI,IAAI,MAAM,IAAI,WAAW,+BAA+B,MAAM,iBAAiB,KAAK,wBAAwB,KAAK,EAAE;EAC7J,IAAI,SAAS,QAAQ,UAAU,SAAS,SAAS,GAAG,MAAM,IAAI,WAAW,+BAA+B,MAAM,+CAA+C;EAC7J,MAAM,OAAO,QAAQ;EACrB,IAAI,EAAE,QAAQ,QAAQ,QAAQ,MAAM,MAAM,IAAI,WAAW,+BAA+B,MAAM,iBAAiB,KAAK,kBAAkB,KAAK,2BAA2B,IAAI,EAAE;EAC5K,OAAO;CACT,CAAC;AACH;;;;;;;AAQA,SAAgB,eAAe,UAAkC,SAA2C;CAC1G,MAAM,UAAU,OAAO;CACvB,MAAM,EAAE,UAAU,KAAK,cAAc;CACrC,MAAM,QAAkB,CAAC;CACzB,MAAM,cAA4B,CAAC;CACnC,IAAI,SAAS;CACb,IAAI,aAAa;CACjB,IAAI,iBAAiB;;CAErB,MAAM,QAAQ,IAAY,MAAc,SAA8B;EACpE,IAAI,SAAS,MAAM,YAAY,KAAK;GAAE,OAAO,MAAM;GAAQ,MAAM;GAAM;EAAK,CAAC;EAC7E,MAAM,KAAK,EAAE;EACb,UAAU;CACZ;CACA,IAAI,WAA0B;CAC9B,KAAK,MAAM,EAAE,MAAM,UAAU,UAAU;EACrC,IAAI,aAAa,QAAQ,OAAO,WAAW,WAAW;GACpD,cAAc;GACd,kBAAkB,OAAO;GACzB,KAAK,IAAI,OAAO,GAAG,WAAW,OAAO,WAAW,MAAM,QAAQ,GAAG,KAAK,WAAW,OAAO,UAAU,MAAM,IAAI;EAC9G;EACA,YAAY,KAAK;GAAE,OAAO,MAAM;GAAQ;GAAM;EAAK,CAAC;EACpD,MAAM,KAAK,IAAI;EACf,MAAM,OAAO,QAAQ;EACrB,KAAK,IAAI,OAAO,GAAG,OAAO,OAAO,WAAW,MAAM,QAAQ,GAAG,KAAK,OAAO,OAAO,UAAU,QAAQ,MAAM,IAAI;EAC5G,WAAW,SAAS,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU,IAAI;CAClE;CAEA,OAAO;EAAE,MAAA;GADgB,QAAQ;GAAiB,UAAU;GAAW,MAAM;GAAM,SAAS,SAAS,MAAO,UAAU,GAAG;GAAG;EAChH;EAAG;EAAa;EAAQ;EAAY;CAAe;AACjE"}
package/build/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { Arrivals, Phase, Profile, SCHEDULE_FORMAT, Schedule, ScheduleOptions, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
2
2
  import { Completion, LoadMeasure, ScheduleCheck, SecondSample, latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
3
- export { type Arrivals, type Completion, type LoadMeasure, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, checkProfile, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
3
+ import { ClosedLoopOptions, ClosedLoopRun, LoopRequest, closedLoopPlan } from "./closed.js";
4
+ export { type Arrivals, type ClosedLoopOptions, type ClosedLoopRun, type Completion, type LoadMeasure, type LoopRequest, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, checkProfile, closedLoopPlan, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
package/build/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import { latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
2
1
  import { SCHEDULE_FORMAT, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
3
- export { SCHEDULE_FORMAT, checkProfile, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
2
+ import { closedLoopPlan } from "./closed.js";
3
+ import { latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
4
+ export { SCHEDULE_FORMAT, checkProfile, closedLoopPlan, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
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.1",
4
+ "version": "0.1.2",
5
5
  "description": "Open-loop load schedules with planned arrival times from a rate profile, and a measure of latency from the planned time with no coordinated omission.",
6
6
  "keywords": [
7
7
  "load",
@@ -38,8 +38,8 @@
38
38
  "node": ">=22"
39
39
  },
40
40
  "dependencies": {
41
- "@mark1russell7/rigor-generators": "^0.1.5",
42
- "@mark1russell7/rigor-statistics": "^0.1.13"
41
+ "@mark1russell7/rigor-generators": "^0.1.6",
42
+ "@mark1russell7/rigor-statistics": "^0.1.15"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@mark1russell7/cue": "^2.0.0",