@peerbit/test-utils 2.3.18 → 2.3.19-000e3f1
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/dist/benchmark/fanout-peerbit-sim.d.ts +11 -0
- package/dist/benchmark/fanout-peerbit-sim.d.ts.map +1 -0
- package/dist/benchmark/fanout-peerbit-sim.js +745 -0
- package/dist/benchmark/fanout-peerbit-sim.js.map +1 -0
- package/dist/benchmark/index.d.ts +5 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/dist/benchmark/index.js +42 -0
- package/dist/benchmark/index.js.map +1 -0
- package/dist/src/inmemory-libp2p.d.ts +2 -0
- package/dist/src/inmemory-libp2p.d.ts.map +1 -0
- package/dist/src/inmemory-libp2p.js +2 -0
- package/dist/src/inmemory-libp2p.js.map +1 -0
- package/dist/src/session.d.ts +68 -2
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/session.js +497 -3
- package/dist/src/session.js.map +1 -1
- package/package.json +23 -10
- package/src/inmemory-libp2p.ts +1 -0
- package/src/session.ts +653 -11
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FanoutTree sim using full Peerbit clients over an in-memory libp2p shim.
|
|
3
|
+
*
|
|
4
|
+
* This is intended as a “real integration” complement to `fanout-tree-sim`,
|
|
5
|
+
* focusing on large-n local runs without TCP/noise costs.
|
|
6
|
+
*
|
|
7
|
+
* Run:
|
|
8
|
+
* pnpm -C packages/clients/test-utils run bench -- fanout-peerbit-sim --nodes 1000 --degree 6 --messages 300 --msgSize 1024 --msgRate 30 --seed 1
|
|
9
|
+
*/
|
|
10
|
+
import { delay } from "@peerbit/time";
|
|
11
|
+
import { TestSession } from "../src/session.js";
|
|
12
|
+
const mulberry32 = (seed) => {
|
|
13
|
+
let t = seed >>> 0;
|
|
14
|
+
return () => {
|
|
15
|
+
t += 0x6d2b79f5;
|
|
16
|
+
let x = t;
|
|
17
|
+
x = Math.imul(x ^ (x >>> 15), x | 1);
|
|
18
|
+
x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
|
|
19
|
+
return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const parseArgs = (argv) => {
|
|
23
|
+
const get = (key) => {
|
|
24
|
+
const idx = argv.indexOf(key);
|
|
25
|
+
if (idx === -1)
|
|
26
|
+
return undefined;
|
|
27
|
+
return argv[idx + 1];
|
|
28
|
+
};
|
|
29
|
+
const maybeNumber = (key) => {
|
|
30
|
+
const v = get(key);
|
|
31
|
+
if (v == null)
|
|
32
|
+
return undefined;
|
|
33
|
+
const n = Number(v);
|
|
34
|
+
return Number.isFinite(n) ? n : undefined;
|
|
35
|
+
};
|
|
36
|
+
const maybeString = (key) => {
|
|
37
|
+
const v = get(key);
|
|
38
|
+
if (v == null)
|
|
39
|
+
return undefined;
|
|
40
|
+
return String(v);
|
|
41
|
+
};
|
|
42
|
+
const bool = (key, defaultValue) => {
|
|
43
|
+
const v = get(key);
|
|
44
|
+
if (v == null)
|
|
45
|
+
return defaultValue;
|
|
46
|
+
return v !== "0" && v !== "false";
|
|
47
|
+
};
|
|
48
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
49
|
+
console.log([
|
|
50
|
+
"fanout-peerbit-sim",
|
|
51
|
+
"",
|
|
52
|
+
"Args:",
|
|
53
|
+
" --preset NAME preset workload (ci-small|ci-loss|live|reliable|scale-1k)",
|
|
54
|
+
" --nodes N (default: 1000)",
|
|
55
|
+
" --degree K (default: 6)",
|
|
56
|
+
" --seed S (default: 1)",
|
|
57
|
+
" --concurrency C join+dial concurrency (default: 200)",
|
|
58
|
+
" --bootstrapCount N tracker/bootstraps to use (default: 3, excludes rootIndex)",
|
|
59
|
+
" --bootstrapMaxPeers N max bootstraps to dial/query per node (default: 1, 0=all)",
|
|
60
|
+
" --rootIndex I (default: 0)",
|
|
61
|
+
" --subscribers N number of subscribers (default: nodes-1)",
|
|
62
|
+
" --relayFraction F fraction of subscribers that can relay (default: 0.25)",
|
|
63
|
+
" --messages M (default: 300)",
|
|
64
|
+
" --msgRate R messages/sec (default: 30)",
|
|
65
|
+
" --msgSize B bytes (default: 1024)",
|
|
66
|
+
" --intervalMs X override publish interval (default: derived from msgRate)",
|
|
67
|
+
" --settleMs X wait after publish (default: 2000)",
|
|
68
|
+
" --deadlineMs X deadline for 'within deadline' metric (default: 2000)",
|
|
69
|
+
" --timeoutMs X global timeout (default: 300000)",
|
|
70
|
+
" --joinTimeoutMs X per-peer join timeout (default: 60000)",
|
|
71
|
+
" --joinReqTimeoutMs X per-attempt join req timeout (default: 2000)",
|
|
72
|
+
" --rootUploadLimitBps BPS (default: 100000000)",
|
|
73
|
+
" --rootMaxChildren N (default: 64)",
|
|
74
|
+
" --relayUploadLimitBps BPS (default: 20000000)",
|
|
75
|
+
" --relayMaxChildren N (default: 32)",
|
|
76
|
+
" --repair 0|1 (default: 1)",
|
|
77
|
+
" --neighborRepair 0|1 (default: 0)",
|
|
78
|
+
" --allowKick 0|1 (default: 1)",
|
|
79
|
+
" --mockCrypto 0|1 (default: 1)",
|
|
80
|
+
" --dialDelayMs X (default: 0)",
|
|
81
|
+
" --streamRxDelayMs X (default: 0)",
|
|
82
|
+
" --streamHighWaterMarkBytes B (default: 262144)",
|
|
83
|
+
" --dropDataFrameRate P drop rate for low-priority FanoutTree data frames (default: 0)",
|
|
84
|
+
" --dropSeed S RNG seed for drops (default: seed)",
|
|
85
|
+
" --churnEveryMs X churn interval (default: 0, off)",
|
|
86
|
+
" --churnDownMs X offline duration per churn event (default: 0)",
|
|
87
|
+
" --churnFraction F fraction of joined subscribers to churn (default: 0)",
|
|
88
|
+
" --assertMinJoinedPct X fail if join% below X (default: 0, off)",
|
|
89
|
+
" --assertMinDeliveryPct X fail if delivery% below X (default: 0, off)",
|
|
90
|
+
" --assertMinDeadlineDeliveryPct X fail if deadline delivery% below X (default: 0, off)",
|
|
91
|
+
" --assertMaxOverheadFactor X fail if overheadFactorData above X (default: 0, off)",
|
|
92
|
+
" --assertMaxUploadFracPct X fail if peak upload above X% of cap (default: 0, off)",
|
|
93
|
+
" --assertMaxControlBpp X fail if control bytes / delivered payload bytes above X (default: 0, off)",
|
|
94
|
+
" --assertMaxTrackerBpp X fail if tracker bytes / delivered payload bytes above X (default: 0, off)",
|
|
95
|
+
" --assertMaxRepairBpp X fail if repair bytes / delivered payload bytes above X (default: 0, off)",
|
|
96
|
+
].join("\n"));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
const preset = maybeString("--preset");
|
|
100
|
+
const presetOpts = preset === "ci-small"
|
|
101
|
+
? {
|
|
102
|
+
nodes: 25,
|
|
103
|
+
degree: 4,
|
|
104
|
+
subscribers: 20,
|
|
105
|
+
relayFraction: 0.3,
|
|
106
|
+
messages: 20,
|
|
107
|
+
msgRate: 50,
|
|
108
|
+
msgSize: 64,
|
|
109
|
+
settleMs: 500,
|
|
110
|
+
deadlineMs: 500,
|
|
111
|
+
timeoutMs: 20_000,
|
|
112
|
+
bootstrapCount: 3,
|
|
113
|
+
bootstrapMaxPeers: 1,
|
|
114
|
+
rootMaxChildren: 4,
|
|
115
|
+
relayMaxChildren: 4,
|
|
116
|
+
repair: true,
|
|
117
|
+
neighborRepair: false,
|
|
118
|
+
assertMinJoinedPct: 99.9,
|
|
119
|
+
assertMinDeliveryPct: 99.9,
|
|
120
|
+
}
|
|
121
|
+
: preset === "ci-loss"
|
|
122
|
+
? {
|
|
123
|
+
nodes: 40,
|
|
124
|
+
degree: 4,
|
|
125
|
+
subscribers: 30,
|
|
126
|
+
relayFraction: 0.35,
|
|
127
|
+
messages: 40,
|
|
128
|
+
msgRate: 50,
|
|
129
|
+
msgSize: 64,
|
|
130
|
+
settleMs: 2_500,
|
|
131
|
+
deadlineMs: 2_000,
|
|
132
|
+
timeoutMs: 40_000,
|
|
133
|
+
bootstrapCount: 3,
|
|
134
|
+
bootstrapMaxPeers: 1,
|
|
135
|
+
rootMaxChildren: 4,
|
|
136
|
+
relayMaxChildren: 4,
|
|
137
|
+
repair: true,
|
|
138
|
+
neighborRepair: true,
|
|
139
|
+
dropDataFrameRate: 0.1,
|
|
140
|
+
// Keep this CI profile bounded in wall-clock time; very aggressive
|
|
141
|
+
// churn (e.g. 200ms) can dominate runtime and hit the global timeout.
|
|
142
|
+
churnEveryMs: 1_000,
|
|
143
|
+
churnDownMs: 100,
|
|
144
|
+
churnFraction: 0.05,
|
|
145
|
+
}
|
|
146
|
+
: preset === "live"
|
|
147
|
+
? {
|
|
148
|
+
nodes: 2000,
|
|
149
|
+
degree: 6,
|
|
150
|
+
bootstrapCount: 3,
|
|
151
|
+
bootstrapMaxPeers: 1,
|
|
152
|
+
messages: 30 * 60,
|
|
153
|
+
msgRate: 30,
|
|
154
|
+
msgSize: 1024,
|
|
155
|
+
settleMs: 2_000,
|
|
156
|
+
deadlineMs: 2_000,
|
|
157
|
+
rootUploadLimitBps: 20_000_000,
|
|
158
|
+
rootMaxChildren: 64,
|
|
159
|
+
relayUploadLimitBps: 10_000_000,
|
|
160
|
+
relayMaxChildren: 32,
|
|
161
|
+
repair: true,
|
|
162
|
+
neighborRepair: true,
|
|
163
|
+
dropDataFrameRate: 0.01,
|
|
164
|
+
churnEveryMs: 2_000,
|
|
165
|
+
churnDownMs: 1_000,
|
|
166
|
+
churnFraction: 0.005,
|
|
167
|
+
}
|
|
168
|
+
: preset === "reliable"
|
|
169
|
+
? {
|
|
170
|
+
nodes: 2000,
|
|
171
|
+
degree: 6,
|
|
172
|
+
bootstrapCount: 3,
|
|
173
|
+
bootstrapMaxPeers: 1,
|
|
174
|
+
messages: 30 * 60,
|
|
175
|
+
msgRate: 30,
|
|
176
|
+
msgSize: 1024,
|
|
177
|
+
settleMs: 10_000,
|
|
178
|
+
deadlineMs: 10_000,
|
|
179
|
+
rootUploadLimitBps: 20_000_000,
|
|
180
|
+
rootMaxChildren: 64,
|
|
181
|
+
relayUploadLimitBps: 10_000_000,
|
|
182
|
+
relayMaxChildren: 32,
|
|
183
|
+
repair: true,
|
|
184
|
+
neighborRepair: true,
|
|
185
|
+
dropDataFrameRate: 0.01,
|
|
186
|
+
churnEveryMs: 2_000,
|
|
187
|
+
churnDownMs: 1_000,
|
|
188
|
+
churnFraction: 0.005,
|
|
189
|
+
}
|
|
190
|
+
: preset === "scale-1k"
|
|
191
|
+
? {
|
|
192
|
+
nodes: 1000,
|
|
193
|
+
degree: 6,
|
|
194
|
+
bootstrapCount: 3,
|
|
195
|
+
bootstrapMaxPeers: 1,
|
|
196
|
+
messages: 200,
|
|
197
|
+
msgRate: 30,
|
|
198
|
+
msgSize: 1024,
|
|
199
|
+
settleMs: 5_000,
|
|
200
|
+
deadlineMs: 2_000,
|
|
201
|
+
timeoutMs: 300_000,
|
|
202
|
+
repair: true,
|
|
203
|
+
neighborRepair: true,
|
|
204
|
+
}
|
|
205
|
+
: {};
|
|
206
|
+
const nodes = Math.max(1, Math.floor(maybeNumber("--nodes") ?? presetOpts.nodes ?? 1000));
|
|
207
|
+
const msgRate = Math.max(1, Math.floor(maybeNumber("--msgRate") ?? presetOpts.msgRate ?? 30));
|
|
208
|
+
const seed = Math.max(0, Math.floor(maybeNumber("--seed") ?? presetOpts.seed ?? 1));
|
|
209
|
+
return {
|
|
210
|
+
nodes,
|
|
211
|
+
degree: Math.max(0, Math.floor(maybeNumber("--degree") ?? presetOpts.degree ?? 6)),
|
|
212
|
+
seed,
|
|
213
|
+
concurrency: Math.max(1, Math.floor(maybeNumber("--concurrency") ?? presetOpts.concurrency ?? 200)),
|
|
214
|
+
bootstrapCount: Math.max(0, Math.floor(maybeNumber("--bootstrapCount") ?? presetOpts.bootstrapCount ?? 3)),
|
|
215
|
+
bootstrapMaxPeers: Math.max(0, Math.floor(maybeNumber("--bootstrapMaxPeers") ?? presetOpts.bootstrapMaxPeers ?? 1)),
|
|
216
|
+
rootIndex: Math.max(0, Math.min(nodes - 1, Math.floor(maybeNumber("--rootIndex") ?? presetOpts.rootIndex ?? 0))),
|
|
217
|
+
subscribers: Math.max(0, Math.min(nodes - 1, Math.floor(maybeNumber("--subscribers") ?? presetOpts.subscribers ?? nodes - 1))),
|
|
218
|
+
relayFraction: Math.max(0, Math.min(1, Number(maybeNumber("--relayFraction") ?? presetOpts.relayFraction ?? 0.25))),
|
|
219
|
+
messages: Math.max(0, Math.floor(maybeNumber("--messages") ?? presetOpts.messages ?? 300)),
|
|
220
|
+
msgRate,
|
|
221
|
+
msgSize: Math.max(1, Math.floor(maybeNumber("--msgSize") ?? presetOpts.msgSize ?? 1024)),
|
|
222
|
+
intervalMs: Math.max(0, Math.floor(maybeNumber("--intervalMs") ?? presetOpts.intervalMs ?? Math.floor(1000 / msgRate))),
|
|
223
|
+
settleMs: Math.max(0, Math.floor(maybeNumber("--settleMs") ?? presetOpts.settleMs ?? 2000)),
|
|
224
|
+
deadlineMs: Math.max(0, Math.floor(maybeNumber("--deadlineMs") ?? presetOpts.deadlineMs ?? 2000)),
|
|
225
|
+
timeoutMs: Math.max(1, Math.floor(maybeNumber("--timeoutMs") ?? presetOpts.timeoutMs ?? 300_000)),
|
|
226
|
+
joinTimeoutMs: Math.max(1, Math.floor(maybeNumber("--joinTimeoutMs") ?? presetOpts.joinTimeoutMs ?? 60_000)),
|
|
227
|
+
joinReqTimeoutMs: Math.max(1, Math.floor(maybeNumber("--joinReqTimeoutMs") ?? presetOpts.joinReqTimeoutMs ?? 2_000)),
|
|
228
|
+
rootUploadLimitBps: Math.max(0, Math.floor(maybeNumber("--rootUploadLimitBps") ?? presetOpts.rootUploadLimitBps ?? 100_000_000)),
|
|
229
|
+
rootMaxChildren: Math.max(0, Math.floor(maybeNumber("--rootMaxChildren") ?? presetOpts.rootMaxChildren ?? 64)),
|
|
230
|
+
relayUploadLimitBps: Math.max(0, Math.floor(maybeNumber("--relayUploadLimitBps") ?? presetOpts.relayUploadLimitBps ?? 20_000_000)),
|
|
231
|
+
relayMaxChildren: Math.max(0, Math.floor(maybeNumber("--relayMaxChildren") ?? presetOpts.relayMaxChildren ?? 32)),
|
|
232
|
+
repair: bool("--repair", presetOpts.repair ?? true),
|
|
233
|
+
neighborRepair: bool("--neighborRepair", presetOpts.neighborRepair ?? false),
|
|
234
|
+
allowKick: bool("--allowKick", presetOpts.allowKick ?? true),
|
|
235
|
+
mockCrypto: bool("--mockCrypto", presetOpts.mockCrypto ?? true),
|
|
236
|
+
dialDelayMs: Math.max(0, Math.floor(maybeNumber("--dialDelayMs") ?? presetOpts.dialDelayMs ?? 0)),
|
|
237
|
+
streamRxDelayMs: Math.max(0, Math.floor(maybeNumber("--streamRxDelayMs") ?? presetOpts.streamRxDelayMs ?? 0)),
|
|
238
|
+
streamHighWaterMarkBytes: Math.max(1, Math.floor(maybeNumber("--streamHighWaterMarkBytes") ?? presetOpts.streamHighWaterMarkBytes ?? 256 * 1024)),
|
|
239
|
+
dropDataFrameRate: Math.max(0, Math.min(1, Number(maybeNumber("--dropDataFrameRate") ?? presetOpts.dropDataFrameRate ?? 0))),
|
|
240
|
+
dropSeed: Math.max(0, Math.floor(maybeNumber("--dropSeed") ?? presetOpts.dropSeed ?? seed)),
|
|
241
|
+
churnEveryMs: Math.max(0, Math.floor(maybeNumber("--churnEveryMs") ?? presetOpts.churnEveryMs ?? 0)),
|
|
242
|
+
churnDownMs: Math.max(0, Math.floor(maybeNumber("--churnDownMs") ?? presetOpts.churnDownMs ?? 0)),
|
|
243
|
+
churnFraction: Math.max(0, Math.min(1, Number(maybeNumber("--churnFraction") ?? presetOpts.churnFraction ?? 0))),
|
|
244
|
+
assertMinJoinedPct: Math.max(0, Number(maybeNumber("--assertMinJoinedPct") ?? presetOpts.assertMinJoinedPct ?? 0)),
|
|
245
|
+
assertMinDeliveryPct: Math.max(0, Number(maybeNumber("--assertMinDeliveryPct") ?? presetOpts.assertMinDeliveryPct ?? 0)),
|
|
246
|
+
assertMinDeadlineDeliveryPct: Math.max(0, Number(maybeNumber("--assertMinDeadlineDeliveryPct") ?? presetOpts.assertMinDeadlineDeliveryPct ?? 0)),
|
|
247
|
+
assertMaxOverheadFactor: Math.max(0, Number(maybeNumber("--assertMaxOverheadFactor") ?? presetOpts.assertMaxOverheadFactor ?? 0)),
|
|
248
|
+
assertMaxUploadFracPct: Math.max(0, Number(maybeNumber("--assertMaxUploadFracPct") ?? presetOpts.assertMaxUploadFracPct ?? 0)),
|
|
249
|
+
assertMaxControlBpp: Math.max(0, Number(maybeNumber("--assertMaxControlBpp") ?? presetOpts.assertMaxControlBpp ?? 0)),
|
|
250
|
+
assertMaxTrackerBpp: Math.max(0, Number(maybeNumber("--assertMaxTrackerBpp") ?? presetOpts.assertMaxTrackerBpp ?? 0)),
|
|
251
|
+
assertMaxRepairBpp: Math.max(0, Number(maybeNumber("--assertMaxRepairBpp") ?? presetOpts.assertMaxRepairBpp ?? 0)),
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
const runWithConcurrency = async (tasks, concurrency) => {
|
|
255
|
+
if (tasks.length === 0)
|
|
256
|
+
return [];
|
|
257
|
+
const results = new Array(tasks.length);
|
|
258
|
+
let next = 0;
|
|
259
|
+
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, async () => {
|
|
260
|
+
for (;;) {
|
|
261
|
+
const i = next++;
|
|
262
|
+
if (i >= tasks.length)
|
|
263
|
+
return;
|
|
264
|
+
results[i] = await tasks[i]();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
await Promise.all(workers);
|
|
268
|
+
return results;
|
|
269
|
+
};
|
|
270
|
+
const quantile = (sorted, q) => {
|
|
271
|
+
if (sorted.length === 0)
|
|
272
|
+
return 0;
|
|
273
|
+
const i = (sorted.length - 1) * q;
|
|
274
|
+
const lo = Math.floor(i);
|
|
275
|
+
const hi = Math.ceil(i);
|
|
276
|
+
if (lo === hi)
|
|
277
|
+
return sorted[lo];
|
|
278
|
+
const t = i - lo;
|
|
279
|
+
return sorted[lo] * (1 - t) + sorted[hi] * t;
|
|
280
|
+
};
|
|
281
|
+
const anySignal = (signals) => {
|
|
282
|
+
// Prefer built-in AbortSignal.any when available (Node >=18),
|
|
283
|
+
// otherwise fall back to a small polyfill.
|
|
284
|
+
const anyFn = AbortSignal.any;
|
|
285
|
+
if (typeof anyFn === "function") {
|
|
286
|
+
return anyFn(signals);
|
|
287
|
+
}
|
|
288
|
+
const controller = new AbortController();
|
|
289
|
+
const onAbort = (ev) => {
|
|
290
|
+
const sig = ev?.target;
|
|
291
|
+
controller.abort(sig?.reason ?? new Error("aborted"));
|
|
292
|
+
};
|
|
293
|
+
for (const s of signals) {
|
|
294
|
+
if (s.aborted) {
|
|
295
|
+
controller.abort(s.reason ?? new Error("aborted"));
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
299
|
+
}
|
|
300
|
+
controller.signal.clear = () => {
|
|
301
|
+
for (const s of signals)
|
|
302
|
+
s.removeEventListener("abort", onAbort);
|
|
303
|
+
};
|
|
304
|
+
return controller.signal;
|
|
305
|
+
};
|
|
306
|
+
const main = async () => {
|
|
307
|
+
const params = parseArgs(process.argv.slice(2));
|
|
308
|
+
const timeout = new AbortController();
|
|
309
|
+
const timeoutId = setTimeout(() => {
|
|
310
|
+
timeout.abort(new Error(`timeout after ${params.timeoutMs}ms`));
|
|
311
|
+
}, params.timeoutMs);
|
|
312
|
+
let session;
|
|
313
|
+
let mainError;
|
|
314
|
+
let stopError;
|
|
315
|
+
try {
|
|
316
|
+
session = await TestSession.disconnectedInMemory(params.nodes, {
|
|
317
|
+
seed: params.seed,
|
|
318
|
+
concurrency: params.concurrency,
|
|
319
|
+
mockCrypto: params.mockCrypto,
|
|
320
|
+
network: {
|
|
321
|
+
dialDelayMs: params.dialDelayMs,
|
|
322
|
+
streamRxDelayMs: params.streamRxDelayMs,
|
|
323
|
+
streamHighWaterMarkBytes: params.streamHighWaterMarkBytes,
|
|
324
|
+
dropDataFrameRate: params.dropDataFrameRate,
|
|
325
|
+
dropSeed: params.dropSeed,
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
// Sparse underlay (bounded degree) for large-n.
|
|
329
|
+
const graph = (await session.connectRandomGraph({
|
|
330
|
+
degree: Math.min(params.degree, Math.max(0, params.nodes - 1)),
|
|
331
|
+
seed: params.seed,
|
|
332
|
+
concurrency: params.concurrency,
|
|
333
|
+
}))[0] ?? [];
|
|
334
|
+
const rng = mulberry32(params.seed);
|
|
335
|
+
const rootPeer = session.peers[params.rootIndex];
|
|
336
|
+
const root = rootPeer.services.fanout;
|
|
337
|
+
const topic = "concert";
|
|
338
|
+
const rootId = root.publicKeyHash;
|
|
339
|
+
const rootNeighbors = new Set(graph[params.rootIndex] ?? []);
|
|
340
|
+
const bootstrapIndices = [];
|
|
341
|
+
for (let i = 0; i < params.nodes && bootstrapIndices.length < params.bootstrapCount; i++) {
|
|
342
|
+
if (i === params.rootIndex)
|
|
343
|
+
continue;
|
|
344
|
+
bootstrapIndices.push(i);
|
|
345
|
+
}
|
|
346
|
+
const bootstrapAddrs = [
|
|
347
|
+
...new Set(bootstrapIndices.flatMap((idx) => {
|
|
348
|
+
const p = session.peers[idx];
|
|
349
|
+
const addrs = p?.libp2p?.getMultiaddrs?.() ?? [];
|
|
350
|
+
return addrs.map((a) => (typeof a === "string" ? a : a.toString()));
|
|
351
|
+
})),
|
|
352
|
+
];
|
|
353
|
+
// Use bootstraps as rendezvous trackers (announcements + queries).
|
|
354
|
+
if (bootstrapAddrs.length > 0) {
|
|
355
|
+
root.setBootstraps(bootstrapAddrs);
|
|
356
|
+
}
|
|
357
|
+
// Choose subscribers (exclude root).
|
|
358
|
+
const candidates = [];
|
|
359
|
+
for (let i = 0; i < params.nodes; i++) {
|
|
360
|
+
if (i === params.rootIndex)
|
|
361
|
+
continue;
|
|
362
|
+
candidates.push(i);
|
|
363
|
+
}
|
|
364
|
+
for (let i = candidates.length - 1; i > 0; i--) {
|
|
365
|
+
const j = Math.floor(rng() * (i + 1));
|
|
366
|
+
const tmp = candidates[i];
|
|
367
|
+
candidates[i] = candidates[j];
|
|
368
|
+
candidates[j] = tmp;
|
|
369
|
+
}
|
|
370
|
+
const subscriberIndices = candidates.slice(0, params.subscribers);
|
|
371
|
+
// Select relays among subscribers.
|
|
372
|
+
const relayCount = Math.max(0, Math.min(subscriberIndices.length, Math.floor(params.relayFraction * subscriberIndices.length)));
|
|
373
|
+
const relaySet = new Set();
|
|
374
|
+
if (relayCount > 0) {
|
|
375
|
+
// Force root-adjacent relays so the tree can actually grow on sparse underlays.
|
|
376
|
+
// (Otherwise, the join can stall at ~root degree if none of the root neighbors can relay.)
|
|
377
|
+
for (const idx of rootNeighbors) {
|
|
378
|
+
if (idx === params.rootIndex)
|
|
379
|
+
continue;
|
|
380
|
+
relaySet.add(idx);
|
|
381
|
+
}
|
|
382
|
+
for (const idx of subscriberIndices) {
|
|
383
|
+
if (relaySet.size >= relayCount)
|
|
384
|
+
break;
|
|
385
|
+
relaySet.add(idx);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Root opens channel.
|
|
389
|
+
root.openChannel(topic, rootId, {
|
|
390
|
+
role: "root",
|
|
391
|
+
msgRate: params.msgRate,
|
|
392
|
+
msgSize: params.msgSize,
|
|
393
|
+
uploadLimitBps: params.rootUploadLimitBps,
|
|
394
|
+
maxChildren: params.rootMaxChildren,
|
|
395
|
+
allowKick: params.allowKick,
|
|
396
|
+
repair: params.repair,
|
|
397
|
+
neighborRepair: params.neighborRepair,
|
|
398
|
+
});
|
|
399
|
+
const joinedNodes = new Set();
|
|
400
|
+
// Bootstrap the tree by joining relays adjacent to the root first.
|
|
401
|
+
// This makes join success much less sensitive to random relay placement.
|
|
402
|
+
if (relayCount > 0 && rootNeighbors.size > 0) {
|
|
403
|
+
const bootstrapRelays = [...rootNeighbors].filter((i) => i !== params.rootIndex && i < params.nodes);
|
|
404
|
+
await runWithConcurrency(bootstrapRelays.map((idx) => async () => {
|
|
405
|
+
const peer = session.peers[idx];
|
|
406
|
+
const fanout = peer.services.fanout;
|
|
407
|
+
await fanout.joinChannel(topic, rootId, {
|
|
408
|
+
msgRate: params.msgRate,
|
|
409
|
+
msgSize: params.msgSize,
|
|
410
|
+
uploadLimitBps: params.relayUploadLimitBps,
|
|
411
|
+
maxChildren: params.relayMaxChildren,
|
|
412
|
+
allowKick: params.allowKick,
|
|
413
|
+
repair: params.repair,
|
|
414
|
+
neighborRepair: params.neighborRepair,
|
|
415
|
+
}, {
|
|
416
|
+
bootstrap: bootstrapAddrs,
|
|
417
|
+
bootstrapMaxPeers: params.bootstrapMaxPeers,
|
|
418
|
+
timeoutMs: Math.max(1, Math.min(params.joinTimeoutMs, 10_000)),
|
|
419
|
+
joinReqTimeoutMs: params.joinReqTimeoutMs,
|
|
420
|
+
signal: timeout.signal,
|
|
421
|
+
});
|
|
422
|
+
joinedNodes.add(idx);
|
|
423
|
+
}), Math.min(params.concurrency, bootstrapRelays.length));
|
|
424
|
+
}
|
|
425
|
+
const joinStart = Date.now();
|
|
426
|
+
const joined = new Uint8Array(subscriberIndices.length);
|
|
427
|
+
await runWithConcurrency(subscriberIndices.map((idx, i) => async () => {
|
|
428
|
+
const peer = session.peers[idx];
|
|
429
|
+
const fanout = peer.services.fanout;
|
|
430
|
+
const isRelay = relaySet.has(idx);
|
|
431
|
+
try {
|
|
432
|
+
await fanout.joinChannel(topic, rootId, {
|
|
433
|
+
msgRate: params.msgRate,
|
|
434
|
+
msgSize: params.msgSize,
|
|
435
|
+
uploadLimitBps: isRelay ? params.relayUploadLimitBps : 0,
|
|
436
|
+
maxChildren: isRelay ? params.relayMaxChildren : 0,
|
|
437
|
+
allowKick: params.allowKick,
|
|
438
|
+
repair: params.repair,
|
|
439
|
+
neighborRepair: params.neighborRepair,
|
|
440
|
+
}, {
|
|
441
|
+
bootstrap: bootstrapAddrs,
|
|
442
|
+
bootstrapMaxPeers: params.bootstrapMaxPeers,
|
|
443
|
+
timeoutMs: params.joinTimeoutMs,
|
|
444
|
+
joinReqTimeoutMs: params.joinReqTimeoutMs,
|
|
445
|
+
signal: timeout.signal,
|
|
446
|
+
});
|
|
447
|
+
joined[i] = 1;
|
|
448
|
+
joinedNodes.add(idx);
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
joined[i] = 0;
|
|
452
|
+
}
|
|
453
|
+
}), params.concurrency);
|
|
454
|
+
const joinedCount = joined.reduce((a, b) => a + b, 0);
|
|
455
|
+
const joinMs = Date.now() - joinStart;
|
|
456
|
+
// Delivery tracking.
|
|
457
|
+
const publishAt = new Array(params.messages).fill(0);
|
|
458
|
+
const receivedBySubscriber = subscriberIndices.map(() => new Uint8Array(params.messages));
|
|
459
|
+
let churnEvents = 0;
|
|
460
|
+
let churnedPeersTotal = 0;
|
|
461
|
+
let delivered = 0;
|
|
462
|
+
let deliveredWithinDeadline = 0;
|
|
463
|
+
let duplicates = 0;
|
|
464
|
+
const latencySamples = [];
|
|
465
|
+
const maxLatencySamples = 50_000;
|
|
466
|
+
let latencySeen = 0;
|
|
467
|
+
const recordLatency = (ms) => {
|
|
468
|
+
latencySeen += 1;
|
|
469
|
+
if (latencySamples.length < maxLatencySamples) {
|
|
470
|
+
latencySamples.push(ms);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const j = Math.floor(rng() * latencySeen);
|
|
474
|
+
if (j < maxLatencySamples)
|
|
475
|
+
latencySamples[j] = ms;
|
|
476
|
+
};
|
|
477
|
+
for (let i = 0; i < subscriberIndices.length; i++) {
|
|
478
|
+
if (!joined[i])
|
|
479
|
+
continue;
|
|
480
|
+
const idx = subscriberIndices[i];
|
|
481
|
+
const peer = session.peers[idx];
|
|
482
|
+
const fanout = peer.services.fanout;
|
|
483
|
+
fanout.addEventListener("fanout:data", (ev) => {
|
|
484
|
+
const d = ev?.detail;
|
|
485
|
+
if (!d)
|
|
486
|
+
return;
|
|
487
|
+
if (d.topic !== topic)
|
|
488
|
+
return;
|
|
489
|
+
if (d.root !== rootId)
|
|
490
|
+
return;
|
|
491
|
+
const seq = d.seq >>> 0;
|
|
492
|
+
if (seq >= params.messages)
|
|
493
|
+
return;
|
|
494
|
+
const seenArr = receivedBySubscriber[i];
|
|
495
|
+
if (seenArr[seq]) {
|
|
496
|
+
duplicates += 1;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
seenArr[seq] = 1;
|
|
500
|
+
delivered += 1;
|
|
501
|
+
const t0 = publishAt[seq] ?? 0;
|
|
502
|
+
if (t0 > 0) {
|
|
503
|
+
const ms = Date.now() - t0;
|
|
504
|
+
recordLatency(ms);
|
|
505
|
+
if (params.deadlineMs > 0 && ms <= params.deadlineMs) {
|
|
506
|
+
deliveredWithinDeadline += 1;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
// Publish loop.
|
|
512
|
+
const payload = new Uint8Array(params.msgSize);
|
|
513
|
+
const churnController = new AbortController();
|
|
514
|
+
const churnSignal = anySignal([timeout.signal, churnController.signal]);
|
|
515
|
+
const churnLoop = async () => {
|
|
516
|
+
const everyMs = Math.max(0, Math.floor(params.churnEveryMs));
|
|
517
|
+
const downMs = Math.max(0, Math.floor(params.churnDownMs));
|
|
518
|
+
const fraction = Math.max(0, Math.min(1, Number(params.churnFraction)));
|
|
519
|
+
if (everyMs <= 0 || downMs <= 0 || fraction <= 0)
|
|
520
|
+
return;
|
|
521
|
+
const network = session.inMemory?.network;
|
|
522
|
+
if (!network)
|
|
523
|
+
return;
|
|
524
|
+
const joinedSubscriberIndices = subscriberIndices.filter((_, i) => joined[i] === 1);
|
|
525
|
+
if (joinedSubscriberIndices.length === 0)
|
|
526
|
+
return;
|
|
527
|
+
const excluded = new Set([params.rootIndex, ...bootstrapIndices]);
|
|
528
|
+
for (;;) {
|
|
529
|
+
if (churnSignal.aborted)
|
|
530
|
+
return;
|
|
531
|
+
await delay(everyMs, { signal: churnSignal });
|
|
532
|
+
if (churnSignal.aborted)
|
|
533
|
+
return;
|
|
534
|
+
const candidates = joinedSubscriberIndices.filter((idx) => !excluded.has(idx));
|
|
535
|
+
if (candidates.length === 0)
|
|
536
|
+
continue;
|
|
537
|
+
const target = Math.min(candidates.length, Math.max(1, Math.floor(candidates.length * fraction)));
|
|
538
|
+
const chosen = new Set();
|
|
539
|
+
const maxAttempts = Math.max(10, target * 20);
|
|
540
|
+
for (let tries = 0; chosen.size < target && tries < maxAttempts; tries++) {
|
|
541
|
+
const idx = candidates[Math.floor(rng() * candidates.length)];
|
|
542
|
+
const peer = session.peers[idx];
|
|
543
|
+
const peerId = peer?.libp2p?.peerId;
|
|
544
|
+
if (!peerId)
|
|
545
|
+
continue;
|
|
546
|
+
if (network.isPeerOffline(peerId))
|
|
547
|
+
continue;
|
|
548
|
+
chosen.add(idx);
|
|
549
|
+
}
|
|
550
|
+
if (chosen.size === 0)
|
|
551
|
+
continue;
|
|
552
|
+
churnEvents += 1;
|
|
553
|
+
churnedPeersTotal += chosen.size;
|
|
554
|
+
const now = Date.now();
|
|
555
|
+
await Promise.all([...chosen].map(async (idx) => {
|
|
556
|
+
const peer = session.peers[idx];
|
|
557
|
+
const peerId = peer?.libp2p?.peerId;
|
|
558
|
+
if (!peerId)
|
|
559
|
+
return;
|
|
560
|
+
network.setPeerOffline(peerId, downMs, now);
|
|
561
|
+
await network.disconnectPeer(peerId);
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const publishStart = Date.now();
|
|
566
|
+
const churnPromise = churnLoop().catch(() => { });
|
|
567
|
+
try {
|
|
568
|
+
for (let seq = 0; seq < params.messages; seq++) {
|
|
569
|
+
if (timeout.signal.aborted)
|
|
570
|
+
break;
|
|
571
|
+
publishAt[seq] = Date.now();
|
|
572
|
+
await root.publishData(topic, rootId, payload);
|
|
573
|
+
if (params.intervalMs > 0) {
|
|
574
|
+
await delay(params.intervalMs, { signal: timeout.signal });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
finally {
|
|
579
|
+
churnController.abort();
|
|
580
|
+
await churnPromise;
|
|
581
|
+
churnSignal.clear?.();
|
|
582
|
+
}
|
|
583
|
+
const publishMs = Date.now() - publishStart;
|
|
584
|
+
// Signal end-of-stream so subscribers can detect tail gaps and repair.
|
|
585
|
+
if (params.repair && params.messages > 0) {
|
|
586
|
+
await root.publishEnd(topic, rootId, params.messages);
|
|
587
|
+
}
|
|
588
|
+
// Allow repair/tail settling.
|
|
589
|
+
if (params.settleMs > 0) {
|
|
590
|
+
await delay(params.settleMs, { signal: timeout.signal });
|
|
591
|
+
}
|
|
592
|
+
const expected = joinedCount * params.messages;
|
|
593
|
+
const deliveredPct = expected > 0 ? (100 * delivered) / expected : 0;
|
|
594
|
+
const deliveredWithinDeadlinePct = expected > 0 ? (100 * deliveredWithinDeadline) / expected : 0;
|
|
595
|
+
latencySamples.sort((a, b) => a - b);
|
|
596
|
+
const latencyP50 = quantile(latencySamples, 0.5);
|
|
597
|
+
const latencyP95 = quantile(latencySamples, 0.95);
|
|
598
|
+
const latencyP99 = quantile(latencySamples, 0.99);
|
|
599
|
+
const latencyMax = latencySamples.length > 0 ? latencySamples[latencySamples.length - 1] : 0;
|
|
600
|
+
// Protocol stats.
|
|
601
|
+
let dataPayloadBytesSent = 0;
|
|
602
|
+
let controlBytesSent = 0;
|
|
603
|
+
let controlBytesSentJoin = 0;
|
|
604
|
+
let controlBytesSentRepair = 0;
|
|
605
|
+
let controlBytesSentTracker = 0;
|
|
606
|
+
let dataWriteDrops = 0;
|
|
607
|
+
let reparent = 0;
|
|
608
|
+
for (const p of session.peers) {
|
|
609
|
+
const fanout = p.services.fanout;
|
|
610
|
+
const m = fanout.getChannelMetrics(topic, rootId);
|
|
611
|
+
dataPayloadBytesSent += m.dataPayloadBytesSent;
|
|
612
|
+
controlBytesSent += m.controlBytesSent;
|
|
613
|
+
controlBytesSentJoin += m.controlBytesSentJoin;
|
|
614
|
+
controlBytesSentRepair += m.controlBytesSentRepair;
|
|
615
|
+
controlBytesSentTracker += m.controlBytesSentTracker;
|
|
616
|
+
dataWriteDrops += m.dataWriteDrops ?? 0;
|
|
617
|
+
reparent +=
|
|
618
|
+
(m.reparentDisconnect ?? 0) + (m.reparentStale ?? 0) + (m.reparentKicked ?? 0);
|
|
619
|
+
}
|
|
620
|
+
const idealTreePayloadBytes = params.msgSize * params.messages * joinedNodes.size;
|
|
621
|
+
const overheadFactorData = idealTreePayloadBytes > 0 ? dataPayloadBytesSent / idealTreePayloadBytes : 0;
|
|
622
|
+
const deliveredPayloadBytes = delivered * params.msgSize;
|
|
623
|
+
const controlBpp = deliveredPayloadBytes > 0 ? controlBytesSent / deliveredPayloadBytes : 0;
|
|
624
|
+
const trackerBpp = deliveredPayloadBytes > 0 ? controlBytesSentTracker / deliveredPayloadBytes : 0;
|
|
625
|
+
const repairBpp = deliveredPayloadBytes > 0 ? controlBytesSentRepair / deliveredPayloadBytes : 0;
|
|
626
|
+
// Peak upload vs cap (best-effort; counts framed bytes, including overhead).
|
|
627
|
+
const uploadCapByHash = new Map();
|
|
628
|
+
if (params.rootUploadLimitBps > 0) {
|
|
629
|
+
uploadCapByHash.set(rootId, params.rootUploadLimitBps);
|
|
630
|
+
}
|
|
631
|
+
for (const idx of joinedNodes) {
|
|
632
|
+
if (idx === params.rootIndex)
|
|
633
|
+
continue;
|
|
634
|
+
if (!relaySet.has(idx))
|
|
635
|
+
continue;
|
|
636
|
+
if (params.relayUploadLimitBps <= 0)
|
|
637
|
+
continue;
|
|
638
|
+
const h = session.peers[idx].services.fanout.publicKeyHash;
|
|
639
|
+
uploadCapByHash.set(h, params.relayUploadLimitBps);
|
|
640
|
+
}
|
|
641
|
+
let maxUploadFracPct = 0;
|
|
642
|
+
let maxUploadNode;
|
|
643
|
+
let maxUploadBps = 0;
|
|
644
|
+
for (const [hash, pm] of session.inMemory?.network.peerMetricsByHash ?? []) {
|
|
645
|
+
const cap = uploadCapByHash.get(hash);
|
|
646
|
+
if (!cap || cap <= 0)
|
|
647
|
+
continue;
|
|
648
|
+
const frac = (100 * pm.maxBytesPerSecond) / cap;
|
|
649
|
+
if (frac > maxUploadFracPct) {
|
|
650
|
+
maxUploadFracPct = frac;
|
|
651
|
+
maxUploadNode = hash;
|
|
652
|
+
maxUploadBps = pm.maxBytesPerSecond;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// Stream queue stats.
|
|
656
|
+
let streamQueuedBytesTotal = 0;
|
|
657
|
+
let streamQueuedBytesMax = 0;
|
|
658
|
+
for (const p of session.peers) {
|
|
659
|
+
const q = Math.max(0, Math.floor(p.services.fanout.getQueuedBytes()));
|
|
660
|
+
streamQueuedBytesTotal += q;
|
|
661
|
+
if (q > streamQueuedBytesMax)
|
|
662
|
+
streamQueuedBytesMax = q;
|
|
663
|
+
}
|
|
664
|
+
const lines = [];
|
|
665
|
+
lines.push("fanout peerbit-sim results");
|
|
666
|
+
lines.push(`- nodes: ${params.nodes}, degree: ${params.degree}, seed: ${params.seed}`);
|
|
667
|
+
lines.push(`- bootstraps: count=${bootstrapIndices.length}, addrs=${bootstrapAddrs.length}, maxPeers=${params.bootstrapMaxPeers} (excludes rootIndex=${params.rootIndex})`);
|
|
668
|
+
lines.push(`- subscribers: ${params.subscribers} (relays=${relayCount}, relayFraction=${params.relayFraction.toFixed(2)})`);
|
|
669
|
+
lines.push(`- join: joined=${joinedCount}/${params.subscribers} (${((100 * joinedCount) /
|
|
670
|
+
Math.max(1, params.subscribers)).toFixed(2)}%), time=${joinMs}ms`);
|
|
671
|
+
lines.push(`- tree: attachedNodes=${joinedNodes.size + 1} (root+${joinedNodes.size})`);
|
|
672
|
+
lines.push(`- publish: messages=${params.messages}, msgRate=${params.msgRate}/s, msgSize=${params.msgSize}B, intervalMs=${params.intervalMs}, time=${publishMs}ms`);
|
|
673
|
+
lines.push(`- delivery: expected=${expected}, delivered=${delivered} (${deliveredPct.toFixed(2)}%), withinDeadline=${deliveredWithinDeadline} (${deliveredWithinDeadlinePct.toFixed(2)}%), duplicates=${duplicates}`);
|
|
674
|
+
lines.push(`- latency ms: samples=${latencySamples.length}, p50=${latencyP50.toFixed(1)}, p95=${latencyP95.toFixed(1)}, p99=${latencyP99.toFixed(1)}, max=${latencyMax.toFixed(1)}`);
|
|
675
|
+
lines.push(`- overhead: dataPayloadBytesSent=${dataPayloadBytesSent}, idealTreePayloadBytes=${idealTreePayloadBytes}, overheadFactorData=${overheadFactorData.toFixed(3)}`);
|
|
676
|
+
lines.push(`- control: bytesSent=${controlBytesSent} (join=${controlBytesSentJoin} tracker=${controlBytesSentTracker} repair=${controlBytesSentRepair}) bpp=${controlBpp.toFixed(4)} (tracker=${trackerBpp.toFixed(4)} repair=${repairBpp.toFixed(4)}) dataWriteDrops=${dataWriteDrops} reparent=${reparent}`);
|
|
677
|
+
lines.push(`- upload: max=${maxUploadBps} B/s (${maxUploadFracPct.toFixed(1)}% of cap) node=${maxUploadNode ?? "-"}`);
|
|
678
|
+
lines.push(`- streamQueuedBytes: total=${streamQueuedBytesTotal}, maxNode=${streamQueuedBytesMax}`);
|
|
679
|
+
lines.push(`- churn: everyMs=${params.churnEveryMs} downMs=${params.churnDownMs} fraction=${params.churnFraction} events=${churnEvents} peers=${churnedPeersTotal}`);
|
|
680
|
+
lines.push(`- loss: dropDataFrameRate=${params.dropDataFrameRate} dropSeed=${params.dropSeed}`);
|
|
681
|
+
if (session.inMemory?.network) {
|
|
682
|
+
const m = session.inMemory.network.metrics;
|
|
683
|
+
lines.push(`- network: dials=${m.dials}, connsOpened=${m.connectionsOpened}, connsClosed=${m.connectionsClosed}, streamsOpened=${m.streamsOpened}, framesSent=${m.framesSent}, bytesSent=${m.bytesSent}, framesDropped=${m.framesDropped}, bytesDropped=${m.bytesDropped}`);
|
|
684
|
+
}
|
|
685
|
+
console.log(lines.join("\n"));
|
|
686
|
+
const joinedPct = params.subscribers > 0 ? (100 * joinedCount) / params.subscribers : 100;
|
|
687
|
+
const asserts = [];
|
|
688
|
+
if (params.assertMinJoinedPct > 0 && joinedPct < params.assertMinJoinedPct) {
|
|
689
|
+
asserts.push(`join% ${joinedPct.toFixed(2)} < assertMinJoinedPct ${params.assertMinJoinedPct}`);
|
|
690
|
+
}
|
|
691
|
+
if (params.assertMinDeliveryPct > 0 && deliveredPct < params.assertMinDeliveryPct) {
|
|
692
|
+
asserts.push(`delivery% ${deliveredPct.toFixed(2)} < assertMinDeliveryPct ${params.assertMinDeliveryPct}`);
|
|
693
|
+
}
|
|
694
|
+
if (params.assertMinDeadlineDeliveryPct > 0 &&
|
|
695
|
+
deliveredWithinDeadlinePct < params.assertMinDeadlineDeliveryPct) {
|
|
696
|
+
asserts.push(`deadline% ${deliveredWithinDeadlinePct.toFixed(2)} < assertMinDeadlineDeliveryPct ${params.assertMinDeadlineDeliveryPct}`);
|
|
697
|
+
}
|
|
698
|
+
if (params.assertMaxOverheadFactor > 0 && overheadFactorData > params.assertMaxOverheadFactor) {
|
|
699
|
+
asserts.push(`overheadFactorData ${overheadFactorData.toFixed(3)} > assertMaxOverheadFactor ${params.assertMaxOverheadFactor}`);
|
|
700
|
+
}
|
|
701
|
+
if (params.assertMaxUploadFracPct > 0 && maxUploadFracPct > params.assertMaxUploadFracPct) {
|
|
702
|
+
asserts.push(`maxUploadFracPct ${maxUploadFracPct.toFixed(1)} > assertMaxUploadFracPct ${params.assertMaxUploadFracPct}`);
|
|
703
|
+
}
|
|
704
|
+
if (params.assertMaxControlBpp > 0 && controlBpp > params.assertMaxControlBpp) {
|
|
705
|
+
asserts.push(`controlBpp ${controlBpp.toFixed(4)} > assertMaxControlBpp ${params.assertMaxControlBpp}`);
|
|
706
|
+
}
|
|
707
|
+
if (params.assertMaxTrackerBpp > 0 && trackerBpp > params.assertMaxTrackerBpp) {
|
|
708
|
+
asserts.push(`trackerBpp ${trackerBpp.toFixed(4)} > assertMaxTrackerBpp ${params.assertMaxTrackerBpp}`);
|
|
709
|
+
}
|
|
710
|
+
if (params.assertMaxRepairBpp > 0 && repairBpp > params.assertMaxRepairBpp) {
|
|
711
|
+
asserts.push(`repairBpp ${repairBpp.toFixed(4)} > assertMaxRepairBpp ${params.assertMaxRepairBpp}`);
|
|
712
|
+
}
|
|
713
|
+
if (asserts.length > 0) {
|
|
714
|
+
throw new Error(`fanout-peerbit-sim assertions failed: ${asserts.join("; ")}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
mainError = error;
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
clearTimeout(timeoutId);
|
|
723
|
+
if (session) {
|
|
724
|
+
try {
|
|
725
|
+
await session.stop();
|
|
726
|
+
}
|
|
727
|
+
catch (sessionStopError) {
|
|
728
|
+
if (!mainError) {
|
|
729
|
+
stopError = sessionStopError;
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
console.error("fanout-peerbit-sim: failed to stop session after error", sessionStopError);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (stopError) {
|
|
738
|
+
throw stopError;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
void main().catch((err) => {
|
|
742
|
+
console.error(err);
|
|
743
|
+
process.exitCode = 1;
|
|
744
|
+
});
|
|
745
|
+
//# sourceMappingURL=fanout-peerbit-sim.js.map
|