@mark1russell7/harness 0.1.0 → 0.3.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
@@ -1,5 +1,6 @@
1
1
  import { Quantity } from "@mark1russell7/quantity";
2
2
  import { KernelRecord } from "@mark1russell7/prober";
3
+ import "testcontainers";
3
4
  //#region src/cell.d.ts
4
5
  /**
5
6
  * A fault is a failure that the harness injects, by the name of its manifestation. Each name is
@@ -11,16 +12,107 @@ import { KernelRecord } from "@mark1russell7/prober";
11
12
  * It is the reference for the noise of the harness itself.
12
13
  * - `stall` is a black hole that stops by itself, `Cell.stall` after the client sends its
13
14
  * request. The VM times it. It shows what recovers, and when.
15
+ * - `fin` stops the process of the peer with SIGKILL, while its kernel stays up. The kernel closes its sockets
16
+ * with a FIN, or with a reset when unread data is in the queue.
17
+ * - `unreachable` answers each segment from the observer with an ICMP unreachable (`Cell.icmp`).
18
+ * Linux keeps it as a soft error on an established socket, and it continues to retransmit.
19
+ * - `simplex` drops the segments in one direction only (`Cell.drops`).
20
+ * - `mtu-blackhole` drops each segment to the observer that is longer than `Cell.mtu` bytes.
21
+ * - `netem` is tc netem on the egress of the target (`Cell.netem`): loss, duplication,
22
+ * reordering or corruption. Its outcome is a distribution.
23
+ *
24
+ * These faults are of a topology (`Cell.topology`):
25
+ * - `middlebox-forget` injects nothing. The router of the topology forgets an idle flow at its own
26
+ * idle timeout, and its form tells what a late packet then meets.
27
+ * - `proxy-idle` injects nothing. The proxy of the topology closes an idle connection at its own
28
+ * timeout.
29
+ * - `toxic` adds a toxic of Toxiproxy (`Cell.toxic`) through its HTTP API.
30
+ * - `server-close` makes the server close the connection of its peer, with the command of the
31
+ * target (`Target.close`): `CLIENT KILL` in Redis.
32
+ */
33
+ type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close";
34
+ /**
35
+ * A middlebox form tells what a router does with a flow that it forgot, when a late packet comes:
36
+ * - `reset-both` sends a reset to the two ends when it forgets the flow.
37
+ * - `reset-next` answers the next packet from inside with a reset to its sender, and drops a late
38
+ * packet from outside.
39
+ * - `silent-drop` drops each late packet, in each direction.
40
+ * - `recreate` lets a late packet from inside through, and the flow starts again. It drops a late
41
+ * packet from outside.
42
+ * - `none` is a plain NAT router. A late packet from inside starts the flow again, and the stack of
43
+ * the router answers a late packet from outside with a reset.
44
+ */
45
+ type MiddleboxForm = "reset-both" | "reset-next" | "silent-drop" | "recreate" | "none";
46
+ /**
47
+ * A router is a hop that forwards and translates the addresses of the side of the client (NAT),
48
+ * with conntrack. It is the router image of the harness.
49
+ */
50
+ interface RouterHop {
51
+ readonly name: string;
52
+ readonly kind: "router";
53
+ readonly form: MiddleboxForm;
54
+ /** This time is the idle timeout of an established flow: nf_conntrack_tcp_timeout_established, in whole seconds. */
55
+ readonly idle: Quantity;
56
+ /**
57
+ * This time is the interval of the listing that evicts each expired flow. Without it, the
58
+ * interval is 0.1 s. The DESTROY event of an expired flow waits for that listing.
59
+ */
60
+ readonly pump?: Quantity;
61
+ }
62
+ /**
63
+ * A proxy is a hop that ends each TCP connection from the side of the client and opens its own to
64
+ * the next hop. It is a stock image, pinned by digest.
65
+ */
66
+ interface ProxyHop {
67
+ readonly name: string;
68
+ readonly kind: "proxy";
69
+ readonly image: string;
70
+ /** This port is the port that the proxy listens on for the side of the client. */
71
+ readonly port: number;
72
+ readonly command?: readonly string[];
73
+ /** These files go into the proxy before it starts, by their path. In their content, {{next}} is the address and port of the next hop. */
74
+ readonly files?: Readonly<Record<string, string>>;
75
+ /** This text is the log line that tells that the proxy serves. */
76
+ readonly ready?: string;
77
+ /** This record is the HTTP API of Toxiproxy: its port, and the name of the proxy that gets the toxics. */
78
+ readonly api?: {
79
+ readonly port: number;
80
+ readonly proxy: string;
81
+ };
82
+ /** These are the sysctls of the namespace of the proxy. A privileged sidecar sets them before each run. */
83
+ readonly sysctls?: Readonly<Record<string, string>>;
84
+ /** These are the settings of the proxy, for the manifest: for example its idle timeouts. */
85
+ readonly settings?: Readonly<Record<string, string>>;
86
+ }
87
+ /**
88
+ * A hop is one box between the client and the target. Each hop is on two networks: the network
89
+ * of the side of the client, and the network of the side of the target.
14
90
  */
15
- type Fault = "blackhole" | "freeze-process" | "rst" | "stall";
91
+ type Hop = RouterHop | ProxyHop;
92
+ /**
93
+ * A topology is the chain of hops between the client and the target, in sequence from the client.
94
+ * Without hops, the client and the target are a direct pair.
95
+ */
96
+ interface Topology {
97
+ readonly name: string;
98
+ readonly hops: readonly Hop[];
99
+ }
100
+ /** A toxic is one toxic of Toxiproxy, as its API takes it. Its toxicity is 1. */
101
+ interface Toxic {
102
+ readonly type: "latency" | "timeout" | "reset_peer" | "slow_close" | "bandwidth" | "limit_data" | "slicer";
103
+ readonly stream: "downstream" | "upstream";
104
+ readonly attributes: Readonly<Record<string, number>>;
105
+ }
16
106
  /**
17
107
  * A reference is the event that the time of a detector counts from: the event that arms the
18
108
  * timer in its machine document. Keepalive counts from the last received segment. The give-up of
19
109
  * the retransmissions counts from the first retransmission of the RTO timer, not from the tail
20
110
  * loss probe one RTO before it. The request itself meets a reset, thus a reset counts from the
21
- * send of the client.
111
+ * send of the client. A FIN counts from the change of state of the socket of the peer when its
112
+ * process dies (`peer-closed`). A toxic of Toxiproxy counts from the start of its commit, as the
113
+ * injector stamps it (`fault`).
22
114
  */
23
- type Reference = "last-received" | "first-retransmission" | "request-sent";
115
+ type Reference = "last-received" | "first-retransmission" | "request-sent" | "peer-closed" | "fault";
24
116
  /**
25
117
  * This prediction tells when a connection moves again after a stall. That occurs at the first
26
118
  * firing of its retransmission timer after the stall stops, and its reply comes back one round
@@ -43,16 +135,31 @@ interface Recovers {
43
135
  readonly high: Quantity;
44
136
  };
45
137
  }
138
+ /**
139
+ * This prediction tells when the reply to the request comes back, from the send of the request.
140
+ * For example, the reply comes after the latency of a toxic, or after a forgotten flow starts again.
141
+ */
142
+ interface Replies {
143
+ readonly low: Quantity;
144
+ readonly high: Quantity;
145
+ }
46
146
  /**
47
147
  * A prediction is what a cell predicts. Either a detection occurs in an interval [T_lo, T_hi]
48
148
  * after the reference event, or nothing detects the fault in a window. Nothing detects a stall
49
149
  * that is shorter than the give-up, and the connection recovers from it. The prediction then also
50
- * tells when.
150
+ * tells when. A silent prediction can also tell when the reply to the request comes back
151
+ * (`replies`).
152
+ *
153
+ * A cell whose outcome is a distribution, for example under netem loss, has no band. Its
154
+ * prediction is `detects: null` with the reason, and its runs are measurements only. A
155
+ * measurement-only cell that names a reference (`from`) also records the time from it to the end
156
+ * of the connection, or to the last data.
51
157
  */
52
158
  type Prediction = {
53
159
  readonly detects: true;
54
160
  /** This name is the timer of the machine that fires first, or the event that detects the fault, with its errno. */
55
161
  readonly timer: string;
162
+ /** This name is the errno of the detection, or EOF for an end of file without an error. */
56
163
  readonly errno: string;
57
164
  readonly from: Reference;
58
165
  /** This time is the closed form, less the most that the timer can fire early. */
@@ -63,6 +170,12 @@ type Prediction = {
63
170
  readonly detects: false;
64
171
  readonly window: Quantity;
65
172
  readonly recovers?: Recovers;
173
+ readonly replies?: Replies;
174
+ } | {
175
+ readonly detects: null;
176
+ readonly window: Quantity;
177
+ readonly why: string;
178
+ readonly from?: Reference;
66
179
  };
67
180
  /** A target is the server that a cell connects to: a stock image. */
68
181
  interface Target {
@@ -80,16 +193,42 @@ interface Target {
80
193
  readonly set: readonly string[];
81
194
  readonly get: readonly string[];
82
195
  };
196
+ /**
197
+ * This command replaces the command of the image, for example a shell that holds the server
198
+ * process as its child. Then a `fin` fault can stop the server, and the container stays up.
199
+ */
200
+ readonly command?: readonly string[];
201
+ /** This command stops the server process in the target with SIGKILL, as argv, for a `fin` fault. */
202
+ readonly kill?: readonly string[];
203
+ /** This command makes the server close the connection of each client, as argv, for a `server-close` fault. */
204
+ readonly close?: readonly string[];
83
205
  }
84
206
  /** A client is what the runs of a cell use: the TCP client of the harness, or a driver on its socket. */
85
207
  type Client = "tcp" | "ioredis";
208
+ /**
209
+ * A client image is a process that a lab starts as the client of each run, in the network
210
+ * namespace of the client. An example is an agent of an application on Node. Its command gets the
211
+ * arguments of the TCP client of the harness. These are the address and the port of the target, then
212
+ * the options of the cell. It writes JSON lines with an `ev` and a `t` (CLOCK_MONOTONIC in
213
+ * nanoseconds) on its standard output, as the client of the harness does.
214
+ */
215
+ interface ClientImage {
216
+ readonly image: string;
217
+ readonly command: readonly string[];
218
+ }
86
219
  /**
87
220
  * An observer is the side whose detection a cell measures. The client is one observer. The
88
- * server is the other observer. It is the hold of the target on the connection of the client, at
221
+ * server is another observer. It is the hold of the target on the connection of the client, at
89
222
  * its server socket in its namespace. The fault is on the peer of the observer. Thus
90
223
  * freeze-process freezes the target for the client, and the client for the server.
224
+ *
225
+ * A proxy of a topology is the third observer: its socket toward the client, or its socket toward
226
+ * the target.
91
227
  */
92
- type Observer = "client" | "server";
228
+ type Observer = "client" | "server" | {
229
+ readonly hop: string;
230
+ readonly toward: "client" | "target";
231
+ };
93
232
  /** A cell is one cell of the matrix: a topology, its settings, a workload, a fault and the prediction. */
94
233
  interface Cell {
95
234
  readonly name: string;
@@ -97,8 +236,15 @@ interface Cell {
97
236
  /** These are the sysctls of the network namespace of the client, as the kernel spells them. The harness sets them before the client connects. */
98
237
  readonly sysctls: Readonly<Record<string, string>>;
99
238
  readonly probe: {
100
- /** This mode tells if the client is idle after its first reply, or sends a request into the fault. */
101
- readonly mode: "idle" | "inflight";
239
+ /**
240
+ * This mode tells if the client is idle after its first reply, or sends a request into the
241
+ * fault. In a stream, it sends `count` requests into it, each `interval` after the reply before.
242
+ */
243
+ readonly mode: "idle" | "inflight" | "stream";
244
+ /** This count is the number of requests of a stream. */
245
+ readonly count?: number;
246
+ /** This time is the pause after each reply of a stream, before the next request. */
247
+ readonly interval?: Quantity;
102
248
  /** This flag sets SO_KEEPALIVE without overrides for the socket: the keepalive sysctls of the namespace apply. */
103
249
  readonly keepalive: boolean;
104
250
  /** This time sets TCP_USER_TIMEOUT. Without it, the default of the kernel (off) applies. */
@@ -122,6 +268,21 @@ interface Cell {
122
268
  readonly fault: Fault;
123
269
  /** This time is the length of a stall, from the send of the request. */
124
270
  readonly stall?: Quantity;
271
+ /** This code is the ICMP unreachable of an `unreachable` fault. */
272
+ readonly icmp?: "host" | "net" | "port" | "admin";
273
+ /** This direction is the one that a `simplex` fault drops: the segments to the server, or to the client. */
274
+ readonly drops?: "to-server" | "to-client";
275
+ /** This length is the longest segment, in bytes, that an `mtu-blackhole` fault lets through to the observer. */
276
+ readonly mtu?: number;
277
+ /** This specification is the tc netem of a `netem` fault, on the egress of the target: for example "loss 5%". */
278
+ readonly netem?: string;
279
+ /**
280
+ * This delay goes on the egress of the target before the client connects, with tc netem. Thus
281
+ * each round trip is longer by the delay, and so is the RTO.
282
+ */
283
+ readonly latency?: Quantity;
284
+ /** These commands run in the target before a client connects, each as argv: for example, a large value to read. */
285
+ readonly targetSetup?: readonly (readonly string[])[];
125
286
  readonly prediction: Prediction;
126
287
  /** This side is the side whose detection the cell measures. Without it, the side is the client. */
127
288
  readonly observer?: Observer;
@@ -129,6 +290,16 @@ interface Cell {
129
290
  readonly targetSettings?: Readonly<Record<string, string>>;
130
291
  /** These are the sysctls of the network namespace of the target, for a cell on the server side: its tcp_retries2. */
131
292
  readonly targetSysctls?: Readonly<Record<string, string>>;
293
+ /** This topology puts hops between the client and the target. Without it, the client and the target are a direct pair. */
294
+ readonly topology?: Topology;
295
+ /** This place is where the fault goes, when it is not the peer of the observer: for example one side of a proxy. */
296
+ readonly faultAt?: FaultPlace;
297
+ /** This time is how long an in-flight client stays idle after the fault holds, before it sends its request. */
298
+ readonly idleBefore?: Quantity;
299
+ /** This toxic is the toxic of a `toxic` fault, which the Toxiproxy hop gets. */
300
+ readonly toxic?: Toxic;
301
+ /** This image replaces the TCP client of the harness for each run. */
302
+ readonly client?: ClientImage;
132
303
  /** This count is the number of runs of the cell (11), across new environments (two at least). */
133
304
  readonly runs: number;
134
305
  readonly environments: number;
@@ -139,6 +310,21 @@ interface Cell {
139
310
  */
140
311
  readonly concurrency?: number;
141
312
  }
313
+ /**
314
+ * A fault place is the namespace that gets the rules of a fault. "target" and "client" are the two
315
+ * ends. A hop place is a hop, with the side that the rules face. That side is the next hop toward
316
+ * the client, or the next hop toward the target.
317
+ */
318
+ type FaultPlace = "target" | "client" | {
319
+ readonly hop: string;
320
+ readonly toward: "client" | "target";
321
+ };
322
+ /** 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. */
323
+ interface MiddleboxEvent {
324
+ readonly ev: string;
325
+ readonly t: number;
326
+ readonly [field: string]: unknown;
327
+ }
142
328
  /** A probe event is one line that the client wrote. `t` is CLOCK_MONOTONIC in nanoseconds: an instant, not an amount. */
143
329
  interface ProbeEvent {
144
330
  readonly ev: string;
@@ -149,6 +335,11 @@ interface ProbeEvent {
149
335
  interface Run {
150
336
  readonly environment: number;
151
337
  readonly index: number;
338
+ /**
339
+ * This flag marks the warm-up of an environment. It occurs before the counted runs, and the
340
+ * verdict does not count it. On a loaded machine, a new environment is sometimes slow at first.
341
+ */
342
+ readonly warmup?: true;
152
343
  readonly events: readonly ProbeEvent[];
153
344
  /** This bracket is the time of the injector around the commit of the fault, on the same clock. It is null for a freeze. */
154
345
  readonly fault: {
@@ -171,6 +362,25 @@ interface Run {
171
362
  * the run void. Null also makes the run void: no clock line came after the run to tell the count.
172
363
  */
173
364
  readonly lost: number | null;
365
+ /** These are the addresses and the namespaces of a topology, which the measurement of a run reads. */
366
+ readonly topology?: RunTopology;
367
+ /** These are the lines of the routers of a topology from the start to the end of the run. */
368
+ readonly middlebox?: readonly MiddleboxEvent[];
369
+ }
370
+ /**
371
+ * A run topology tells where each socket of a run is, in a topology:
372
+ * - `targetPeer` is the address of the peer of the target, as the target sees it: the address of
373
+ * the last hop.
374
+ * - `hops` are the network namespaces of the hops, by their names, with the address of the peer of
375
+ * each hop on each side.
376
+ */
377
+ interface RunTopology {
378
+ readonly targetPeer: string;
379
+ readonly hops: Readonly<Record<string, {
380
+ readonly netns: number;
381
+ readonly client: string;
382
+ readonly target: string;
383
+ }>>;
174
384
  }
175
385
  //#endregion
176
386
  //#region src/observe.d.ts
@@ -207,20 +417,45 @@ interface Measured {
207
417
  * the RTO timer. After a stall, they are each retransmission from the send, the loss probe first.
208
418
  */
209
419
  readonly steps: readonly Quantity[];
420
+ /**
421
+ * These are the reply times of a measurement-only cell. For each request after the first, a
422
+ * sample is the time from its send to the first data after it.
423
+ */
424
+ readonly samples?: readonly Quantity[];
210
425
  /** This count is the number of keepalive probes that the kernel sent. */
211
426
  readonly probes: number;
212
427
  /** This count is the number of segments that the kernel sent again. */
213
428
  readonly retransmissions: number;
214
429
  /** This count is the number of segments that the socket received after the request. */
215
430
  readonly acks: number;
431
+ /** This count is the number of bytes that the client read after the request. */
432
+ readonly bytes?: number;
433
+ /**
434
+ * This time is when a router of the topology forgot the flow of the client. It counts from the
435
+ * last segment that the client received before the fault. It is null when no router forgot the flow.
436
+ */
437
+ readonly forgot?: Quantity | null;
216
438
  /** This text is the reason that the run cannot count, or null. A void run is not a pass and not a finding. */
217
439
  readonly void: string | null;
218
440
  /** This text tells what in the run disagrees with the prediction, other than its timing, or null. */
219
441
  readonly finding: string | null;
220
442
  }
443
+ /** A socket place is a socket of a run: its namespace and its cookie. */
444
+ interface SocketPlace {
445
+ readonly netns: number;
446
+ readonly cookie: number;
447
+ }
448
+ /**
449
+ * This function finds the socket of the observer of a cell in a run:
450
+ * - the client socket, by the address that it connected from, in its namespace.
451
+ * - the socket of the target for the same connection, in its namespace.
452
+ * - a socket of a proxy of the topology, toward the client or toward the target.
453
+ */
454
+ export declare const socketOf: (cell: Cell, run: Run) => SocketPlace | undefined;
221
455
  /**
222
456
  * This function reads one run against its cell, from the records of the kernel first.
223
- * - The socket of the run is the socket that the client connected from, in its namespace.
457
+ * - The socket of the run is the socket of its observer. It is the socket that the client connected
458
+ * from, in its namespace, or the socket of the target or of a proxy for that connection.
224
459
  * - The fault must hold before the event that it must meet. That is before the request goes into
225
460
  * it, and before the first keepalive probe. A live peer answers that probe, and the answer
226
461
  * starts the idle time again. A run where the fault was late is void. A run whose records the
@@ -241,8 +476,11 @@ export declare const NOISE: Quantity;
241
476
  export declare const RELATIVE = .05;
242
477
  /** These are the run counts of a cell: 11, then 22 and 44 while its median crosses the edge of the band. */
243
478
  export declare const STEPS: readonly [11, 22, 44];
244
- /** A decision is the result of a verdict. */
245
- type Decision = "holds" | "fails" | "extend" | "inconclusive";
479
+ /**
480
+ * A decision is the result of a verdict. A cell whose outcome is a distribution has no band, and
481
+ * its decision is `measured`: a record, not a test.
482
+ */
483
+ type Decision = "holds" | "fails" | "extend" | "inconclusive" | "measured";
246
484
  /** An interval is a range of times. */
247
485
  interface Interval {
248
486
  readonly low: Quantity;
@@ -290,8 +528,34 @@ export declare const verdictOf: (prediction: Prediction, runs: readonly Measured
290
528
  export declare const harnessImage: () => Promise<string>;
291
529
  /** This function gives the image of the Node client. */
292
530
  export declare const nodeClientImage: () => Promise<string>;
293
- /** A network fault is a fault that the injector applies, by the name of its manifestation. */
294
- type NetworkFault = "blackhole" | "rst";
531
+ /** This function gives the image of the router. */
532
+ export declare const routerImage: () => Promise<string>;
533
+ /**
534
+ * This time is the interval of the listing of a router, when its hop does not set one. A listing
535
+ * evicts each expired flow that it walks past, and the DESTROY event of that flow follows.
536
+ */
537
+ export declare const PUMP: Quantity;
538
+ /**
539
+ * This time is the longest listing of the conntrack table of a router that a prediction assumes.
540
+ * A DESTROY follows an expiry within the interval of the pump and two listings. On 2026-10-15,
541
+ * the slowest of 500 listings in an idle router took 19.4 ms, and the median took 5.5 ms. A
542
+ * longer listing writes a `pump-slow` line, and a run with one near its detection is void.
543
+ */
544
+ export declare const LISTING: Quantity;
545
+ /**
546
+ * A network fault is a fault that the injector applies as a filter rule. It is a black hole (both
547
+ * directions, or one), or a reset on the next segment. It can also be an ICMP unreachable for each
548
+ * segment, or a drop of each segment that is longer than a length.
549
+ */
550
+ type NetworkFault = "blackhole" | "rst" | "reject" | "mtu";
551
+ /** These are the options of a network fault: the direction of a black hole, the code of a reject, the length of an MTU drop. */
552
+ interface FaultOptions {
553
+ /** This direction is relative to the namespace of the rule: `in` drops what comes from the peer, `out` what goes to it. */
554
+ readonly direction?: "in" | "out";
555
+ readonly code?: "host" | "net" | "port" | "admin";
556
+ /** This length is the longest segment in bytes that an MTU drop lets through. */
557
+ readonly above?: number;
558
+ }
295
559
  /**
296
560
  * A fault side is the namespace that gets the rules of a fault: the namespace of the peer of the
297
561
  * observer. For the client, it is the namespace of the target (the injector). For the server, it
@@ -301,6 +565,17 @@ type NetworkFault = "blackhole" | "rst";
301
565
  * because it fires only for a retransmission that went out.
302
566
  */
303
567
  type FaultSide = "target" | "client";
568
+ /** A hop of a manifest is one hop of a topology, as the lab started it. */
569
+ interface ManifestHop {
570
+ readonly name: string;
571
+ readonly kind: "router" | "proxy";
572
+ readonly image: string;
573
+ readonly id: string;
574
+ /** These are the settings of the hop: the form and the idle timeout of a router, the files and settings of a proxy. */
575
+ readonly settings: Readonly<Record<string, string>>;
576
+ /** These are the sysctls of the namespace of the hop, as read back after the harness set them. */
577
+ readonly sysctls: Readonly<Record<string, string>>;
578
+ }
304
579
  /** A manifest is what a lab records about its machine, its images and its settings. */
305
580
  interface Manifest {
306
581
  readonly kernel: string;
@@ -322,18 +597,32 @@ interface Manifest {
322
597
  readonly id: string;
323
598
  readonly packages: string[];
324
599
  };
325
- /** This record is the client of each run: the TCP client of the harness, or a driver from its own image. */
600
+ /** This record is the client of each run: the TCP client of the harness, a driver, or an image that the cell gives. */
326
601
  readonly client: {
327
- readonly kind: Client;
602
+ readonly kind: Client | "image";
328
603
  readonly image: string;
329
604
  };
330
605
  /** These are the namespaces that the prober watched, by inode. */
331
606
  readonly netns: {
332
607
  readonly client: number;
333
608
  readonly target: number;
609
+ readonly hops?: Readonly<Record<string, number>>;
334
610
  };
335
611
  /** These are the sysctls of the client namespace, as read back after the harness set them. */
336
612
  readonly sysctls: Readonly<Record<string, string>>;
613
+ /** These are the hops of a topology, in sequence from the client. */
614
+ readonly hops?: readonly ManifestHop[];
615
+ }
616
+ /** These are the JSON lines of a program, as they arrive. */
617
+ interface Lines<T extends {
618
+ readonly ev: string;
619
+ }> {
620
+ readonly seen: T[];
621
+ /** These are the other lines that it wrote, for example an error. They explain a program that stopped its output. */
622
+ readonly other: string[];
623
+ consume: (stream: NodeJS.ReadableStream) => void;
624
+ /** This method gives the first line of one of these kinds, or it rejects after `within` ms. */
625
+ until(kinds: readonly string[], within: number, who: string): Promise<T>;
337
626
  }
338
627
  /** A probe is the client container of one run. */
339
628
  interface Probe {
@@ -344,17 +633,51 @@ interface Probe {
344
633
  /** This method freezes the client with the cgroup freezer: its process stops, and its kernel continues to answer. */
345
634
  freeze(): Promise<void>;
346
635
  thaw(): Promise<void>;
636
+ /** This method stops the process of the client with SIGKILL. Its kernel then closes its socket with a FIN, or a reset. */
637
+ kill(): Promise<void>;
347
638
  readonly events: readonly ProbeEvent[];
348
639
  stop(): Promise<void>;
349
640
  }
350
- /** A lab is one environment: the target, the client, the injector and the prober. */
641
+ /** An agent is a container that a lab starts in the namespaces of the target, for example a Node process that signals the server. */
642
+ interface Agent {
643
+ /** These are the JSON lines that it wrote, as they arrive. */
644
+ readonly lines: Lines<{
645
+ readonly ev: string;
646
+ }>;
647
+ /** This method starts a command in the agent, and gives its standard output. */
648
+ exec(argv: readonly string[]): Promise<string>;
649
+ stop(): Promise<void>;
650
+ }
651
+ /** A lab is one environment: the target, the client, the hops of a topology, the injectors and the prober. */
351
652
  interface Lab {
653
+ /** This address is the address that the client connects to: the target, or the first proxy of a topology. */
352
654
  readonly targetIp: string;
655
+ /** This port is the port that the client connects to. */
656
+ readonly targetPort: number;
353
657
  readonly clientIp: string;
354
658
  readonly netns: {
355
659
  readonly client: number;
356
660
  readonly target: number;
661
+ readonly hops?: Readonly<Record<string, number>>;
357
662
  };
663
+ /** This record is where each socket of a topology is, or null for a direct pair. */
664
+ readonly topology: RunTopology | null;
665
+ /** These are the lines of the routers of a topology, in sequence, each with the name of its hop. A direct pair has none. */
666
+ readonly middlebox: readonly MiddleboxEvent[];
667
+ /** This method adds a toxic to the Toxiproxy hop of a topology, or removes it with null. The stamps of the injector bracket it. */
668
+ toxic(toxic: Toxic | null): Promise<{
669
+ t0: number;
670
+ t1: number;
671
+ }>;
672
+ /** This method makes the target close the connection of each client, with the command of the target. */
673
+ serverClose(): Promise<void>;
674
+ /**
675
+ * This method starts an agent in the network namespace of the target, and in its process
676
+ * namespace with `pid`. Its command keeps it up, for example `sleep infinity`.
677
+ */
678
+ agent(image: string, command: readonly string[], options?: {
679
+ readonly pid?: boolean;
680
+ }): Promise<Agent>;
358
681
  /** These are all the records that the prober wrote, in sequence. */
359
682
  readonly kernel: readonly KernelRecord[];
360
683
  /** This method sets the sysctls of the client namespace through its privileged sidecar, and reads them back. */
@@ -364,22 +687,46 @@ interface Lab {
364
687
  /** This method sets the sysctls of the target namespace through a privileged sidecar there, and reads them back. */
365
688
  targetSysctl(values: Readonly<Record<string, string>>): Promise<Record<string, string>>;
366
689
  /**
367
- * This method applies a fault between the client and the target, from the namespace of the
368
- * target. The fault is a black hole, or a reset that answers the next segment of the client. The stamps of
369
- * the injector bracket it.
690
+ * This method applies a fault between the client and the target, by default from the namespace
691
+ * of the target. The fault is a black hole, or a reset that answers the next segment of the
692
+ * client. At a proxy of a topology, the rules face the peer of the proxy on one side. The stamps
693
+ * of the injector bracket it.
370
694
  */
371
- fault(kind: NetworkFault, at?: FaultSide): Promise<{
695
+ fault(kind: NetworkFault, at?: FaultPlace, options?: FaultOptions): Promise<{
372
696
  t0: number;
373
697
  t1: number;
374
698
  }>;
375
699
  /** This method removes a fault. */
376
- lift(kind: NetworkFault, at?: FaultSide): Promise<void>;
700
+ lift(kind: NetworkFault, at?: FaultPlace, options?: FaultOptions): Promise<void>;
701
+ /**
702
+ * This method puts tc netem on the egress of a namespace, for example "delay 20ms" or "loss 5%",
703
+ * or removes it with null. The stamps of the injector bracket it.
704
+ */
705
+ netem(spec: string | null, at?: FaultSide): Promise<{
706
+ t0: number;
707
+ t1: number;
708
+ }>;
709
+ /**
710
+ * This method sets the TSO, GSO and GRO of a namespace. With them off, netem and a length match
711
+ * act on segments, not on GSO super-packets of up to 64 KB.
712
+ */
713
+ offload(on: boolean, at?: FaultSide): Promise<void>;
714
+ /** This method stops the server process in the target with SIGKILL, with the command of the target. */
715
+ kill(): Promise<void>;
716
+ /**
717
+ * This method resolves the neighbors of the path in the two directions, with a ping from each
718
+ * namespace. Without it, the first connection of an environment waits for ARP. Under a netem
719
+ * delay, that wait is one more delayed round, and its round trip and RTO are longer.
720
+ */
721
+ warm(): Promise<void>;
722
+ /** This method runs commands in the target before a client connects, each as argv. */
723
+ setup(commands: ReadonlyArray<readonly string[]>): Promise<void>;
377
724
  /**
378
725
  * This method stops a black hole at an instant on the monotonic clock of the VM. The injector
379
726
  * waits for that instant in the VM, thus the length of a stall does not include the latency of
380
727
  * a docker exec. Stamps bracket it, as for a fault.
381
728
  */
382
- heal(at: FaultSide, when: number): Promise<{
729
+ heal(at: FaultPlace, when: number): Promise<{
383
730
  t0: number;
384
731
  t1: number;
385
732
  }>;
@@ -402,20 +749,46 @@ interface Lab {
402
749
  manifest(sysctls: Readonly<Record<string, string>>, settings?: Readonly<Record<string, string>>, targetSysctls?: Readonly<Record<string, string>>): Promise<Manifest>;
403
750
  close(): Promise<void>;
404
751
  }
752
+ /** This function gives the content of a file of a proxy, with the address of the next hop for each {{next}}. */
753
+ export declare const templated: (content: string, next: string) => string;
754
+ /** This function gives the JSON body of a toxic for the API of Toxiproxy. Each toxic of a cell has the name `fault`. */
755
+ export declare const toxicBody: (toxic: Toxic) => string;
756
+ /**
757
+ * This function gives the routes of a chain: for each router, the networks after it, which the
758
+ * element before it reaches through it. Element 0 is the client, and element k is hop k - 1. Network
759
+ * j is the network between element j and element j + 1.
760
+ */
761
+ export declare const routesOf: (hops: readonly Pick<Hop, "kind">[]) => Array<{
762
+ readonly from: number;
763
+ readonly via: number;
764
+ readonly networks: readonly number[];
765
+ }>;
766
+ /**
767
+ * This function gives the endpoint that an element connects to: the first element after it that
768
+ * is not a router. Element 0 is the client, element k is hop k - 1, and the last element is the target.
769
+ */
770
+ export declare const endpointAfter: (hops: readonly Pick<Hop, "kind">[], element: number) => number;
405
771
  /**
406
772
  * This function opens one environment:
407
- * - a new network and the stock target.
773
+ * - a chain of new networks, and the stock target on the last one. A direct pair has one network.
408
774
  * - a client that holds its own network namespace, with a privileged sidecar in it for its
409
- * sysctls. Docker Desktop keeps /proc/sys read-only in a container that operates.
775
+ * sysctls and its routes. Docker Desktop keeps /proc/sys read-only in a container that operates
776
+ * without privileges.
777
+ * - each hop of the topology, on the network of each of its sides. A hop is a router, or a stock
778
+ * proxy with an injector sidecar in its namespace.
410
779
  * - an injector sidecar in the namespace of the target.
411
- * - the prober, a privileged container that loads the kernel program and watches the two
412
- * namespaces.
780
+ * - the prober, a privileged container that loads the kernel program and watches the namespace of
781
+ * the client, of the target and of each hop.
413
782
  *
414
783
  * The client of each run is its own container in the namespace of the client. It is the TCP
415
- * client of the harness, or a driver from its own image (ioredis on Node). The two print the same
416
- * lines.
784
+ * client of the harness, a driver from its own image (ioredis on Node), or an image that the
785
+ * caller gives. Each prints the same lines.
786
+ *
787
+ * The client reaches the networks after a router through that router, by a route that its
788
+ * privileged sidecar adds. A router translates the address of the side of the client, thus the
789
+ * target and each later hop see the router as the peer.
417
790
  */
418
- export declare const openLab: (target: Target, kind?: Client) => Promise<Lab>;
791
+ export declare const openLab: (target: Target, kind?: Client | ClientImage, topology?: Topology) => Promise<Lab>;
419
792
  //#endregion
420
793
  //#region src/run.d.ts
421
794
  /** A cell result is a cell with its manifests, its runs, their measurements and its verdict. */
@@ -423,6 +796,8 @@ interface CellResult {
423
796
  readonly cell: Cell;
424
797
  readonly manifests: readonly Manifest[];
425
798
  readonly runs: readonly Run[];
799
+ /** These are the warm-up runs, one for each environment before its counted runs. The verdict does not count them. */
800
+ readonly warmups: readonly Run[];
426
801
  readonly measured: readonly Measured[];
427
802
  readonly verdict: Verdict;
428
803
  /** These times are when its first environment opened and its last environment closed, on the wall clock. */
@@ -448,8 +823,10 @@ export declare const spread: (runs: number, environments: number) => number[];
448
823
  * the cell from 11 runs to 22, and then to 44, each extension in new environments. With a
449
824
  * concurrency of more than one, that many environments operate at the same time, each with its
450
825
  * own prober. The runs then go across at least as many environments.
826
+ *
827
+ * Each environment does a warm-up first, and the result keeps it apart from the counted runs.
451
828
  */
452
829
  export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
453
830
  //#endregion
454
- export type { Cell, CellResult, Client, Decision, Fault, FaultSide, Interval, KernelRecord, Lab, Manifest, Measured, NetworkFault, Observer, Outcome, Prediction, Probe, ProbeEvent, Recovers, Reference, Run, Target, Verdict };
831
+ export type { Agent, Cell, CellResult, Client, ClientImage, Decision, Fault, FaultPlace, FaultSide, Hop, Interval, KernelRecord, Lab, Manifest, ManifestHop, Measured, MiddleboxEvent, MiddleboxForm, NetworkFault, Observer, Outcome, Prediction, Probe, ProbeEvent, ProxyHop, Recovers, Reference, Replies, RouterHop, Run, RunTopology, SocketPlace, Target, Topology, Toxic, Verdict };
455
832
  //# sourceMappingURL=index.d.ts.map