@mark1russell7/harness 0.14.4 → 0.15.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
@@ -206,6 +206,7 @@ From 0.11.20, the harness builds each image with the CLI of Docker (`docker buil
206
206
  - the kernel program of the prober, compiled against the BTF of the kernel that builds it, and its Rust collector.
207
207
  - `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.
208
208
  - 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.
209
+ - `load`, the open-loop driver of an HTTP/1.1 server (SYS-93). Its plan (`systems-load/1`) is a list of streams, each with a key, a schedule (`rigor-schedule/1`) and its requests. Each stream has its own connection, thus a slow reply delays only its own stream. A send does not wait for a reply. A response is whole after its `Content-Length` or its last chunk. The `sent` and `replied` lines name the stream (`k`) and the arrival (`i`), and a `replied` line names the status.
209
210
  - `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.
210
211
  - `inject` also sends a signal to a process by its name (`signal`), and one command to a server of the Redis protocol (`resp`). Stamps bracket each.
211
212
  - **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. The same image holds the client of a group in Sentinel mode (`client-sentinel.mjs`) and the observer of its Sentinels (`observer.mjs`).
@@ -1,4 +1,5 @@
1
1
  import { Quantity } from "@mark1russell7/quantity";
2
+ import { Profile, Schedule, ScheduleOptions } from "@mark1russell7/rigor-chaos";
2
3
  import { KernelRecord } from "@mark1russell7/prober";
3
4
  //#region src/cell.d.ts
4
5
  /**
@@ -360,6 +361,57 @@ interface Kafka {
360
361
  readonly minInsync: number;
361
362
  /** This count is the number of writers of the client, each with one write at a time. */
362
363
  readonly writers: number;
364
+ /** This record makes each run a run of two members of a consumer group, with one member stopped. Without it, the client is a producer. */
365
+ readonly pause?: KafkaPause;
366
+ }
367
+ /**
368
+ * A Kafka pause is one run of two members of a consumer group, on a topic with two partitions. The
369
+ * fault stops the process of the first member while it holds a batch, and the hold of the cell ends
370
+ * that stop. Each setting of a member comes from `Cell.driverOptions`.
371
+ */
372
+ interface KafkaPause {
373
+ /** This name is the protocol of the group: the broker keeps the session under `consumer`, and the client sets it under `classic`. */
374
+ readonly protocol: "consumer" | "classic";
375
+ /** This time is how long the first member holds each batch before it commits. */
376
+ readonly process: Quantity;
377
+ /** This choice is what the first member does after the stop. It commits at once, or it polls until it has a new epoch and then commits. */
378
+ readonly afterPause: "commit" | "rejoin";
379
+ /** This count is the number of records in each partition before the members start. */
380
+ readonly fill: number;
381
+ /** This time is from the start of the processing of a batch to the stop. */
382
+ readonly stopAfter: Quantity;
383
+ }
384
+ /** A pause band is the band of one time of a paused consumer, from the stop. */
385
+ interface PauseBand {
386
+ readonly low: Quantity;
387
+ readonly high: Quantity;
388
+ }
389
+ /**
390
+ * A pause prediction is the law of a paused consumer. A null fence tells that the stop is too short.
391
+ * The group then does not fence the member, and the other member does not get its partition.
392
+ */
393
+ interface PausePrediction {
394
+ readonly fence: PauseBand | null;
395
+ /** This band is the first report of the partition by the other member. Null tells that this cell has no band for it. */
396
+ readonly takeover: PauseBand | null;
397
+ /**
398
+ * This choice is the result of the commit after the stop:
399
+ * - `fails`: a commit before any poll fails with `CommitFailedException`.
400
+ * - `acknowledged`: the commit succeeds, and the epoch of the member does not change.
401
+ * - `rejoin`: the member gets a new epoch, the same commit succeeds, and the committed offset moves back.
402
+ */
403
+ readonly thaw: "fails" | "acknowledged" | "rejoin";
404
+ /** This time bounds the new epoch of `rejoin`, from the end of the stop. */
405
+ readonly rejoinWithin?: Quantity;
406
+ }
407
+ /** A run pause is what a run of a paused consumer records beyond the lines of the stopped member. */
408
+ interface RunPause {
409
+ /** These are the lines of the other member. */
410
+ readonly other: readonly ProbeEvent[];
411
+ /** These are the log lines of the brokers that tell that a member left this group. */
412
+ readonly fences: readonly string[];
413
+ /** This name is the group of the run. */
414
+ readonly group: string;
363
415
  }
364
416
  /**
365
417
  * A cluster stage is one step of the timeline of a cluster after a fault. A line of the log of a
@@ -789,6 +841,15 @@ type Prediction = {
789
841
  * when a held port comes back. A value outside it is a finding of its run.
790
842
  */
791
843
  readonly ports?: PortsPrediction;
844
+ /**
845
+ * This record is the law of a conntrack table: its flows, a connect that finds no entry, and the
846
+ * return of a slot. A value outside it is a finding of its run.
847
+ */
848
+ readonly conntrack?: ConntrackPrediction;
849
+ /** This record is the law of a paused consumer (`Kafka.pause`). A run outside it is a finding of its run. */
850
+ readonly pause?: PausePrediction;
851
+ /** These are the limits of an open-loop cell (`Cell.probe.plan`). Without them, its load is a measurement. */
852
+ readonly load?: LoadPrediction;
792
853
  };
793
854
  /** A queue band is a time from one line of a queue cell to another line, with its two bounds. */
794
855
  interface QueueBand {
@@ -888,6 +949,90 @@ interface PortsPrediction {
888
949
  /** This count is the segments that the sockets of the client send again. */
889
950
  readonly retransmissions?: number;
890
951
  }
952
+ /**
953
+ * This prediction is the law of a conntrack table. It reads the lines of the ctfull client. The client
954
+ * holds flows on its loopback until the count of its namespace reads the limit, then it does its plan.
955
+ */
956
+ interface ConntrackPrediction {
957
+ /** This count is the limit of the table: the flows that the client holds, and the count that it reads then. */
958
+ readonly max: number;
959
+ /** This count is what the namespace of the target reads while the client holds its flows. */
960
+ readonly neighbor?: number;
961
+ /** This record is the law of a connect that finds no entry: its end, and the rise of the two counters of the kernel. */
962
+ readonly refused?: {
963
+ readonly within: QueueBand;
964
+ readonly errno: string;
965
+ readonly drops: number;
966
+ readonly retransFail: number;
967
+ };
968
+ /** This band is the time from the `closing` line to the first fall of the count, and to its zero. */
969
+ readonly drained?: QueueBand;
970
+ /**
971
+ * This record is the law of a scan under pressure. It has the lowest count, the count after the
972
+ * connect, and the times of that connect. With `highest`, the lowest count is a band from `lowest` to
973
+ * `highest`. Then the count after the connect is one more than the lowest count of the run.
974
+ */
975
+ readonly pressure?: {
976
+ readonly lowest: number;
977
+ readonly highest?: number;
978
+ readonly after: number;
979
+ readonly points: readonly QueueBand[];
980
+ };
981
+ }
982
+ /**
983
+ * This setting is one sysctl of the namespace of the host, for the time of an environment. The harness
984
+ * reads the value before it, reads the value that it set, and restores the first value at the end.
985
+ */
986
+ interface HostSysctl {
987
+ readonly name: string;
988
+ readonly value: string;
989
+ /** The harness does not set the value when this sysctl of the host reads above `atMost`. The cell is then void. */
990
+ readonly guard?: {
991
+ readonly name: string;
992
+ readonly atMost: number;
993
+ };
994
+ }
995
+ /** A load plan is the schedule of an open-loop client as data: the rate over time, and how the arrivals fall. */
996
+ interface LoadPlan {
997
+ readonly profile: Profile;
998
+ readonly options: ScheduleOptions;
999
+ }
1000
+ /**
1001
+ * A load stream is one connection of the client `load`: its key, its schedule and its requests.
1002
+ * Arrival i sends request i modulo the count of requests. Each stream has its own connection, thus
1003
+ * a slow stream does not delay the arrivals of another stream.
1004
+ */
1005
+ interface LoadStream {
1006
+ /** This text names the stream in each line of the client (`k`). Two streams of a cell do not have the same key. */
1007
+ readonly key: string;
1008
+ readonly plan: LoadPlan;
1009
+ /** These texts are whole HTTP/1.1 requests, each with its head and its body. */
1010
+ readonly requests: readonly string[];
1011
+ }
1012
+ /** A run stream is the schedule that one stream of the client `load` had in a run. */
1013
+ interface RunStream {
1014
+ readonly key: string;
1015
+ readonly schedule: Schedule;
1016
+ }
1017
+ /** A load band is a limit on one quantile of the latency of an open-loop cell, over the arrivals of its counted runs. */
1018
+ interface LoadBand {
1019
+ /** This number is the quantile, from 0 to 1. */
1020
+ readonly quantile: number;
1021
+ readonly low?: Quantity;
1022
+ readonly high: Quantity;
1023
+ }
1024
+ /** A load prediction tells how close to its schedule a client stays, and where the latency lands. */
1025
+ interface LoadPrediction {
1026
+ /** This time is the largest lag of a send behind its planned time. A later send is a finding of its run. */
1027
+ readonly lag: Quantity;
1028
+ readonly bands: readonly LoadBand[];
1029
+ /**
1030
+ * This share is the limit on the failed arrivals of the counted runs, from 0 to 1. An arrival fails
1031
+ * when the client did not send it, when it has no reply, or when its reply has the status of a
1032
+ * failure. The verdict reads the Wilson interval at 0.95, thus a limit of 0.01 needs 381 arrivals.
1033
+ */
1034
+ readonly failures?: number;
1035
+ }
891
1036
  /** A target is the server that a cell connects to: a stock image. */
892
1037
  interface Target {
893
1038
  /** This image is a stock image, pinned by digest. */
@@ -1003,7 +1148,7 @@ interface Subject {
1003
1148
  * (`http-agent`) and an HTTP/1.1 `Client` of undici (`undici`). The MongoDB driver is `mongodb`, node-postgres is `pg`, and HikariCP over pgjdbc is `hikari`.
1004
1149
  * The client of amqplib is `amqp`, and the client of grpc-js is `grpc`. The WebSocket client of `@procedures/client` is a program of its subject (`ClientImage`).
1005
1150
  */
1006
- type Client = "tcp" | "ioredis" | "go-http" | "http-agent" | "undici" | "mongodb" | "pg" | "hikari" | "amqp" | "grpc";
1151
+ type Client = "tcp" | "ioredis" | "go-http" | "http-agent" | "undici" | "mongodb" | "pg" | "hikari" | "amqp" | "grpc" | "load";
1007
1152
  /**
1008
1153
  * A WebSocket probe is the workload and the settings of the WebSocket client of
1009
1154
  * `@procedures/client`, the `client` program of its subject. The cell gives each setting, thus the client takes
@@ -1071,7 +1216,15 @@ interface ClientImage {
1071
1216
  /** This image is a stock image by its name, pinned by digest, or the source of the image of a subject. */
1072
1217
  readonly image: string | ImageSource;
1073
1218
  readonly command: readonly string[];
1219
+ /**
1220
+ * These capabilities are added to the default set of the container of the client. A read of the
1221
+ * statistics of conntrack over netlink needs NET_ADMIN in the namespace of the client.
1222
+ */
1223
+ readonly capabilities?: readonly ClientCapability[] | undefined;
1074
1224
  }
1225
+ /** These are the capabilities that a client image can get. */
1226
+ declare const CLIENT_CAPABILITIES: readonly ["NET_ADMIN"];
1227
+ type ClientCapability = (typeof CLIENT_CAPABILITIES)[number];
1075
1228
  /**
1076
1229
  * An observer is the side whose detection a cell measures. The client is one observer. The
1077
1230
  * server is another observer. It is the hold of the target on the connection of the client, at
@@ -1096,8 +1249,13 @@ interface Cell {
1096
1249
  * This mode tells if the client is idle after its first reply, or sends a request into the
1097
1250
  * fault. In a stream, it sends `count` requests into it, each `interval` after the reply before.
1098
1251
  * An HTTP client in a `gap` sends its second request a gap after its first reply, by itself.
1252
+ * On a `schedule`, it sends each request at its time in `plan`, and a send does not wait for a reply.
1099
1253
  */
1100
- readonly mode: "idle" | "inflight" | "stream" | "gap";
1254
+ readonly mode: "idle" | "inflight" | "stream" | "gap" | "schedule";
1255
+ /** This plan is the schedule of the requests in the mode `schedule`. */
1256
+ readonly plan?: LoadPlan;
1257
+ /** These streams are the plan of the client `load` in the mode `schedule`, in place of `plan`. */
1258
+ readonly streams?: readonly LoadStream[];
1101
1259
  /** This count is the number of requests of a stream. */
1102
1260
  readonly count?: number;
1103
1261
  /** This time is the pause after each reply of a stream, before the next request. */
@@ -1194,6 +1352,14 @@ interface Cell {
1194
1352
  readonly latency?: Quantity;
1195
1353
  /** These commands run in the target before a client connects, each as argv: for example, a large value to read. */
1196
1354
  readonly targetSetup?: readonly (readonly string[])[];
1355
+ /** These commands run in the namespaces of the client and the target, each as argv, after the first connection of the environment. */
1356
+ readonly namespaceSetup?: readonly (readonly string[])[];
1357
+ /**
1358
+ * These are sysctls of the host, which a cell needs because only the host can write them. The value
1359
+ * applies to each container of the host, thus a shard runs them on its own runner. A host that does
1360
+ * not have one makes the cell void (rule 8 of the environments).
1361
+ */
1362
+ readonly hostSysctls?: readonly HostSysctl[];
1197
1363
  readonly prediction: Prediction;
1198
1364
  /** This side is the side whose detection the cell measures. Without it, the side is the client. */
1199
1365
  readonly observer?: Observer;
@@ -1400,8 +1566,16 @@ interface Run {
1400
1566
  readonly group?: RunGroup;
1401
1567
  /** These are the lines of a target that writes them (`Target.lines`), from the start to the end of the run. */
1402
1568
  readonly server?: readonly ProbeEvent[];
1569
+ /** This text tells what the environment of the run could not do for its cell. The run is void. */
1570
+ readonly missing?: string;
1571
+ /** This record is the other member and the broker logs of a run of a paused consumer. */
1572
+ readonly pause?: RunPause;
1403
1573
  /** This record is the fabric of a cell with `Topology.fabric`. */
1404
1574
  readonly fabric?: RunFabric;
1575
+ /** This schedule is the one that the client of an open-loop cell had: each planned time stays with its record. */
1576
+ readonly schedule?: Schedule;
1577
+ /** These schedules are the ones that the streams of the client `load` had. */
1578
+ readonly streams?: readonly RunStream[];
1405
1579
  }
1406
1580
  /** A run route is one change of a route at a leaf of a fabric, on the monotonic clock. */
1407
1581
  interface RunRoute {
@@ -1513,5 +1687,5 @@ interface RunTopology {
1513
1687
  }>>;
1514
1688
  }
1515
1689
  //#endregion
1516
- export { Replies as $, LineCount as A, Prediction as B, HopSignal as C, latestEndOf as Ct, Jitter as D, ImageSource as E, Observer as F, QueueConnection as G, ProxyHop as H, Outcomes as I, Reconnects as J, QueueEnd as K, Pair as L, MiddleboxEvent as M, MiddleboxExpectation as N, Kafka as O, MiddleboxForm as P, ReplicaSetStage as Q, PinnedImage as R, HopPlace as S, isHopPlace as St, HttpProbe as T, ProxyTime as U, ProbeEvent as V, QueueBand as W, Reference as X, Recovers as Y, ReplicaSet as Z, GroupPlace as _, Subject as _t, Cluster as a, RunDrill as at, Hop as b, Toxic as bt, CountBand as c, RunPart as ct, DriverStep as d, SentinelEvent as dt, RequestPrediction as et, Fabric as f, SentinelStage as ft, Group as g, StageBand as gt, FaultPlace as h, Stage as ht, ClientImage as i, RunBackup as it, LineMatch as j, KernelRecord as k, Drill as l, RunRoute as lt, Fault as m, ShippedImage as mt, Cell as n, RttRange as nt, ClusterClientAt as o, RunFabric as ot, FabricManifest as p, Shipped as pt, QueuePrediction as q, Client as r, Run as rt, ClusterStage as s, RunGroup as st, BuildImage as t, RouterHop as tt, DriverBand as u, RunTopology as ut, GroupPrediction as v, Target as vt, HopWrite as w, HopEvent as x, WsProbe as xt, GroupRole as y, Topology as yt, PortsPrediction as z };
1517
- //# sourceMappingURL=cell-DZm8zXN4.d.ts.map
1690
+ export { ProxyHop as $, ImageSource as A, Subject as At, LoadStream as B, Hop as C, RunTopology as Ct, HopWrite as D, ShippedImage as Dt, HopSignal as E, Shipped as Et, LineCount as F, isHopPlace as Ft, Outcomes as G, MiddleboxExpectation as H, LineMatch as I, latestEndOf as It, PausePrediction as J, Pair as K, LoadBand as L, Kafka as M, Topology as Mt, KafkaPause as N, Toxic as Nt, HostSysctl as O, Stage as Ot, KernelRecord as P, WsProbe as Pt, ProbeEvent as Q, LoadPlan as R, GroupRole as S, RunStream as St, HopPlace as T, SentinelStage as Tt, MiddleboxForm as U, MiddleboxEvent as V, Observer as W, PortsPrediction as X, PinnedImage as Y, Prediction as Z, Fault as _, RunFabric as _t, ClientCapability as a, Reconnects as at, GroupPlace as b, RunPause as bt, ClusterClientAt as c, ReplicaSet as ct, CountBand as d, RequestPrediction as dt, ProxyTime as et, Drill as f, RouterHop as ft, FabricManifest as g, RunDrill as gt, Fabric as h, RunBackup as ht, Client as i, QueuePrediction as it, Jitter as j, Target as jt, HttpProbe as k, StageBand as kt, ClusterStage as l, ReplicaSetStage as lt, DriverStep as m, Run as mt, CLIENT_CAPABILITIES as n, QueueConnection as nt, ClientImage as o, Recovers as ot, DriverBand as p, RttRange as pt, PauseBand as q, Cell as r, QueueEnd as rt, Cluster as s, Reference as st, BuildImage as t, QueueBand as tt, ConntrackPrediction as u, Replies as ut, FaultPlace as v, RunGroup as vt, HopEvent as w, SentinelEvent as wt, GroupPrediction as x, RunRoute as xt, Group as y, RunPart as yt, LoadPrediction as z };
1691
+ //# sourceMappingURL=cell-biffSiEJ.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { valueIn } from "@mark1russell7/quantity";
1
2
  import { createHash } from "node:crypto";
2
3
  import { execFile } from "node:child_process";
3
4
  import { tmpdir } from "node:os";
@@ -6,6 +7,22 @@ import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpa
6
7
  import { createRequire } from "node:module";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { isDeepStrictEqual } from "node:util";
10
+ //#region src/cell.ts
11
+ /** These are the capabilities that a client image can get. */
12
+ const CLIENT_CAPABILITIES = ["NET_ADMIN"];
13
+ /** This function tells if a fault place is a hop of a topology. */
14
+ const isHopPlace = (place) => typeof place === "object" && "hop" in place;
15
+ /**
16
+ * This function tells if the request of a client goes before the fault of its cell. That is so for a
17
+ * hop event that comes while the request is in flight (`HopEvent.inFlight`). Then a request after the fault is void.
18
+ */
19
+ const requestFirst = (cell) => cell.fault === "hop-event" && cell.event?.inFlight === true;
20
+ /**
21
+ * This function gives the latest end of a band in seconds, from the reply of the client. It is
22
+ * the high bound, after the latest reference of the band (`startsBy`).
23
+ */
24
+ const latestEndOf = (prediction) => valueIn(prediction.high, "s") + (prediction.startsBy === void 0 ? 0 : valueIn(prediction.startsBy, "s"));
25
+ //#endregion
9
26
  //#region src/locks.ts
10
27
  /**
11
28
  * These are the names of the lockfiles that the harness records. A `go.mod` is the lockfile of a Go
@@ -292,6 +309,10 @@ const SHIPPED_CLIENTS = {
292
309
  grpc: {
293
310
  image: shipped("grpc"),
294
311
  command: ["node", "/opt/harness/grpc/client-grpc.mjs"]
312
+ },
313
+ load: {
314
+ image: shipped("harness"),
315
+ command: ["/opt/harness/load"]
295
316
  }
296
317
  };
297
318
  /** These are the names of the clients that the harness ships. */
@@ -641,6 +662,6 @@ const targetSourceOf = (target) => target.subject ?? target.image;
641
662
  /** This function gives the image that a target operates: the image of its subject, or its stock image. */
642
663
  const targetImageOf = (target) => imageOf(targetSourceOf(target));
643
664
  //#endregion
644
- export { unlockedManifests as A, programOf as C, subjectProblems as D, subjectClient as E, jarVersions as F, locksOf as I, npmVersions as L, LOCK_NAMES as M, cargoVersions as N, targetImageOf as O, goVersions as P, versionsOf as R, planOf as S, shippedSource as T, kafkaClientImage as _, SHIPPED_NAMES as a, nodeHttpImage as b, buildRecordOf as c, harnessImage as d, harnessVersion as f, inContext as g, imageProblems as h, SHIPPED_CLIENTS as i, unpinnedBases as j, targetSourceOf as k, goHttpImage as l, imageOf as m, PINNED as n, amqpClientImage as o, hikariClientImage as p, REPOSITORY as r, basesOf as s, CLIENT_NAMES as t, grpcClientImage as u, mongoClientImage as v, routerImage as w, pgClientImage as x, nodeClientImage as y };
665
+ export { unlockedManifests as A, isHopPlace as B, programOf as C, subjectProblems as D, subjectClient as E, jarVersions as F, requestFirst as H, locksOf as I, npmVersions as L, LOCK_NAMES as M, cargoVersions as N, targetImageOf as O, goVersions as P, versionsOf as R, planOf as S, shippedSource as T, latestEndOf as V, kafkaClientImage as _, SHIPPED_NAMES as a, nodeHttpImage as b, buildRecordOf as c, harnessImage as d, harnessVersion as f, inContext as g, imageProblems as h, SHIPPED_CLIENTS as i, unpinnedBases as j, targetSourceOf as k, goHttpImage as l, imageOf as m, PINNED as n, amqpClientImage as o, hikariClientImage as p, REPOSITORY as r, basesOf as s, CLIENT_NAMES as t, grpcClientImage as u, mongoClientImage as v, routerImage as w, pgClientImage as x, nodeClientImage as y, CLIENT_CAPABILITIES as z };
645
666
 
646
- //# sourceMappingURL=image-EZ8ioZ3L.js.map
667
+ //# sourceMappingURL=image-BxVTFZ1o.js.map