@mark1russell7/harness 0.3.0 → 0.4.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/README.md CHANGED
@@ -10,9 +10,10 @@ A **cell** (`cell.ts`) is all that one measurement needs, as data:
10
10
 
11
11
  - **The topology.** A stock target image, pinned by digest, and the hops between the client and the target (`Cell.topology`). Without hops, the client and the target are a direct pair.
12
12
  - **The settings.** The sysctls of the client namespace, and the socket options of the client.
13
- - **The workload.** A connection that stays idle, or a connection with a request in flight.
14
- - **The fault.** A black hole, a reset, a frozen process, or a stall. In a topology, also a router that forgets a flow, a proxy that closes an idle connection, or a toxic of Toxiproxy.
13
+ - **The workload.** A connection that stays idle, a connection with a request in flight, or a stream of requests. A stream of `GET` requests for a long value keeps many segments in flight.
14
+ - **The fault.** A black hole, a reset, a frozen process, or a stall. In a topology, also a fault at a router or a proxy, or a router that forgets a flow. Also a proxy that closes an idle connection, or a toxic of Toxiproxy.
15
15
  - **The prediction.** An interval [T_lo, T_hi] after the event that arms the timer, or silence in a window. After a stall, the prediction also tells when the connection recovers. A silent prediction can also tell when the reply comes back (`replies`).
16
+ - **The assumptions of the band.** A band can name the range of first round trips that it assumes (`rtt`), and the proxy times that it includes (`through`).
16
17
 
17
18
  ## The environment
18
19
 
@@ -31,7 +32,7 @@ Each command is an argv array.
31
32
 
32
33
  A topology (`Topology`) is the chain of hops from the client to the target, as data. Each hop is on two networks, one for each side. Network j joins element j and element j + 1: the client is element 0, and the target is the last element.
33
34
 
34
- - **A router** (`RouterHop`) is the router image of the harness. It forwards, and it translates the addresses of the side of the client (NAT) with conntrack. Its idle timeout is `nf_conntrack_tcp_timeout_established`, in whole seconds. The router is privileged, thus it sets the sysctls of its own namespace. Its form (`MiddleboxForm`) tells what a late packet meets after the router forgets the flow:
35
+ - **A router** (`RouterHop`) is the router image of the harness. It forwards, and it translates the addresses of the side of the client (NAT) with conntrack. Its idle timeout is `nf_conntrack_tcp_timeout_established`, in whole seconds. The router is privileged, thus it sets the sysctls of its own namespace, and the `sysctls` of its hop. An injector sidecar shares its namespace. Its form (`MiddleboxForm`) tells what a late packet meets after the router forgets the flow:
35
36
 
36
37
  | Form | At the expiry | A late packet from inside | A late packet from outside |
37
38
  | --- | --- | --- | --- |
@@ -63,7 +64,8 @@ The harness builds three images. The tag of each image comes from the content of
63
64
  - **The harness image** (`image/Dockerfile`) has two stages, each a digest-pinned Alpine. `Cargo.lock` pins the crates. The image holds:
64
65
  - the kernel program of the prober, compiled against the BTF of the kernel that builds it, and its Rust collector.
65
66
  - `client`, the TCP client of a cell. Rust's `std::net` sets no socket option by itself, thus the measurement gets the timers of the namespace. The client also samples `TCP_INFO` each 2 ms, a second vantage on its socket. It waits in `poll()`, which does not lock the socket. A keepalive timer that finds its socket held tries again HZ/20 later.
66
- - `inject`, which applies a fault in one `iptables-restore` commit, with monotonic stamps around the commit. Thus the start of a fault is known in the VM. A `docker exec` from Windows comes 190 ms late at the median.
67
+ - In a stream, a reply of the `client` is whole at its CRLF, or after the length of a bulk string. The client then writes a `replied` line.
68
+ - `inject`, which applies a fault in one `iptables-restore` commit, with monotonic stamps around the commit. Thus the start of a fault is known in the VM. A `docker exec` from Windows comes 190 ms late at the median. With `--via DEV`, the rules go in the FORWARD chain, on the device toward one side.
67
69
  - **The Node client image** (`image/node`) is a driver client: ioredis 6.0.0 on Node 24.21.0. The digest of the image and an npm lockfile pin them. `client-ioredis.mjs` prints the lines of the TCP client on the same clock, and it adds the status changes of ioredis as `status` lines.
68
70
  - **The router image** (`image/router`) is a digest-pinned Alpine with conntrack-tools, nftables, iproute2 and Python. `mbx.sh` sets the form of the router in one nftables table, `ctwatch.py` writes the conntrack events, and `resetter.py` sends the resets of the `reset-both` form. Each run records its packages in the manifest.
69
71
 
@@ -81,15 +83,22 @@ ioredis sets the keepalive of its socket itself, after it connects. Thus a cell
81
83
  - **A simplex partition** (`simplex`, `Cell.drops`) drops the segments to the server, or the segments to the client, but not both.
82
84
  - **A FIN** (`fin`) stops the process of the peer with SIGKILL while its kernel stays up. For the client, the target starts its server as the child of a shell (`Target.command`), and `Target.kill` stops it. For the server, the harness stops the container of the client.
83
85
  - **An MTU black hole** (`mtu-blackhole`, `Cell.mtu`) drops each segment longer than the length, in the namespace that receives it.
84
- - **netem** (`netem`, `Cell.netem`) is tc netem on the egress of the target: loss, duplication, reordering or corruption. The outcome is a distribution.
86
+ - **netem** (`netem`, `Cell.netem`) is tc netem on the egress of the target: loss, duplication, reordering or corruption. The outcome is a distribution. At a hop, netem is on the egress of the device of the hop toward one side.
85
87
  - **A latency** (`Cell.latency`) is tc netem delay on the egress of the target before the client connects. Thus the round trip and the RTO include it.
86
- - **A fault at a hop** (`Cell.faultAt`) puts the rules of a fault in the namespace of a proxy, toward the client or toward the target. The rules act on the sockets of the proxy. A router forwards past INPUT and OUTPUT, thus the harness refuses a fault at a router.
88
+ - **A fault at a proxy** (`Cell.faultAt`) puts the rules of a fault in the namespace of the proxy, toward the client or toward the target. The rules act on the sockets of the proxy.
89
+ - **A fault at a router** (`Cell.faultAt`) puts FORWARD rules in the namespace of the router, through its injector sidecar. A router forwards past INPUT and OUTPUT. Each rule faces one side by the device toward it, and by the address of the peer on that side:
90
+ - A black hole drops what comes in from that side (`-i DEV -s PEER`) and what goes out to it (`-o DEV -d PEER`). A simplex partition drops one of the two.
91
+ - A reset answers each segment from that side with a reset from the router. The reset has the address that the segment went to.
92
+ - An ICMP unreachable comes from the address of the router on that device, not from the peer. Its rate limit is in the `sysctls` of the router.
93
+ - The rules of the fault are in the filter table of iptables. The form of the router is a table of nftables at the same priority. Thus the two can both drop a packet, and an accept of one does not pass a drop of the other.
94
+ - Conntrack sees each packet before a FORWARD rule drops it. Thus a retransmission into a black hole at the router refreshes the flow. The resets of the `reset-both` form leave from the stack of the router, past its own FORWARD rules.
95
+ - **No fault** (`none`) injects nothing. A calibration cell measures the path itself (`Cell.calibrates`).
87
96
  - **A forgotten flow** (`middlebox-forget`) injects nothing. The router of the topology forgets an idle flow at its own timeout. `Cell.idleBefore` holds an in-flight request until after the forget.
88
97
  - **An idle proxy** (`proxy-idle`) injects nothing. The proxy closes an idle connection at its own timeout.
89
98
  - **A toxic** (`toxic`, `Cell.toxic`) is one toxic of Toxiproxy, added through its HTTP API by `inject http`, with monotonic stamps around the request. Its name is `fault`, and the harness removes it after the run. A cell whose fault is another one adds its toxic first: a slow close, then the close of the server.
90
99
  - **A close of the server** (`server-close`) makes the server close the connection of each client, with `Target.close`: `CLIENT KILL` for Redis.
91
100
 
92
- A length match and netem act on GSO super-packets of up to 64 KB, not on segments, while TSO, GSO and GRO are on. Thus the cells of `mtu-blackhole` and `netem` turn them off on the two ends (`lab.offload`).
101
+ A length match and netem act on GSO super-packets of up to 64 KB, not on segments, while TSO, GSO and GRO are on. Thus the cells of `mtu-blackhole` and `netem` turn them off on the two ends (`lab.offload`). They also turn them off on the two devices of a hop where the fault is.
93
102
 
94
103
  Each run starts from kernels that keep nothing from the run before it (`lab.forget()`). When a connection closes, each kernel keeps TCP metrics for the address of its peer. These metrics set the first round trip of the next connection, and thus its RTO. The harness removes the `tcp_metrics` of the two namespaces before each run.
95
104
 
@@ -101,13 +110,17 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
101
110
  - **The time.** It times the detection from the reference event of the prediction to the error report of the kernel. eBPF stamps the two events. For keepalive, the reference is the last received segment. For the give-up, it is the first retransmission of the RTO timer, not the tail loss probe one RTO before it. For a reset, it is the send of the client, which no kernel record marks. The client stamps the send before its write.
102
111
  - **The other views.** It also gives the same interval from the `TCP_INFO` samples of the client, and the delay before the client saw the error. It also gives the steps of the kernel: each keepalive probe, or each expiry of the RTO timer.
103
112
  - **Void runs.** A run is void when the timer that drives the measurement was two jiffies or more behind its wheel slot in the timed interval. The wheel does a slot in the tick when it is due. Thus such a firing measured the host, which stopped a vCPU, not the kernel. A keepalive timer that found its socket held also makes the run void. A run whose fault came too late is void too.
113
+ - **The first round trip.** The measurement gives the first round trip of the socket of the observer (`rtt`). It is the smoothed round trip in the first record of the socket: the sample of the handshake. A run whose first round trip is above the range of its prediction is void, with the reason `rtt-over-assumption` and the value. Such a run measured another path than the band assumes. For example, a round trip of 4.826 ms makes an RTO of 52 jiffies, and a ladder that assumes 1 ms has 51.
104
114
  - **Findings.** A finding is a wrong errno, a detector that did not fire, or a detection where the prediction is silence. A router that forgot the flow in the window of a silent prediction is also a finding.
105
115
  - **A reply.** A silent prediction with `replies` times the send of the request to the first segment with data that comes back. A proxy can acknowledge the request first, with no data.
106
116
  - **A toxic.** A toxic counts from the start of its commit (`fault`), as the injector stamps it.
107
117
  - **A topology.** A run in a topology also gives the bytes that the client read after its request, and when a router forgot its flow (`forgot`).
108
118
  - **Recovery.** After a stall (`prediction.recovers`), it times the send to the first segment back after the stall stopped. That segment is the answer of the server to the retransmission that got through. A run whose stall stopped outside the window of its prediction is void. A run where nothing came back is a finding.
109
119
  - **An end of file.** A FIN counts from the change of state of the socket of the peer when it closes (`peer-closed`). It ends at the change of the socket of the observer to CLOSE_WAIT. The kernel stamps the two changes.
110
- - **A stream.** In a stream (`probe.mode` is `stream`), the client sends `count` requests. The run keeps the time from each send to the first data after it (`samples`).
120
+ - **A stream.** In a stream (`probe.mode` is `stream`), the client sends `count` requests. The run keeps the time from each send to the last byte of its reply (`samples`). A record without `replied` lines gives the time to the first data.
121
+ - **Reordering.** A measurement-only run also gives what reordering, duplicates and losses did (`reordering`). At the client, it counts the arrivals beyond a hole and the arrivals of data that it had already, against `rcv_nxt`. From the sender, it gives the most SACKed and lost segments, the DSACKs, the fast retransmissions, and the reordering degree and count of the kernel.
122
+ - **An arrival is not always one segment.** The segments that arrive while the owner holds the socket wait in its backlog, and the kernel joins the contiguous ones. Thus the kernel counts out-of-order segments (`rcv_ooopack`), and the prober counts arrivals.
123
+ - **A proxy's own time.** A calibration cell (`Cell.calibrates`) gives the time of its proxy for each request and its reply (`crossings`). Each time goes from an arrival on one socket of the proxy to its next write on the other socket (a `tx` record).
111
124
 
112
125
  ## The verdict
113
126
 
@@ -117,8 +130,11 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
117
130
  - **Median equivalence.** The order-statistic interval of the median must be in the band, made 5 % wider.
118
131
  - **Extension.** A median across the edge of the band extends the cell from 11 runs to 22, then 44. Then the verdict is inconclusive.
119
132
  - **Recovery.** The recovery after a stall gets the same test as a detection, by its band. A detection is then a finding.
120
- - **Measurements.** A cell whose outcome is a distribution (`detects: null`) has no band. Its decision is `measured`, and the verdict gives the median and the slowest reply time. A measurement-only cell that names a reference (`from`) gives the count of each outcome. It also gives the median and the range of the time from the reference. The time ends at the end of the connection, or at the last data.
133
+ - **Measurements.** A cell whose outcome is a distribution (`detects: null`) has no band. Its decision is `measured`, and the verdict gives the median, the 99th percentile and the slowest reply time. It adds the sum of what reordering did, when anything came out of order, twice or SACKed.
134
+ - **A reference.** A measurement-only cell that names a reference (`from`) gives the count of each outcome. It also gives the median and the range of the time from the reference. The time ends at the end of the connection, or at the last data.
135
+ - **A calibration.** A calibration cell gives the spread of the time of its proxy in each direction (`proxy`). The spread is the count, the median, the 99th and 99.9th percentiles by nearest rank, and the maximum.
121
136
  - **A reply.** A reply in its band (`replies`) gets the same test as a detection.
137
+ - **The time of a proxy.** A band through a proxy includes an upper quantile of the time of the proxy for each crossing (`through`). The author of the cell adds it from a calibration cell, and the verdict tests the band as it is.
122
138
 
123
139
  The distribution test needs a simulator, which does not exist at this time.
124
140
 
@@ -148,6 +164,6 @@ A run needs Docker, and privileged containers for the prober and the sidecars. T
148
164
 
149
165
  `pnpm --filter @mark1russell7/harness test` starts the tests. They measure test records with the shape of real kernel records (keepalive, and the tail loss probe before the retransmissions of the RTO timer). They also test the containment, the median equivalence, the extension and the silence of the verdict.
150
166
 
151
- The cells themselves need Docker and privileged containers. CI does not start them.
167
+ The cells themselves need Docker and privileged containers. CI does not start them. `HARNESS_LAB=1` starts the lab tests on Docker: a client image, an agent, and a reset and netem at a router.
152
168
 
153
169
  `pnpm --filter @mark1russell7/harness build` writes the package for npm into `build/`.
package/build/index.d.ts CHANGED
@@ -29,8 +29,14 @@ import "testcontainers";
29
29
  * - `toxic` adds a toxic of Toxiproxy (`Cell.toxic`) through its HTTP API.
30
30
  * - `server-close` makes the server close the connection of its peer, with the command of the
31
31
  * target (`Target.close`): `CLIENT KILL` in Redis.
32
+ * - `none` injects nothing. A calibration cell measures the path itself: for example the time of a
33
+ * proxy between its two sockets (`Cell.calibrates`).
34
+ *
35
+ * A fault at a router of the topology (`Cell.faultAt`) acts on what the router forwards toward
36
+ * one side. It is a black hole, a reset or an ICMP unreachable. It can also be a simplex drop, an
37
+ * MTU drop or netem.
32
38
  */
33
- type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close";
39
+ type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "none";
34
40
  /**
35
41
  * A middlebox form tells what a router does with a flow that it forgot, when a late packet comes:
36
42
  * - `reset-both` sends a reset to the two ends when it forgets the flow.
@@ -58,6 +64,11 @@ interface RouterHop {
58
64
  * interval is 0.1 s. The DESTROY event of an expired flow waits for that listing.
59
65
  */
60
66
  readonly pump?: Quantity;
67
+ /**
68
+ * These are the sysctls of the namespace of the router, which the router sets itself: it is
69
+ * privileged. An example is `net.ipv4.icmp_ratelimit` for the ICMP errors of a fault at the router.
70
+ */
71
+ readonly sysctls?: Readonly<Record<string, string>>;
61
72
  }
62
73
  /**
63
74
  * A proxy is a hop that ends each TCP connection from the side of the client and opens its own to
@@ -143,6 +154,28 @@ interface Replies {
143
154
  readonly low: Quantity;
144
155
  readonly high: Quantity;
145
156
  }
157
+ /**
158
+ * An RTT range is the range of the first round trip of the observer that a band assumes. The
159
+ * models read a round trip, for example 1 ms. The band at that value holds within the noise for
160
+ * each round trip in the range. A run whose first round trip is above the range is void.
161
+ */
162
+ interface RttRange {
163
+ readonly low: Quantity;
164
+ readonly high: Quantity;
165
+ }
166
+ /**
167
+ * A proxy time is the time of one proxy in one direction that a band includes. It is an upper
168
+ * quantile of the times that a calibration cell of the proxy measured. Each time goes from an
169
+ * arrival on one socket of the proxy to its write on the other. `up` goes toward the target, and
170
+ * `down` toward the client.
171
+ */
172
+ interface ProxyTime {
173
+ readonly hop: string;
174
+ readonly direction: "up" | "down";
175
+ /** This fraction is the quantile of the calibration, for example 0.99. */
176
+ readonly quantile: number;
177
+ readonly time: Quantity;
178
+ }
146
179
  /**
147
180
  * A prediction is what a cell predicts. Either a detection occurs in an interval [T_lo, T_hi]
148
181
  * after the reference event, or nothing detects the fault in a window. Nothing detects a stall
@@ -150,6 +183,9 @@ interface Replies {
150
183
  * tells when. A silent prediction can also tell when the reply to the request comes back
151
184
  * (`replies`).
152
185
  *
186
+ * A band can name the first round trip that it assumes (`rtt`), and the proxy times that it
187
+ * includes (`through`).
188
+ *
153
189
  * A cell whose outcome is a distribution, for example under netem loss, has no band. Its
154
190
  * prediction is `detects: null` with the reason, and its runs are measurements only. A
155
191
  * measurement-only cell that names a reference (`from`) also records the time from it to the end
@@ -164,13 +200,17 @@ type Prediction = {
164
200
  readonly from: Reference;
165
201
  /** This time is the closed form, less the most that the timer can fire early. */
166
202
  readonly low: Quantity;
167
- /** This time is the closed form, plus the most that the timer can fire late. */
203
+ /** This time is the closed form, plus the most that the timer can fire late, plus each proxy time. */
168
204
  readonly high: Quantity;
205
+ readonly rtt?: RttRange;
206
+ readonly through?: readonly ProxyTime[];
169
207
  } | {
170
208
  readonly detects: false;
171
209
  readonly window: Quantity;
172
210
  readonly recovers?: Recovers;
173
211
  readonly replies?: Replies;
212
+ readonly rtt?: RttRange;
213
+ readonly through?: readonly ProxyTime[];
174
214
  } | {
175
215
  readonly detects: null;
176
216
  readonly window: Quantity;
@@ -274,7 +314,10 @@ interface Cell {
274
314
  readonly drops?: "to-server" | "to-client";
275
315
  /** This length is the longest segment, in bytes, that an `mtu-blackhole` fault lets through to the observer. */
276
316
  readonly mtu?: number;
277
- /** This specification is the tc netem of a `netem` fault, on the egress of the target: for example "loss 5%". */
317
+ /**
318
+ * This specification is the tc netem of a `netem` fault, for example "loss 5%". It is on the
319
+ * egress of the target, or at a hop (`faultAt`) on the egress of its device toward one side.
320
+ */
278
321
  readonly netem?: string;
279
322
  /**
280
323
  * This delay goes on the egress of the target before the client connects, with tc netem. Thus
@@ -292,7 +335,7 @@ interface Cell {
292
335
  readonly targetSysctls?: Readonly<Record<string, string>>;
293
336
  /** This topology puts hops between the client and the target. Without it, the client and the target are a direct pair. */
294
337
  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. */
338
+ /** This place is where the fault goes, when it is not the peer of the observer: for example one side of a hop. */
296
339
  readonly faultAt?: FaultPlace;
297
340
  /** This time is how long an in-flight client stays idle after the fault holds, before it sends its request. */
298
341
  readonly idleBefore?: Quantity;
@@ -300,6 +343,12 @@ interface Cell {
300
343
  readonly toxic?: Toxic;
301
344
  /** This image replaces the TCP client of the harness for each run. */
302
345
  readonly client?: ClientImage;
346
+ /**
347
+ * This name is the proxy hop whose own time a calibration cell measures. The time of a request
348
+ * (`up`) starts at its arrival on the socket of the proxy toward the client. It stops at the
349
+ * write on the socket toward the target. The time of its reply (`down`) goes the other way.
350
+ */
351
+ readonly calibrates?: string;
303
352
  /** This count is the number of runs of the cell (11), across new environments (two at least). */
304
353
  readonly runs: number;
305
354
  readonly environments: number;
@@ -313,7 +362,8 @@ interface Cell {
313
362
  /**
314
363
  * A fault place is the namespace that gets the rules of a fault. "target" and "client" are the two
315
364
  * 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.
365
+ * the client, or the next hop toward the target. The rules of a proxy act on its sockets. The
366
+ * rules of a router act on what it forwards, by its device toward that side: the FORWARD chain.
317
367
  */
318
368
  type FaultPlace = "target" | "client" | {
319
369
  readonly hop: string;
@@ -435,11 +485,57 @@ interface Measured {
435
485
  * last segment that the client received before the fault. It is null when no router forgot the flow.
436
486
  */
437
487
  readonly forgot?: Quantity | null;
488
+ /**
489
+ * This time is the first round trip of the socket of the observer. It is the smoothed round trip
490
+ * in its first record, which is the sample of the handshake. It is null when no record has it.
491
+ */
492
+ readonly rtt?: Quantity | null;
493
+ /** This record tells what reordering did in a run of a measurement-only cell, from the records of its two ends. */
494
+ readonly reordering?: Reordering;
495
+ /** These are the times of the proxy of a calibration cell, one for each request and its reply. */
496
+ readonly crossings?: Crossings;
438
497
  /** This text is the reason that the run cannot count, or null. A void run is not a pass and not a finding. */
439
498
  readonly void: string | null;
440
499
  /** This text tells what in the run disagrees with the prediction, other than its timing, or null. */
441
500
  readonly finding: string | null;
442
501
  }
502
+ /**
503
+ * A reordering record is what reordering, duplicates and losses did to the data of a run. The
504
+ * receiver is the client, and the sender is the socket of its peer.
505
+ * - `outOfOrder` counts the arrivals at the client beyond a hole, and `duplicates` counts the
506
+ * arrivals whose data the client had. The prober reads the two from each arrival, against `rcv_nxt`.
507
+ * - An arrival can hold more than one segment. The kernel joins the segments that wait in the
508
+ * backlog of a socket while its owner holds it.
509
+ * - `ooo` is the count of the kernel of the client in segments (`rcv_ooopack`).
510
+ * - `sacked` and `lost` are the most segments that the sender held as SACKed and as lost.
511
+ * - `dsacks` is the DSACK blocks that the sender received, and `fast` its fast retransmissions.
512
+ * - `degree` is the highest reordering degree of the sender (`tp->reordering`), and `seen` the
513
+ * reorderings that it detected (`reord_seen`).
514
+ *
515
+ * The counts of the kernel are null for a record of the prober before version 0.2.
516
+ */
517
+ interface Reordering {
518
+ readonly outOfOrder: number;
519
+ readonly duplicates: number;
520
+ readonly ooo: number | null;
521
+ readonly sacked: number | null;
522
+ readonly lost: number | null;
523
+ readonly dsacks: number | null;
524
+ readonly fast: number;
525
+ readonly degree: number | null;
526
+ readonly seen: number | null;
527
+ }
528
+ /**
529
+ * A crossings record is the time of a proxy for each request (`up`) and for each reply (`down`).
530
+ * The time of a request starts at its arrival on the socket toward the client. It stops at the
531
+ * write on the socket toward the target. The time of a reply goes the other way. A write is a
532
+ * `tx` record of the prober.
533
+ */
534
+ interface Crossings {
535
+ readonly hop: string;
536
+ readonly up: readonly Quantity[];
537
+ readonly down: readonly Quantity[];
538
+ }
443
539
  /** A socket place is a socket of a run: its namespace and its cookie. */
444
540
  interface SocketPlace {
445
541
  readonly netns: number;
@@ -452,6 +548,16 @@ interface SocketPlace {
452
548
  * - a socket of a proxy of the topology, toward the client or toward the target.
453
549
  */
454
550
  export declare const socketOf: (cell: Cell, run: Run) => SocketPlace | undefined;
551
+ /** This function gives what reordering did in a run, from the records of the receiver and of the sender. */
552
+ export declare const reorderingOf: (receiver: readonly KernelRecord[], sender: readonly KernelRecord[]) => Reordering;
553
+ /**
554
+ * This function gives the times of a proxy in a run, for each request after the first. The time
555
+ * of a request starts at its arrival on the socket of the proxy toward the client. It stops at the
556
+ * next write on the socket toward the target. The time of its reply starts at its arrival on the
557
+ * socket toward the target, and it stops at the next write toward the client. A request without
558
+ * each record gives no time.
559
+ */
560
+ export declare const crossingsOf: (run: Run, hop: string, toClient: SocketPlace | undefined, toTarget: SocketPlace | undefined) => Crossings;
455
561
  /**
456
562
  * This function reads one run against its cell, from the records of the kernel first.
457
563
  * - The socket of the run is the socket of its observer. It is the socket that the client connected
@@ -486,6 +592,14 @@ interface Interval {
486
592
  readonly low: Quantity;
487
593
  readonly high: Quantity;
488
594
  }
595
+ /** A spread is the distribution of a set of times: its count, its median and its upper quantiles by nearest rank. */
596
+ interface Spread {
597
+ readonly count: number;
598
+ readonly median: Quantity;
599
+ readonly p99: Quantity;
600
+ readonly p999: Quantity;
601
+ readonly max: Quantity;
602
+ }
489
603
  /** A verdict is the judgement of a cell on its runs. */
490
604
  interface Verdict {
491
605
  readonly decision: Decision;
@@ -501,8 +615,20 @@ interface Verdict {
501
615
  readonly median: Quantity | null;
502
616
  /** This interval is the order-statistic interval of the median at 95 %, or the whole range for fewer than six runs. */
503
617
  readonly medianInterval: Interval | null;
618
+ /** This record is the sum of what reordering did in each counted run of a measurement-only cell. */
619
+ readonly reordering?: Reordering;
620
+ /** This record is the time of the proxy of a calibration cell, in each direction. */
621
+ readonly proxy?: {
622
+ readonly hop: string;
623
+ readonly up: Spread | null;
624
+ readonly down: Spread | null;
625
+ };
504
626
  readonly why: string;
505
627
  }
628
+ /** This function gives the value at quantile p of sorted values, by nearest rank: x_(⌈p·n⌉). */
629
+ export declare const quantileOf: (sorted: readonly number[], p: number) => number;
630
+ /** This function gives the spread of a set of times, or null for none. */
631
+ export declare const spreadOf: (times: readonly Quantity[]) => Spread | null;
506
632
  /**
507
633
  * This function gives the distribution-free interval of a median: [x_(k), x_(n+1−k)], for the
508
634
  * largest k whose two tails each hold 2.5 % or less. For eleven runs, it gives [x_(2), x_(10)],
@@ -700,17 +826,18 @@ interface Lab {
700
826
  lift(kind: NetworkFault, at?: FaultPlace, options?: FaultOptions): Promise<void>;
701
827
  /**
702
828
  * 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.
829
+ * or removes it with null. At a hop, it is the egress of the device of the hop toward one side.
830
+ * The stamps of the injector bracket it.
704
831
  */
705
- netem(spec: string | null, at?: FaultSide): Promise<{
832
+ netem(spec: string | null, at?: FaultPlace): Promise<{
706
833
  t0: number;
707
834
  t1: number;
708
835
  }>;
709
836
  /**
710
837
  * 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.
838
+ * act on segments, not on GSO super-packets of up to 64 KB. At a hop, it sets the two devices of the hop.
712
839
  */
713
- offload(on: boolean, at?: FaultSide): Promise<void>;
840
+ offload(on: boolean, at?: FaultPlace): Promise<void>;
714
841
  /** This method stops the server process in the target with SIGKILL, with the command of the target. */
715
842
  kill(): Promise<void>;
716
843
  /**
@@ -768,6 +895,8 @@ export declare const routesOf: (hops: readonly Pick<Hop, "kind">[]) => Array<{
768
895
  * is not a router. Element 0 is the client, element k is hop k - 1, and the last element is the target.
769
896
  */
770
897
  export declare const endpointAfter: (hops: readonly Pick<Hop, "kind">[], element: number) => number;
898
+ /** This function gives the device of a route from its line in `ip -o route get`: "10.0.0.2 dev eth0 src …". */
899
+ export declare const deviceOf: (route: string) => string;
771
900
  /**
772
901
  * This function opens one environment:
773
902
  * - a chain of new networks, and the stock target on the last one. A direct pair has one network.
@@ -828,5 +957,5 @@ export declare const spread: (runs: number, environments: number) => number[];
828
957
  */
829
958
  export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
830
959
  //#endregion
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 };
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 };
832
961
  //# sourceMappingURL=index.d.ts.map