@mark1russell7/harness 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.d.ts CHANGED
@@ -35,8 +35,20 @@ import "testcontainers";
35
35
  * A fault at a router of the topology (`Cell.faultAt`) acts on what the router forwards toward
36
36
  * one side. It is a black hole, a reset or an ICMP unreachable. It can also be a simplex drop, an
37
37
  * MTU drop or netem.
38
+ *
39
+ * These faults are of a replicated group (`Cell.group`), on the part that `Cell.faultAt` names:
40
+ * - `blackhole` drops each segment between the part and each of its peers. `rst` answers each
41
+ * segment from a peer with a reset, thus the part refuses each connection.
42
+ * - `partition` is a black hole between the part and some of its peers only (`GroupPlace.cuts`).
43
+ * - `freeze-process` stops the server process of the part with SIGSTOP, and SIGCONT thaws it. Its
44
+ * kernel stays up. `fin` stops the process with SIGKILL.
45
+ * - `planned-failover` asks a Sentinel for a failover: `SENTINEL FAILOVER <group>`.
46
+ *
47
+ * This fault is of an HTTP server:
48
+ * - `app-stall` injects nothing. The server process holds the reply to the request, because the
49
+ * request asks for that (`HttpProbe.hold`), while its kernel stays up.
38
50
  */
39
- type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "none";
51
+ type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "partition" | "planned-failover" | "app-stall" | "none";
40
52
  /**
41
53
  * A middlebox form tells what a router does with a flow that it forgot, when a late packet comes:
42
54
  * - `reset-both` sends a reset to the two ends when it forgets the flow.
@@ -108,6 +120,57 @@ interface Topology {
108
120
  readonly name: string;
109
121
  readonly hops: readonly Hop[];
110
122
  }
123
+ /**
124
+ * A group is a replicated group under an external failover authority, as data. It is a primary and
125
+ * its replicas, and the Sentinels that decide which member is the primary. Each member and each
126
+ * Sentinel is a container from the same stock image, pinned by digest. Each has its own network
127
+ * namespace, thus a fault can act on one part. The client finds the primary through the
128
+ * Sentinels, and the parts and the client share one network.
129
+ */
130
+ interface Group {
131
+ /** This name is the name of the group that the Sentinels monitor, for example `mymaster`. */
132
+ readonly name: string;
133
+ /** This image is the stock image of each member and each Sentinel, pinned by digest. */
134
+ readonly image: string;
135
+ /** These are the programs of the image: the server, the Sentinel and the command line client. */
136
+ readonly programs: {
137
+ readonly server: string;
138
+ readonly sentinel: string;
139
+ readonly cli: string;
140
+ };
141
+ readonly port: number;
142
+ readonly sentinelPort: number;
143
+ /** These are the names of the members. The first one is the primary when the environment starts. */
144
+ readonly members: readonly string[];
145
+ readonly sentinels: readonly string[];
146
+ /** This count is how many Sentinels must agree that the primary is down. */
147
+ readonly quorum: number;
148
+ /** These are the settings of each member, as its configuration file spells them. The harness adds `replicaof` for each replica. */
149
+ readonly memberSettings: Readonly<Record<string, string>>;
150
+ /**
151
+ * These are the settings of each Sentinel for the group, as `sentinel <setting> <group> <value>`
152
+ * spells them: `down-after-milliseconds`, `failover-timeout` and `parallel-syncs`.
153
+ */
154
+ readonly sentinelSettings: Readonly<Record<string, string>>;
155
+ /** These lines tell that a member and a Sentinel serve. */
156
+ readonly ready: {
157
+ readonly member: string;
158
+ readonly sentinel: string;
159
+ };
160
+ }
161
+ /** A group role is a part of a group by its role at the start of a run: the primary, a replica, or a Sentinel. */
162
+ type GroupRole = "primary" | "replica" | "sentinel";
163
+ /**
164
+ * A group place is the part of a group that a fault acts on. A network fault cuts the part from
165
+ * some of its peers:
166
+ * - `all`: each other part of the group, and each client. This is the default.
167
+ * - `group`: the other members and the Sentinels, but not the clients.
168
+ * - `clients`: the clients, but not the group.
169
+ */
170
+ interface GroupPlace {
171
+ readonly role: GroupRole;
172
+ readonly cuts?: "all" | "group" | "clients";
173
+ }
111
174
  /** A toxic is one toxic of Toxiproxy, as its API takes it. Its toxicity is 1. */
112
175
  interface Toxic {
113
176
  readonly type: "latency" | "timeout" | "reset_peer" | "slow_close" | "bandwidth" | "limit_data" | "slicer";
@@ -176,12 +239,71 @@ interface ProxyTime {
176
239
  readonly quantile: number;
177
240
  readonly time: Quantity;
178
241
  }
242
+ /**
243
+ * A stage is one step of the timeline of a group after a fault, by the event that a Sentinel
244
+ * publishes for it. The stages are the subjective down, the objective down, the start of a
245
+ * failover and the election. Then the selection of a replica, the promotion, the switch to the new
246
+ * primary, and the conversion of the old primary into a replica.
247
+ */
248
+ type Stage = "+sdown" | "+odown" | "+try-failover" | "+elected-leader" | "+selected-slave" | "+promoted-slave" | "+switch-master" | "+convert-to-slave";
249
+ /** A stage band is when the first Sentinel announces a stage, from the reference of the cell. */
250
+ interface StageBand {
251
+ readonly stage: Stage;
252
+ readonly low: Quantity;
253
+ readonly high: Quantity;
254
+ }
255
+ /**
256
+ * A group prediction tells what a group does after the fault, beside the band of its cell:
257
+ * - `lands` is the primary that the first accepted write after the fault lands on: a new one, or the same one.
258
+ * - `failover` tells if a Sentinel announces a switch to a new primary in the window.
259
+ * - `stages` are the bands of the stages of the timeline. Each band includes the error of the
260
+ * stamps of the observer (`stamps`).
261
+ * - `errors` are the errors that the client must meet on the way, each with the band of its
262
+ * first occurrence. An example is the error of the offline queue when it gives up.
263
+ */
264
+ interface GroupPrediction {
265
+ readonly lands: "new" | "same";
266
+ readonly failover: boolean;
267
+ readonly stages: readonly StageBand[];
268
+ readonly stamps?: Quantity;
269
+ readonly errors?: ReadonlyArray<{
270
+ readonly name: string;
271
+ readonly low: Quantity;
272
+ readonly high: Quantity;
273
+ }>;
274
+ }
275
+ /**
276
+ * A middlebox expectation tells what the router of a topology does with the flow in the window of
277
+ * a silent prediction. Without it, the router keeps the flow. With `forgets`, the router forgets
278
+ * the flow in that band after the last segment of the client, and nothing detects it.
279
+ */
280
+ interface MiddleboxExpectation {
281
+ readonly forgets: {
282
+ readonly low: Quantity;
283
+ readonly high: Quantity;
284
+ };
285
+ }
286
+ /**
287
+ * A request prediction tells what the second request of an HTTP client meets, beside the band of
288
+ * its cell. The client writes it in its lines: the connection of each try, and the outcome.
289
+ * - `connection` is the connection of its first try: the one that the first request used
290
+ * (`reused`), or a new one.
291
+ * - `retried` tells that the client sent it again on a new connection by itself.
292
+ * - `error` is the error that reaches the caller, or null for a reply.
293
+ * - `pending` tells that nothing ends the request in the window of the cell.
294
+ */
295
+ interface RequestPrediction {
296
+ readonly connection: "reused" | "new";
297
+ readonly retried: boolean;
298
+ readonly error: string | null;
299
+ readonly pending?: true;
300
+ }
179
301
  /**
180
302
  * A prediction is what a cell predicts. Either a detection occurs in an interval [T_lo, T_hi]
181
303
  * after the reference event, or nothing detects the fault in a window. Nothing detects a stall
182
304
  * that is shorter than the give-up, and the connection recovers from it. The prediction then also
183
305
  * tells when. A silent prediction can also tell when the reply to the request comes back
184
- * (`replies`).
306
+ * (`replies`), and what a router does with the flow (`middlebox`).
185
307
  *
186
308
  * A band can name the first round trip that it assumes (`rtt`), and the proxy times that it
187
309
  * includes (`through`).
@@ -190,12 +312,21 @@ interface ProxyTime {
190
312
  * prediction is `detects: null` with the reason, and its runs are measurements only. A
191
313
  * measurement-only cell that names a reference (`from`) also records the time from it to the end
192
314
  * of the connection, or to the last data.
315
+ *
316
+ * For a group cell, the band is the first write that a primary accepts after the fault, and its
317
+ * errno is `OK`. `group` tells which primary, and the stages of the group on the way.
318
+ *
319
+ * For an HTTP cell, `request` tells what the second request meets. The errno `CLOSED` is a close
320
+ * of the observer itself: its pool closes an idle connection, a FIN that it sends.
193
321
  */
194
322
  type Prediction = {
195
323
  readonly detects: true;
196
324
  /** This name is the timer of the machine that fires first, or the event that detects the fault, with its errno. */
197
325
  readonly timer: string;
198
- /** This name is the errno of the detection, or EOF for an end of file without an error. */
326
+ /**
327
+ * This name is the errno of the detection, or EOF for an end of file without an error. It is
328
+ * OK for a write that a primary accepts, and CLOSED for a close of the observer itself.
329
+ */
199
330
  readonly errno: string;
200
331
  readonly from: Reference;
201
332
  /** This time is the closed form, less the most that the timer can fire early. */
@@ -204,18 +335,25 @@ type Prediction = {
204
335
  readonly high: Quantity;
205
336
  readonly rtt?: RttRange;
206
337
  readonly through?: readonly ProxyTime[];
338
+ readonly group?: GroupPrediction;
339
+ readonly request?: RequestPrediction;
207
340
  } | {
208
341
  readonly detects: false;
209
342
  readonly window: Quantity;
210
343
  readonly recovers?: Recovers;
211
344
  readonly replies?: Replies;
345
+ readonly middlebox?: MiddleboxExpectation;
212
346
  readonly rtt?: RttRange;
213
347
  readonly through?: readonly ProxyTime[];
348
+ readonly group?: GroupPrediction;
349
+ readonly request?: RequestPrediction;
214
350
  } | {
215
351
  readonly detects: null;
216
352
  readonly window: Quantity;
217
353
  readonly why: string;
218
354
  readonly from?: Reference;
355
+ readonly group?: GroupPrediction;
356
+ readonly request?: RequestPrediction;
219
357
  };
220
358
  /** A target is the server that a cell connects to: a stock image. */
221
359
  interface Target {
@@ -242,9 +380,40 @@ interface Target {
242
380
  readonly kill?: readonly string[];
243
381
  /** This command makes the server close the connection of each client, as argv, for a `server-close` fault. */
244
382
  readonly close?: readonly string[];
383
+ /**
384
+ * These files go into the target before it starts, by their path, for example the script of a
385
+ * server on a stock Node image. The image stays stock.
386
+ */
387
+ readonly files?: Readonly<Record<string, string>>;
388
+ /**
389
+ * This flag tells that the target writes JSON lines with an `ev` and a `t` on its standard
390
+ * output, on CLOCK_MONOTONIC. Each run then keeps its lines (`Run.server`).
391
+ */
392
+ readonly lines?: true;
393
+ }
394
+ /**
395
+ * A client is what the runs of a cell use: the TCP client of the harness, or a driver on its
396
+ * socket. The HTTP clients are the `http.Transport` of Go (`go-http`), the `http.Agent` of Node
397
+ * (`http-agent`) and an HTTP/1.1 `Client` of undici (`undici`).
398
+ */
399
+ type Client = "tcp" | "ioredis" | "go-http" | "http-agent" | "undici";
400
+ /**
401
+ * An HTTP probe is the workload of an HTTP client. The first request is a GET of the root. The
402
+ * second request follows the mode of the probe: after the signal (`inflight`), or after a gap
403
+ * (`gap`).
404
+ */
405
+ interface HttpProbe {
406
+ /** This method is the method of the second request. */
407
+ readonly method: "GET" | "POST";
408
+ /** This time is the gap of a `gap` workload: from the end of the first reply to the second request. */
409
+ readonly gap?: Quantity;
410
+ /** This time is how long the server holds the reply to the second request (`/hold?ms=`). */
411
+ readonly hold?: Quantity;
412
+ /** This length is the length of the body of the reply to the second request, in bytes (`/big?bytes=`). */
413
+ readonly bytes?: number;
414
+ /** These are settings of the client over its defaults, by the names of its flags: for example `idle-conn-timeout-ms`. */
415
+ readonly options?: Readonly<Record<string, number>>;
245
416
  }
246
- /** A client is what the runs of a cell use: the TCP client of the harness, or a driver on its socket. */
247
- type Client = "tcp" | "ioredis";
248
417
  /**
249
418
  * A client image is a process that a lab starts as the client of each run, in the network
250
419
  * namespace of the client. An example is an agent of an application on Node. Its command gets the
@@ -279,8 +448,9 @@ interface Cell {
279
448
  /**
280
449
  * This mode tells if the client is idle after its first reply, or sends a request into the
281
450
  * fault. In a stream, it sends `count` requests into it, each `interval` after the reply before.
451
+ * An HTTP client in a `gap` sends its second request a gap after its first reply, by itself.
282
452
  */
283
- readonly mode: "idle" | "inflight" | "stream";
453
+ readonly mode: "idle" | "inflight" | "stream" | "gap";
284
454
  /** This count is the number of requests of a stream. */
285
455
  readonly count?: number;
286
456
  /** This time is the pause after each reply of a stream, before the next request. */
@@ -304,6 +474,8 @@ interface Cell {
304
474
  readonly name: "ioredis";
305
475
  readonly keepAlive: Quantity;
306
476
  };
477
+ /** This workload is the workload of an HTTP client (`Cell.client` is an HTTP client). The client then ignores `keepalive` and `userTimeout`. */
478
+ readonly http?: HttpProbe;
307
479
  };
308
480
  readonly fault: Fault;
309
481
  /** This time is the length of a stall, from the send of the request. */
@@ -341,14 +513,29 @@ interface Cell {
341
513
  readonly idleBefore?: Quantity;
342
514
  /** This toxic is the toxic of a `toxic` fault, which the Toxiproxy hop gets. */
343
515
  readonly toxic?: Toxic;
344
- /** This image replaces the TCP client of the harness for each run. */
345
- readonly client?: ClientImage;
516
+ /** This image, or this client of the harness, replaces the TCP client of the harness for each run. An HTTP client is one. */
517
+ readonly client?: ClientImage | Client;
346
518
  /**
347
519
  * This name is the proxy hop whose own time a calibration cell measures. The time of a request
348
520
  * (`up`) starts at its arrival on the socket of the proxy toward the client. It stops at the
349
521
  * write on the socket toward the target. The time of its reply (`down`) goes the other way.
522
+ *
523
+ * For a group cell, `sentinels` makes the prober watch the namespace of each Sentinel too. Each
524
+ * stamp of the observer then gets the write of its Sentinel that it read (`lags`).
350
525
  */
351
526
  readonly calibrates?: string;
527
+ /**
528
+ * This group replaces the target: a replicated group under its Sentinels. The client is then
529
+ * ioredis in Sentinel mode, and it writes a stream of sequence numbers. `target` stays for the
530
+ * record, and the group does not read it.
531
+ */
532
+ readonly group?: Group;
533
+ /** This time is how long a fault on a group holds after its commit. The injector then lifts it at that instant, on the clock of the VM. */
534
+ readonly hold?: Quantity;
535
+ /** This flag makes a planned failover coordinated: `SENTINEL FAILOVER <group> COORDINATED`. */
536
+ readonly coordinated?: boolean;
537
+ /** These are options of ioredis over its defaults, for the client of a group. A cell at the defaults has none. */
538
+ readonly driverOptions?: Readonly<Record<string, unknown>>;
352
539
  /** This count is the number of runs of the cell (11), across new environments (two at least). */
353
540
  readonly runs: number;
354
541
  readonly environments: number;
@@ -359,16 +546,22 @@ interface Cell {
359
546
  */
360
547
  readonly concurrency?: number;
361
548
  }
549
+ /** A hop place is a hop of a topology, with the side that the rules of a fault face. */
550
+ interface HopPlace {
551
+ readonly hop: string;
552
+ readonly toward: "client" | "target";
553
+ }
362
554
  /**
363
555
  * A fault place is the namespace that gets the rules of a fault. "target" and "client" are the two
364
556
  * ends. A hop place is a hop, with the side that the rules face. That side is the next hop toward
365
557
  * the client, or the next hop toward the target. The rules of a proxy act on its sockets. The
366
558
  * rules of a router act on what it forwards, by its device toward that side: the FORWARD chain.
559
+ *
560
+ * A group place is a part of a group, by its role.
367
561
  */
368
- type FaultPlace = "target" | "client" | {
369
- readonly hop: string;
370
- readonly toward: "client" | "target";
371
- };
562
+ type FaultPlace = "target" | "client" | HopPlace | GroupPlace;
563
+ /** This function tells if a fault place is a hop of a topology. */
564
+ export declare const isHopPlace: (place: FaultPlace | undefined) => place is HopPlace;
372
565
  /** A middlebox event is one line of the router: a change of a conntrack entry, or a reset that it sent. `t` is CLOCK_MONOTONIC in nanoseconds. */
373
566
  interface MiddleboxEvent {
374
567
  readonly ev: string;
@@ -416,6 +609,42 @@ interface Run {
416
609
  readonly topology?: RunTopology;
417
610
  /** These are the lines of the routers of a topology from the start to the end of the run. */
418
611
  readonly middlebox?: readonly MiddleboxEvent[];
612
+ /** This record is the group of a group cell: its parts, and what it did in the run. */
613
+ readonly group?: RunGroup;
614
+ /** These are the lines of a target that writes them (`Target.lines`), from the start to the end of the run. */
615
+ readonly server?: readonly ProbeEvent[];
616
+ }
617
+ /** A Sentinel event is one line of the observer of a group: an event that a Sentinel published, on the clock of the VM. */
618
+ interface SentinelEvent {
619
+ readonly ev: string;
620
+ readonly t: number;
621
+ /** This text is the address and port of the Sentinel. */
622
+ readonly sentinel?: string;
623
+ /** This name is the event, for example `+switch-master`. */
624
+ readonly event?: string;
625
+ /** This text is the detail of the event, as the Sentinel wrote it. */
626
+ readonly detail?: string;
627
+ readonly [field: string]: unknown;
628
+ }
629
+ /** A part is one member or Sentinel of a group, as a run found it: its address and its network namespace. */
630
+ interface RunPart {
631
+ readonly ip: string;
632
+ readonly netns: number;
633
+ }
634
+ /** A run group is the group of a run: its parts, the primary before and after, the timeline, and the writes that it kept. */
635
+ interface RunGroup {
636
+ readonly members: Readonly<Record<string, RunPart>>;
637
+ readonly sentinels: Readonly<Record<string, RunPart>>;
638
+ /** This name is the member that was the primary at the start of the run. */
639
+ readonly primary: string;
640
+ /** This name is the part that got the fault, or null for a fault on the whole group. */
641
+ readonly at: string | null;
642
+ /** These are the lines of the observer of the Sentinels from the start to the end of the run. */
643
+ readonly timeline: readonly SentinelEvent[];
644
+ /** This name is the member that was the primary when the group converged after the run, or null when it did not converge. */
645
+ readonly after: string | null;
646
+ /** These are the sequence numbers that the group holds after the run, when it converged, or null. */
647
+ readonly holds: readonly number[] | null;
419
648
  }
420
649
  /**
421
650
  * A run topology tells where each socket of a run is, in a topology:
@@ -433,6 +662,91 @@ interface RunTopology {
433
662
  }>>;
434
663
  }
435
664
  //#endregion
665
+ //#region src/timeline.d.ts
666
+ /**
667
+ * This module measures a run of a group cell. It gives the first write that a primary accepted
668
+ * after the fault, and the timeline of the group as its Sentinels announced it. It also gives what
669
+ * became of each write.
670
+ */
671
+ /** These are the stages of a timeline, in their sequence after a fault. */
672
+ export declare const STAGES: readonly Stage[];
673
+ /** An error on the way is one kind of error that the client met after the fault. It has its first time from the fault, and its count. */
674
+ interface ErrorOnTheWay {
675
+ readonly name: string;
676
+ readonly first: Quantity;
677
+ readonly count: number;
678
+ }
679
+ /**
680
+ * A writes record tells what became of the writes of a run, by their sequence numbers:
681
+ * - `old` counts the writes that the primary of the start of the run accepted after the fault.
682
+ * - `lost` are the writes that a primary accepted and the group did not keep. They are the cost
683
+ * of a split brain. It is null when the group did not converge after the run.
684
+ * - `unknown` are the writes that the client sent and did not see accepted: an error, or no answer.
685
+ * `held` tells if the group kept each one, or null when it did not converge.
686
+ */
687
+ interface Writes {
688
+ readonly sent: number;
689
+ readonly acked: number;
690
+ readonly old: number;
691
+ readonly lost: readonly number[] | null;
692
+ readonly unknown: ReadonlyArray<{
693
+ readonly n: number;
694
+ readonly held: boolean | null;
695
+ }>;
696
+ }
697
+ /** A group measurement is what one run of a group cell measured, beside its time. */
698
+ interface GroupMeasured {
699
+ /** These are the member that was the primary at the start, and the member of the first accepted write after the fault. */
700
+ readonly from: string;
701
+ readonly to: string | null;
702
+ /** These are the times of each stage from the fault: the first announcement by any Sentinel. A stage that no Sentinel announced is absent. */
703
+ readonly stages: Partial<Record<Stage, Quantity>>;
704
+ readonly writes: Writes;
705
+ readonly errors: readonly ErrorOnTheWay[];
706
+ /** These are the delays of the stamps of the observer after the writes of the Sentinels, for a calibration. */
707
+ readonly lags?: readonly Quantity[];
708
+ }
709
+ /** This function gives the time of each stage of a timeline from an instant: the first announcement of each, after the instant. */
710
+ export declare const stagesOf: (timeline: readonly SentinelEvent[], from: number) => Partial<Record<Stage, Quantity>>;
711
+ /** This function gives what became of each write of a run, from the lines of the client and the keys that the group kept. */
712
+ export declare const writesOf: (run: Run, after: number, primary: string) => Writes;
713
+ /** This function gives the errors that the client met after an instant: each kind, its first time, and its count. */
714
+ export declare const errorsOf: (events: readonly ProbeEvent[], after: number) => ErrorOnTheWay[];
715
+ /**
716
+ * This function gives the delay of each stamp of the observer after the write of its Sentinel that
717
+ * the observer read. That write is the latest write of the Sentinel's socket toward the observer,
718
+ * at the stamp or before it. The prober records each write (`tx`) on the clock of the stamps.
719
+ */
720
+ export declare const lagsOf: (run: Run, kernel: readonly KernelRecord[]) => Quantity[];
721
+ /** This type is the measurement of observe.ts, which a group run fills. */
722
+ interface GroupReading {
723
+ readonly outcome: {
724
+ readonly kind: "answered";
725
+ } | {
726
+ readonly kind: "silent";
727
+ };
728
+ readonly elapsed: Quantity | null;
729
+ readonly socket: Quantity | null;
730
+ readonly noticed: Quantity | null;
731
+ readonly void: string | null;
732
+ readonly finding: string | null;
733
+ readonly group: GroupMeasured;
734
+ }
735
+ /**
736
+ * This function reads one run of a group cell.
737
+ * - The reference is the start of the commit of the fault, as the injector stamped it. For a
738
+ * stall, it is the first write into the fault, as for the stall of a direct pair.
739
+ * - The outcome is the first write that a primary accepted after the fault. The prediction says
740
+ * which primary: a new one, or the same one after the lift.
741
+ * - Its time is the last segment with data that the client's socket to that primary received
742
+ * before the client saw the acceptance. The stamp of the client is the fallback.
743
+ * - A run is void when the prober lost records, or when the client did not become ready. It is
744
+ * also void when a Sentinel entered its TILT mode, or when the election split its votes. A band
745
+ * from the fault whose commit took longer than δ_abs is void too. So is a band whose hold lifted
746
+ * more than δ_abs late.
747
+ */
748
+ export declare const measureGroup: (cell: Cell, run: Run) => GroupReading;
749
+ //#endregion
436
750
  //#region src/observe.d.ts
437
751
  /**
438
752
  * An outcome tells how the wait of a run ended. The socket failed with an errno, or it read the
@@ -447,7 +761,21 @@ type Outcome = {
447
761
  readonly kind: "silent";
448
762
  } | {
449
763
  readonly kind: "answered";
764
+ } | {
765
+ readonly kind: "closed";
450
766
  };
767
+ /**
768
+ * A request reading is what the second request of an HTTP client met, from the lines of the
769
+ * client. It holds the connection of its first try, a new try, and its outcome. `gap` is the time
770
+ * from the end of the first reply to the send of the second request.
771
+ */
772
+ interface RequestReading {
773
+ readonly connection: "reused" | "new";
774
+ readonly retried: boolean;
775
+ readonly error: string | null;
776
+ readonly pending: boolean;
777
+ readonly gap: Quantity | null;
778
+ }
451
779
  /** A measurement is what one run measured, against the prediction of its cell. */
452
780
  interface Measured {
453
781
  readonly environment: number;
@@ -494,6 +822,10 @@ interface Measured {
494
822
  readonly reordering?: Reordering;
495
823
  /** These are the times of the proxy of a calibration cell, one for each request and its reply. */
496
824
  readonly crossings?: Crossings;
825
+ /** This record is what a run of a group cell measured: the stages of its timeline, and what became of each write. */
826
+ readonly group?: GroupMeasured;
827
+ /** This record is what the second request of an HTTP client met, for a cell whose prediction says it. */
828
+ readonly request?: RequestReading | null;
497
829
  /** This text is the reason that the run cannot count, or null. A void run is not a pass and not a finding. */
498
830
  readonly void: string | null;
499
831
  /** This text tells what in the run disagrees with the prediction, other than its timing, or null. */
@@ -566,10 +898,28 @@ export declare const crossingsOf: (run: Run, hop: string, toClient: SocketPlace
566
898
  * it, and before the first keepalive probe. A live peer answers that probe, and the answer
567
899
  * starts the idle time again. A run where the fault was late is void. A run whose records the
568
900
  * prober lost is also void.
901
+ * - For an HTTP client, it also reads what the second request met (`requestOf`). An outcome other
902
+ * than the one that the prediction gives is a finding.
569
903
  */
570
904
  export declare const measure: (cell: Cell, run: Run) => Measured;
905
+ /**
906
+ * This function reads the second request of an HTTP client from its lines. Its first try is its
907
+ * first `request` line, and each `retry` line is a new try. Its end is its `error` line, or its
908
+ * `replied` line. A request with neither is pending. It gives null when the client wrote no try.
909
+ *
910
+ * A new try that gets no connection writes no `retry` line. Its dial writes a `dial-error` line.
911
+ * Thus a `dial-error` line after the first try and before the end is also a new try.
912
+ */
913
+ export declare const requestOf: (events: readonly ProbeEvent[]) => RequestReading | null;
571
914
  //#endregion
572
915
  //#region src/verdict.d.ts
916
+ /** This function gives the words of the outcome of a second request: its connection, a retry, and its end. */
917
+ export declare const requestWords: (outcome: {
918
+ readonly connection: "reused" | "new";
919
+ readonly retried: boolean;
920
+ readonly error: string | null;
921
+ readonly pending?: boolean | undefined;
922
+ }) => string;
573
923
  /**
574
924
  * This amount is the noise of the pipeline itself, δ_abs, as the calibration measured it. It is
575
925
  * the 99.9th percentile of the whole latency of a reset, at the concurrency cap or less. Its
@@ -623,8 +973,65 @@ interface Verdict {
623
973
  readonly up: Spread | null;
624
974
  readonly down: Spread | null;
625
975
  };
976
+ /** This record is what the group of a group cell did over the counted runs: its stages, and its writes. */
977
+ readonly group?: GroupVerdict;
978
+ /**
979
+ * These are the outcomes of the second request of an HTTP client over the counted runs, each
980
+ * with its count: for example "reused, a retry, a reply". An outcome other than the predicted
981
+ * one is a finding of its run.
982
+ */
983
+ readonly requests?: Readonly<Record<string, number>>;
626
984
  readonly why: string;
627
985
  }
986
+ /**
987
+ * A stage verdict is the test of one stage of the timeline of a group against its band. The band
988
+ * is [T_lo − δ_abs, T_hi + the error of the stamps + δ_abs]. A run that did not announce the stage
989
+ * is outside too.
990
+ */
991
+ interface StageVerdict {
992
+ readonly stage: Stage;
993
+ readonly band: Interval;
994
+ readonly median: Quantity | null;
995
+ readonly outside: ReadonlyArray<{
996
+ readonly run: string;
997
+ readonly why: string;
998
+ }>;
999
+ readonly decision: "holds" | "fails";
1000
+ }
1001
+ /**
1002
+ * A group verdict sums what the group of a group cell did in its counted runs:
1003
+ * - `stages` tests each stage that the prediction gives a band.
1004
+ * - `medians` is the median of each stage that a run announced, from the fault.
1005
+ * - `lost` counts the writes that a primary accepted and the group did not keep, in each run.
1006
+ * - `old` counts the writes that the old primary accepted after the fault, in each run.
1007
+ * - `unknown` counts the writes whose outcome the client did not see, and how many the group kept.
1008
+ * - `errors` are the errors on the way: the runs that met each, and the median of its first time.
1009
+ * - `lags` is the spread of the delays of the stamps, for a calibration.
1010
+ */
1011
+ interface GroupVerdict {
1012
+ readonly stages: readonly StageVerdict[];
1013
+ readonly medians: Partial<Record<Stage, Quantity>>;
1014
+ readonly lost: {
1015
+ readonly runs: number;
1016
+ readonly total: number;
1017
+ readonly median: number | null;
1018
+ readonly max: number | null;
1019
+ };
1020
+ readonly old: {
1021
+ readonly median: number | null;
1022
+ readonly max: number | null;
1023
+ };
1024
+ readonly unknown: {
1025
+ readonly total: number;
1026
+ readonly held: number;
1027
+ };
1028
+ readonly errors: ReadonlyArray<{
1029
+ readonly name: string;
1030
+ readonly runs: number;
1031
+ readonly first: Quantity;
1032
+ }>;
1033
+ readonly lags?: Spread | null;
1034
+ }
628
1035
  /** This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). */
629
1036
  export declare const quantileOf: (sorted: readonly number[], p: number) => number;
630
1037
  /** This function gives the spread of a set of times, or null for none. */
@@ -648,6 +1055,8 @@ export declare const medianInterval: (sorted: readonly number[]) => [number, num
648
1055
  * which does not exist at this time.
649
1056
  */
650
1057
  export declare const verdictOf: (prediction: Prediction, runs: readonly Measured[]) => Verdict;
1058
+ /** This function sums what the group of a group cell did in its counted runs, and tests each stage that has a band. */
1059
+ export declare const groupVerdictOf: (prediction: Prediction, counted: readonly Measured[]) => GroupVerdict;
651
1060
  //#endregion
652
1061
  //#region src/lab.d.ts
653
1062
  /** This function gives the image of the harness. */
@@ -656,6 +1065,10 @@ export declare const harnessImage: () => Promise<string>;
656
1065
  export declare const nodeClientImage: () => Promise<string>;
657
1066
  /** This function gives the image of the router. */
658
1067
  export declare const routerImage: () => Promise<string>;
1068
+ /** This function gives the image of the Go HTTP client. */
1069
+ export declare const goHttpImage: () => Promise<string>;
1070
+ /** This function gives the image of the Node HTTP clients. */
1071
+ export declare const nodeHttpImage: () => Promise<string>;
659
1072
  /**
660
1073
  * This time is the interval of the listing of a router, when its hop does not set one. A listing
661
1074
  * evicts each expired flow that it walks past, and the DESTROY event of that flow follows.
@@ -717,6 +1130,10 @@ interface Manifest {
717
1130
  readonly settings: Readonly<Record<string, string>>;
718
1131
  /** These are the sysctls of its namespace, as read back after the harness set them. */
719
1132
  readonly sysctls: Readonly<Record<string, string>>;
1133
+ /** These are the files that went into the target, by their path, each with the sha256 of its content. */
1134
+ readonly files?: Readonly<Record<string, string>>;
1135
+ /** This line is the first line of a target that writes them: a server that tells its settings as it reads them. */
1136
+ readonly first?: ProbeEvent;
720
1137
  };
721
1138
  readonly harness: {
722
1139
  readonly image: string;
@@ -738,6 +1155,24 @@ interface Manifest {
738
1155
  readonly sysctls: Readonly<Record<string, string>>;
739
1156
  /** These are the hops of a topology, in sequence from the client. */
740
1157
  readonly hops?: readonly ManifestHop[];
1158
+ /** These are the parts of a group, with their settings as each part read them back. */
1159
+ readonly group?: {
1160
+ readonly members: ReadonlyArray<{
1161
+ readonly name: string;
1162
+ readonly ip: string;
1163
+ readonly id: string;
1164
+ readonly settings: Readonly<Record<string, string>>;
1165
+ }>;
1166
+ readonly sentinels: ReadonlyArray<{
1167
+ readonly name: string;
1168
+ readonly ip: string;
1169
+ readonly id: string;
1170
+ readonly settings: Readonly<Record<string, string>>;
1171
+ }>;
1172
+ readonly observer: {
1173
+ readonly image: string;
1174
+ };
1175
+ };
741
1176
  }
742
1177
  /** These are the JSON lines of a program, as they arrive. */
743
1178
  interface Lines<T extends {
@@ -790,6 +1225,8 @@ interface Lab {
790
1225
  readonly topology: RunTopology | null;
791
1226
  /** These are the lines of the routers of a topology, in sequence, each with the name of its hop. A direct pair has none. */
792
1227
  readonly middlebox: readonly MiddleboxEvent[];
1228
+ /** These are the lines of a target that writes them (`Target.lines`), in sequence. Another target has none. */
1229
+ readonly server: readonly ProbeEvent[];
793
1230
  /** This method adds a toxic to the Toxiproxy hop of a topology, or removes it with null. The stamps of the injector bracket it. */
794
1231
  toxic(toxic: Toxic | null): Promise<{
795
1232
  t0: number;
@@ -919,6 +1356,141 @@ export declare const deviceOf: (route: string) => string;
919
1356
  */
920
1357
  export declare const openLab: (target: Target, kind?: Client | ClientImage, topology?: Topology) => Promise<Lab>;
921
1358
  //#endregion
1359
+ //#region src/group.d.ts
1360
+ /**
1361
+ * This module is the lab of a replicated group: a primary and its replicas, and the Sentinels
1362
+ * that decide which member is the primary. It opens one environment for a group cell, and it does
1363
+ * the runs of the cell in it. Each member and each Sentinel is a container from the stock image of
1364
+ * the group, in its own network namespace. An injector sidecar shares its network and process
1365
+ * namespaces. The client of each run is ioredis in Sentinel mode. An observer subscribes to the
1366
+ * events of each Sentinel, and the prober watches the namespace of the client.
1367
+ */
1368
+ /** A stamp is the time of the injector around one commit, on CLOCK_MONOTONIC in nanoseconds. */
1369
+ interface Stamp {
1370
+ readonly t0: number;
1371
+ readonly t1: number;
1372
+ }
1373
+ /** A group network fault is a black hole between a part and its peers, or a reset of each segment from them. */
1374
+ type GroupNetworkFault = "blackhole" | "rst";
1375
+ /** A group client is the client container of one run of a group cell. */
1376
+ interface GroupClient {
1377
+ readonly events: readonly ProbeEvent[];
1378
+ until(kinds: readonly string[], within: number): Promise<ProbeEvent>;
1379
+ stop(): Promise<void>;
1380
+ }
1381
+ /** A group lab is one environment of a group cell. */
1382
+ interface GroupLab {
1383
+ readonly group: Group;
1384
+ readonly clientIp: string;
1385
+ readonly observerIp: string;
1386
+ readonly members: Readonly<Record<string, RunPart>>;
1387
+ readonly sentinels: Readonly<Record<string, RunPart>>;
1388
+ /** These are the namespaces that the prober watches: the client, and the Sentinels for a calibration. */
1389
+ readonly netns: {
1390
+ readonly client: number;
1391
+ readonly watched: readonly number[];
1392
+ };
1393
+ /** These are all the records that the prober wrote, in sequence. */
1394
+ readonly kernel: readonly KernelRecord[];
1395
+ /** These are all the lines of the observer of the Sentinels, in sequence. */
1396
+ readonly timeline: readonly SentinelEvent[];
1397
+ /** This method sets the sysctls of the client namespace through its privileged sidecar, and reads them back. */
1398
+ sysctl(values: Readonly<Record<string, string>>): Promise<Record<string, string>>;
1399
+ /** This method waits until the group is whole and calm, and gives the name of its primary. */
1400
+ converge(within?: number): Promise<string>;
1401
+ /** This method removes each key from the group, and waits until each replica has the removal. */
1402
+ flush(primary: string): Promise<void>;
1403
+ /** This method makes each Sentinel forget the state of the group (`SENTINEL RESET`): an aborted failover then holds off no later failover. */
1404
+ reset(): Promise<void>;
1405
+ /** This method gives the sequence numbers that the group holds, from its primary. */
1406
+ holds(primary: string): Promise<number[]>;
1407
+ /** This method gives the part that has a role in the group, when `primary` is the primary. */
1408
+ partOf(role: GroupRole, primary: string): string;
1409
+ /** This method starts the client of a run, with the arguments of `client-sentinel.mjs`. */
1410
+ client(args: readonly string[]): Promise<GroupClient>;
1411
+ /** This method applies a network fault between a part and some of its peers, at once or at an instant of the VM. */
1412
+ fault(kind: GroupNetworkFault, part: string, cuts: NonNullable<GroupPlace["cuts"]>, at?: number): Promise<Stamp>;
1413
+ /** This method removes a network fault, at once or at an instant of the VM. */
1414
+ lift(kind: GroupNetworkFault, part: string, cuts: NonNullable<GroupPlace["cuts"]>, at?: number): Promise<Stamp>;
1415
+ /** This method sends a signal to the server process of a part, at once or at an instant of the VM. */
1416
+ signal(part: string, signal: "KILL" | "STOP" | "CONT", at?: number): Promise<Stamp>;
1417
+ /** This method starts the server of a member again, after a fin. */
1418
+ restart(member: string): Promise<void>;
1419
+ /** This method asks the first Sentinel for a failover of the group. */
1420
+ failover(coordinated: boolean): Promise<Stamp>;
1421
+ /** This method removes the TCP metrics of the client namespace: each run starts from a kernel that keeps nothing. */
1422
+ forget(): Promise<void>;
1423
+ /** This method waits for the next clock line of the prober, which gives the count of lost records. */
1424
+ settle(): Promise<number | null>;
1425
+ manifest(sysctls: Readonly<Record<string, string>>): Promise<Manifest>;
1426
+ close(): Promise<void>;
1427
+ }
1428
+ /** This function gives the configuration file of a member: its port, its settings, and the primary that a replica follows. */
1429
+ export declare const memberConfig: (group: Group, primary: string | null) => string;
1430
+ /** This function gives the configuration file of a Sentinel: its port, the group that it monitors, and the settings of the group. */
1431
+ export declare const sentinelConfig: (group: Group, primary: string) => string;
1432
+ /**
1433
+ * This function gives the peers that a fault on a part cuts it from, by their addresses. `all` is
1434
+ * each other part and the client, `group` is the other parts, and `clients` is the client. The
1435
+ * observer is not a peer, thus the timeline of the group stays whole.
1436
+ */
1437
+ export declare const peersOf: (parts: {
1438
+ readonly members: Readonly<Record<string, RunPart>>;
1439
+ readonly sentinels: Readonly<Record<string, RunPart>>;
1440
+ }, client: string, part: string, cuts: NonNullable<GroupPlace["cuts"]>) => string[];
1441
+ /** This function reads the flat reply of a command line client into its fields: name, value, name, value. */
1442
+ export declare const fieldsOf: (text: string) => Array<Record<string, string>>;
1443
+ /** This function reads the INFO of a server into its fields. */
1444
+ export declare const infoOf: (text: string) => Record<string, string>;
1445
+ /**
1446
+ * A view of the group is what its parts tell at one time. `healthy` tells if each Sentinel agrees
1447
+ * on one primary with no flag but `master`, and knows each replica and each other Sentinel. Each
1448
+ * replica must follow that primary with its link up, and the primary must have each replica online,
1449
+ * with no failover in progress.
1450
+ */
1451
+ interface GroupView {
1452
+ readonly primary: string | null;
1453
+ readonly healthy: boolean;
1454
+ readonly why: string;
1455
+ }
1456
+ /** This function judges the views of the Sentinels and of the members: the primary that they agree on, if the group is healthy. */
1457
+ export declare const viewOf: (group: Group, ips: Readonly<Record<string, string>>, sentinels: ReadonlyArray<{
1458
+ readonly master: Record<string, string>;
1459
+ readonly replicas: ReadonlyArray<Record<string, string>>;
1460
+ }>, members: Readonly<Record<string, Record<string, string>>>) => GroupView;
1461
+ /** This time is the longest that a group takes to converge before a run, or after it. */
1462
+ export declare const CONVERGE_MS = 24e4;
1463
+ /**
1464
+ * This function opens one environment of a group cell:
1465
+ * - one new network, with each member, each Sentinel, the client and the observer on it.
1466
+ * - each member: the stock image, its server as the child of a shell, thus a fin can stop the
1467
+ * server and the container stays up. The first member is the primary, and the others follow it.
1468
+ * - each Sentinel: the stock image, its Sentinel as the child of a shell, which monitors the first member.
1469
+ * - an injector sidecar for each part, in its network and process namespaces.
1470
+ * - a client that holds its own network namespace, with a privileged sidecar for its sysctls.
1471
+ * - the observer of the Sentinels, in a namespace of its own.
1472
+ * - the prober, which watches the namespace of the client, and of each Sentinel for a calibration.
1473
+ */
1474
+ export declare const openGroupLab: (group: Group, options?: {
1475
+ readonly watchSentinels?: boolean;
1476
+ }) => Promise<GroupLab>;
1477
+ /** This function gives the latest time that a group cell waits for its outcome, in ms from the fault. */
1478
+ export declare const deadlineOf: (cell: Cell) => number;
1479
+ /** This function gives the latest subscription of the observer to each Sentinel, in some lines of the observer. */
1480
+ export declare const subscriptionsOf: (timeline: readonly SentinelEvent[]) => SentinelEvent[];
1481
+ /**
1482
+ * This function does one run of a group cell:
1483
+ * - The group converges, and its keys go. The client connects through the Sentinels, and its
1484
+ * stream of writes gets steady.
1485
+ * - The fault occurs on its part, as the primary of this run makes the roles. A fault with a
1486
+ * hold lifts at its instant in the VM. A stall lifts at its length after the first write into it.
1487
+ * - The run waits for its outcome: the first write that a new primary accepts, or the first one
1488
+ * after the lift on the same primary. Then it continues for a moment.
1489
+ * - Each fault goes, the client stops, and the group converges again. The keys that it then holds
1490
+ * tell which writes it kept.
1491
+ */
1492
+ export declare const groupOnce: (lab: GroupLab, cell: Cell, environment: number, index: number) => Promise<Run>;
1493
+ //#endregion
922
1494
  //#region src/run.d.ts
923
1495
  /** A cell result is a cell with its manifests, its runs, their measurements and its verdict. */
924
1496
  interface CellResult {
@@ -944,6 +1516,16 @@ export declare const judge: (cell: Cell, runs: readonly Run[]) => {
944
1516
  measured: Measured[];
945
1517
  verdict: Verdict;
946
1518
  };
1519
+ /**
1520
+ * This time is the pause between the client's first reply and the fault. It is more than Linux's
1521
+ * TCP_DELACK_MAX of 200 ms. Thus the delayed ACK of the reply is out before the fault.
1522
+ */
1523
+ export declare const QUIET_MS = 250;
1524
+ /**
1525
+ * This time is how long an HTTP client waits after its second request, before it exits. The exit
1526
+ * closes its sockets, thus that close comes after the window of the cell.
1527
+ */
1528
+ export declare const LINGER_MS = 1e3;
947
1529
  /** This function divides a count of runs across environments, as equally as possible. */
948
1530
  export declare const spread: (runs: number, environments: number) => number[];
949
1531
  /**
@@ -957,5 +1539,5 @@ export declare const spread: (runs: number, environments: number) => number[];
957
1539
  */
958
1540
  export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
959
1541
  //#endregion
960
- export type { Agent, Cell, CellResult, Client, ClientImage, Crossings, Decision, Fault, FaultPlace, FaultSide, Hop, Interval, KernelRecord, Lab, Manifest, ManifestHop, Measured, MiddleboxEvent, MiddleboxForm, NetworkFault, Observer, Outcome, Prediction, Probe, ProbeEvent, ProxyHop, ProxyTime, Recovers, Reference, Reordering, Replies, RouterHop, RttRange, Run, RunTopology, SocketPlace, Spread, Target, Topology, Toxic, Verdict };
1542
+ export type { Agent, Cell, CellResult, Client, ClientImage, Crossings, Decision, ErrorOnTheWay, Fault, FaultPlace, FaultSide, Group, GroupClient, GroupLab, GroupMeasured, GroupNetworkFault, GroupPlace, GroupPrediction, GroupReading, GroupRole, GroupVerdict, GroupView, Hop, HopPlace, HttpProbe, Interval, KernelRecord, Lab, Manifest, ManifestHop, Measured, MiddleboxEvent, MiddleboxExpectation, MiddleboxForm, NetworkFault, Observer, Outcome, Prediction, Probe, ProbeEvent, ProxyHop, ProxyTime, Recovers, Reference, Reordering, Replies, RequestPrediction, RequestReading, RouterHop, RttRange, Run, RunGroup, RunPart, RunTopology, SentinelEvent, SocketPlace, Spread, Stage, StageBand, StageVerdict, Stamp, Target, Topology, Toxic, Verdict, Writes };
961
1543
  //# sourceMappingURL=index.d.ts.map