@peerbit/pubsub 5.3.12 → 5.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/dist/src/index.d.ts +31 -13
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +622 -145
- package/dist/src/index.js.map +1 -1
- package/package.json +8 -7
- package/src/index.ts +914 -216
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts +0 -3
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js +0 -90
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts +0 -2
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js +0 -712
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts +0 -2
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js +0 -1591
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts +0 -2
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js +0 -297
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts +0 -73
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js +0 -183
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js.map +0 -1
- package/dist/benchmark/fanout-tree-sim-lib.d.ts +0 -303
- package/dist/benchmark/fanout-tree-sim-lib.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-sim-lib.js +0 -1752
- package/dist/benchmark/fanout-tree-sim-lib.js.map +0 -1
- package/dist/benchmark/fanout-tree-sim.d.ts +0 -2
- package/dist/benchmark/fanout-tree-sim.d.ts.map +0 -1
- package/dist/benchmark/fanout-tree-sim.js +0 -870
- package/dist/benchmark/fanout-tree-sim.js.map +0 -1
- package/dist/benchmark/index.d.ts +0 -8
- package/dist/benchmark/index.d.ts.map +0 -1
- package/dist/benchmark/index.js +0 -62
- package/dist/benchmark/index.js.map +0 -1
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts +0 -86
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts.map +0 -1
- package/dist/benchmark/pubsub-topic-sim-lib.js +0 -680
- package/dist/benchmark/pubsub-topic-sim-lib.js.map +0 -1
- package/dist/benchmark/pubsub-topic-sim.d.ts +0 -9
- package/dist/benchmark/pubsub-topic-sim.d.ts.map +0 -1
- package/dist/benchmark/pubsub-topic-sim.js +0 -134
- package/dist/benchmark/pubsub-topic-sim.js.map +0 -1
- package/dist/benchmark/sim/bench-utils.d.ts +0 -25
- package/dist/benchmark/sim/bench-utils.d.ts.map +0 -1
- package/dist/benchmark/sim/bench-utils.js +0 -141
- package/dist/benchmark/sim/bench-utils.js.map +0 -1
|
@@ -1,1752 +0,0 @@
|
|
|
1
|
-
import { PreHash, SignatureWithKey } from "@peerbit/crypto";
|
|
2
|
-
import { InMemoryNetwork, InMemorySession, } from "@peerbit/libp2p-test-utils/inmemory-libp2p.js";
|
|
3
|
-
import { delay } from "@peerbit/time";
|
|
4
|
-
import { anySignal } from "any-signal";
|
|
5
|
-
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
6
|
-
import { FanoutTree } from "../src/index.js";
|
|
7
|
-
import { parentUpgradeRuntimeOptions } from "./fanout-tree-parent-upgrade-preset.js";
|
|
8
|
-
import { int, mulberry32, quantile, runWithConcurrency, } from "./sim/bench-utils.js";
|
|
9
|
-
class SimFanoutTree extends FanoutTree {
|
|
10
|
-
constructor(c, opts) {
|
|
11
|
-
super(c, opts);
|
|
12
|
-
// Fast/mock signing: keep signer identity semantics but skip crypto work.
|
|
13
|
-
this.sign = async () => new SignatureWithKey({
|
|
14
|
-
signature: new Uint8Array([0]),
|
|
15
|
-
publicKey: this.publicKey,
|
|
16
|
-
prehash: PreHash.NONE,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
async verifyAndProcess(message) {
|
|
20
|
-
// Skip expensive crypto verify for large sims, but keep session handling behavior
|
|
21
|
-
// consistent with the real implementation.
|
|
22
|
-
const from = message.header.signatures.publicKeys[0];
|
|
23
|
-
if (!this.peers.has(from.hashcode())) {
|
|
24
|
-
this.updateSession(from, Number(message.header.session));
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const effectiveMaxChildrenForUpload = (params, ch, maxChildren) => {
|
|
30
|
-
const requested = Math.max(0, Math.floor(maxChildren));
|
|
31
|
-
const uploadLimitBps = Math.max(0, Math.floor(ch.uploadLimitBps ?? 0));
|
|
32
|
-
if (uploadLimitBps <= 0)
|
|
33
|
-
return 0;
|
|
34
|
-
const msgRate = Math.max(1, Math.floor(params.msgRate));
|
|
35
|
-
const msgSize = Math.max(1, Math.floor(params.msgSize));
|
|
36
|
-
const uploadOverheadBytes = Math.max(0, Math.floor(ch.uploadOverheadBytes ?? 128));
|
|
37
|
-
const perChildBytes = Math.max(1, 1 + msgSize + uploadOverheadBytes);
|
|
38
|
-
const perChildBps = Math.max(1, Math.floor(msgRate * perChildBytes));
|
|
39
|
-
return Math.max(0, Math.min(requested, Math.floor(uploadLimitBps / perChildBps)));
|
|
40
|
-
};
|
|
41
|
-
const pickDistinct = (rng, n, k, exclude) => {
|
|
42
|
-
if (k <= 0)
|
|
43
|
-
return [];
|
|
44
|
-
const out = new Set();
|
|
45
|
-
while (out.size < k) {
|
|
46
|
-
const candidate = int(rng, n);
|
|
47
|
-
if (exclude.has(candidate))
|
|
48
|
-
continue;
|
|
49
|
-
out.add(candidate);
|
|
50
|
-
}
|
|
51
|
-
return [...out];
|
|
52
|
-
};
|
|
53
|
-
const parseSimPeerIndex = (peerId) => {
|
|
54
|
-
const s = String(peerId?.toString?.() ?? "");
|
|
55
|
-
const m = s.match(/sim-(\d+)/);
|
|
56
|
-
if (!m)
|
|
57
|
-
return 0;
|
|
58
|
-
const n = Number(m[1]);
|
|
59
|
-
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
|
60
|
-
};
|
|
61
|
-
export const resolveFanoutTreeSimParams = (input) => {
|
|
62
|
-
const nodes = Number(input.nodes ?? 2000);
|
|
63
|
-
const bootstraps = Number(input.bootstraps ?? 1);
|
|
64
|
-
const subscribersDefault = Math.max(0, nodes - 1 - bootstraps);
|
|
65
|
-
const msgRate = Number(input.msgRate ?? 30);
|
|
66
|
-
const intervalMsRaw = Number(input.intervalMs ?? 0);
|
|
67
|
-
const intervalMs = intervalMsRaw > 0
|
|
68
|
-
? intervalMsRaw
|
|
69
|
-
: msgRate > 0
|
|
70
|
-
? Math.floor(1000 / msgRate)
|
|
71
|
-
: 0;
|
|
72
|
-
const parentUpgradeMaxChildLoadRatio = Number(input.parentUpgradeMaxChildLoadRatio ?? 0.5);
|
|
73
|
-
const parentUpgradeRootMaxChildLoadRatio = Number(input.parentUpgradeRootMaxChildLoadRatio ??
|
|
74
|
-
Math.min(parentUpgradeMaxChildLoadRatio, 0.4));
|
|
75
|
-
const parentUpgradeMode = input.parentUpgradeMode === "probe" || input.parentUpgradeMode === "shadow"
|
|
76
|
-
? input.parentUpgradeMode
|
|
77
|
-
: input.parentUpgradeMode === "direct"
|
|
78
|
-
? "direct"
|
|
79
|
-
: "shadow";
|
|
80
|
-
return {
|
|
81
|
-
nodes,
|
|
82
|
-
rootIndex: Number(input.rootIndex ?? 0),
|
|
83
|
-
bootstraps,
|
|
84
|
-
bootstrapMaxPeers: Number(input.bootstrapMaxPeers ?? 0),
|
|
85
|
-
subscribers: Number(input.subscribers ?? subscribersDefault),
|
|
86
|
-
relayFraction: Number(input.relayFraction ?? 0.25),
|
|
87
|
-
messages: Number(input.messages ?? 200),
|
|
88
|
-
secondBatchMessages: Number(input.secondBatchMessages ?? 0),
|
|
89
|
-
secondBatchSettleMs: Number(input.secondBatchSettleMs ?? 0),
|
|
90
|
-
msgRate,
|
|
91
|
-
msgSize: Number(input.msgSize ?? 1024),
|
|
92
|
-
intervalMs,
|
|
93
|
-
settleMs: Number(input.settleMs ?? 2_000),
|
|
94
|
-
deadlineMs: Number(input.deadlineMs ?? 0),
|
|
95
|
-
maxDataAgeMs: Number(input.maxDataAgeMs ?? 0),
|
|
96
|
-
timeoutMs: Number(input.timeoutMs ?? 300_000),
|
|
97
|
-
seed: Number(input.seed ?? 1),
|
|
98
|
-
topic: String(input.topic ?? "concert"),
|
|
99
|
-
rootUploadLimitBps: Number(input.rootUploadLimitBps ?? 20_000_000),
|
|
100
|
-
rootMaxChildren: Number(input.rootMaxChildren ?? 64),
|
|
101
|
-
relayUploadLimitBps: Number(input.relayUploadLimitBps ?? 10_000_000),
|
|
102
|
-
relayMaxChildren: Number(input.relayMaxChildren ?? 32),
|
|
103
|
-
allowKick: Boolean(input.allowKick ?? false),
|
|
104
|
-
bidPerByte: Number(input.bidPerByte ?? 0),
|
|
105
|
-
bidPerByteRelay: Number(input.bidPerByteRelay ?? input.bidPerByte ?? 0),
|
|
106
|
-
bidPerByteLeaf: Number(input.bidPerByteLeaf ?? input.bidPerByte ?? 0),
|
|
107
|
-
repair: Boolean(input.repair ?? true),
|
|
108
|
-
repairWindowMessages: Number(input.repairWindowMessages ?? 1024),
|
|
109
|
-
repairMaxBackfillMessages: Number(input.repairMaxBackfillMessages ?? -1),
|
|
110
|
-
repairIntervalMs: Number(input.repairIntervalMs ?? 200),
|
|
111
|
-
repairMaxPerReq: Number(input.repairMaxPerReq ?? 64),
|
|
112
|
-
neighborRepair: Boolean(input.neighborRepair ?? false),
|
|
113
|
-
neighborRepairPeers: Number(input.neighborRepairPeers ?? 2),
|
|
114
|
-
neighborMeshPeers: Number(input.neighborMeshPeers ?? -1),
|
|
115
|
-
neighborAnnounceIntervalMs: Number(input.neighborAnnounceIntervalMs ?? -1),
|
|
116
|
-
neighborMeshRefreshIntervalMs: Number(input.neighborMeshRefreshIntervalMs ?? -1),
|
|
117
|
-
neighborHaveTtlMs: Number(input.neighborHaveTtlMs ?? -1),
|
|
118
|
-
neighborRepairBudgetBps: Number(input.neighborRepairBudgetBps ?? -1),
|
|
119
|
-
neighborRepairBurstMs: Number(input.neighborRepairBurstMs ?? -1),
|
|
120
|
-
streamRxDelayMs: Number(input.streamRxDelayMs ?? 0),
|
|
121
|
-
streamHighWaterMarkBytes: Number(input.streamHighWaterMarkBytes ?? 256 * 1024),
|
|
122
|
-
dialDelayMs: Number(input.dialDelayMs ?? 0),
|
|
123
|
-
joinConcurrency: Number(input.joinConcurrency ?? 256),
|
|
124
|
-
joinReqTimeoutMs: Number(input.joinReqTimeoutMs ?? -1),
|
|
125
|
-
candidateShuffleTopK: Number(input.candidateShuffleTopK ?? -1),
|
|
126
|
-
candidateScoringMode: input.candidateScoringMode === "ranked-strict" ||
|
|
127
|
-
input.candidateScoringMode === "weighted" ||
|
|
128
|
-
input.candidateScoringMode === "ranked-shuffle"
|
|
129
|
-
? input.candidateScoringMode
|
|
130
|
-
: "ranked-shuffle",
|
|
131
|
-
parentUpgradeIntervalMs: Number(input.parentUpgradeIntervalMs ?? 0),
|
|
132
|
-
parentUpgradeLeafOnly: Boolean(input.parentUpgradeLeafOnly ?? true),
|
|
133
|
-
parentUpgradeMinLevelGain: Number(input.parentUpgradeMinLevelGain ?? 1),
|
|
134
|
-
parentUpgradeRootMinLevelGain: Number(input.parentUpgradeRootMinLevelGain ?? 3),
|
|
135
|
-
parentUpgradeRootMinSubtreeGain: Number(input.parentUpgradeRootMinSubtreeGain ??
|
|
136
|
-
input.parentUpgradeRootMinLevelGain ??
|
|
137
|
-
3),
|
|
138
|
-
parentUpgradeNonRootMinLevelGain: Number(input.parentUpgradeNonRootMinLevelGain ?? 2),
|
|
139
|
-
parentUpgradeMinFreeSlots: Number(input.parentUpgradeMinFreeSlots ?? 8),
|
|
140
|
-
parentUpgradeRootMinFreeSlots: Number(input.parentUpgradeRootMinFreeSlots ??
|
|
141
|
-
input.parentUpgradeMinFreeSlots ??
|
|
142
|
-
8),
|
|
143
|
-
parentUpgradeMaxChildLoadRatio,
|
|
144
|
-
parentUpgradeRootMaxChildLoadRatio,
|
|
145
|
-
parentUpgradeCooldownMs: Number(input.parentUpgradeCooldownMs ?? 5_000),
|
|
146
|
-
parentUpgradeFailedBackoffMinMs: Number(input.parentUpgradeFailedBackoffMinMs ?? 5_000),
|
|
147
|
-
parentUpgradeFailedBackoffMaxMs: Number(input.parentUpgradeFailedBackoffMaxMs ?? 60_000),
|
|
148
|
-
parentUpgradeQuietMs: Number(input.parentUpgradeQuietMs ?? 5_000),
|
|
149
|
-
parentUpgradeRepairQuietMs: Number(input.parentUpgradeRepairQuietMs ?? input.parentUpgradeQuietMs ?? 5_000),
|
|
150
|
-
parentUpgradeMaxPerPeer: Number(input.parentUpgradeMaxPerPeer ?? 2),
|
|
151
|
-
parentUpgradeRepairGuard: Boolean(input.parentUpgradeRepairGuard ?? true),
|
|
152
|
-
parentUpgradeDataGuard: Boolean(input.parentUpgradeDataGuard ?? true),
|
|
153
|
-
parentUpgradeMode,
|
|
154
|
-
parentUpgradeVerifyStaleRootCapacity: Boolean(input.parentUpgradeVerifyStaleRootCapacity ??
|
|
155
|
-
parentUpgradeMode === "shadow"),
|
|
156
|
-
parentUpgradeStaleRootProbeProbability: Number(input.parentUpgradeStaleRootProbeProbability ?? 0.0625),
|
|
157
|
-
parentProbeTimeoutMs: Number(input.parentProbeTimeoutMs ?? 500),
|
|
158
|
-
parentProbeMaxPerRound: Number(input.parentProbeMaxPerRound ?? 2),
|
|
159
|
-
parentProbeMaxLagMessages: Number(input.parentProbeMaxLagMessages ?? 0),
|
|
160
|
-
parentProbeRejectCooldownMs: Number(input.parentProbeRejectCooldownMs ?? 10_000),
|
|
161
|
-
parentProbeRejectCooldownMaxMs: Number(input.parentProbeRejectCooldownMaxMs ?? 60_000),
|
|
162
|
-
parentShadowObserveMs: Number(input.parentShadowObserveMs ?? 2_000),
|
|
163
|
-
parentShadowMinObservations: Number(input.parentShadowMinObservations ?? 2),
|
|
164
|
-
parentShadowDualPathMs: Number(input.parentShadowDualPathMs ??
|
|
165
|
-
(parentUpgradeMode === "shadow" ? 5_000 : 0)),
|
|
166
|
-
parentShadowDualPathMinMessages: Number(input.parentShadowDualPathMinMessages ??
|
|
167
|
-
(parentUpgradeMode === "shadow" ? 32 : 1)),
|
|
168
|
-
bootstrapEnsureIntervalMs: Number(input.bootstrapEnsureIntervalMs ?? -1),
|
|
169
|
-
trackerQueryIntervalMs: Number(input.trackerQueryIntervalMs ?? -1),
|
|
170
|
-
joinAttemptsPerRound: Number(input.joinAttemptsPerRound ?? -1),
|
|
171
|
-
candidateCooldownMs: Number(input.candidateCooldownMs ?? -1),
|
|
172
|
-
joinPhases: Boolean(input.joinPhases ?? false),
|
|
173
|
-
joinPhaseSettleMs: Number(input.joinPhaseSettleMs ?? 2_000),
|
|
174
|
-
lateRootConnectAfterMs: Number(input.lateRootConnectAfterMs ?? -1),
|
|
175
|
-
lateRootDuringPublish: Boolean(input.lateRootDuringPublish ?? false),
|
|
176
|
-
lateRootMaxChildren: Number(input.lateRootMaxChildren ?? 0),
|
|
177
|
-
lateRootConnectFraction: Number(input.lateRootConnectFraction ?? 1),
|
|
178
|
-
maxLatencySamples: Number(input.maxLatencySamples ?? 1_000_000),
|
|
179
|
-
profile: Boolean(input.profile ?? false),
|
|
180
|
-
progress: Boolean(input.progress ?? false),
|
|
181
|
-
progressEveryMs: Number(input.progressEveryMs ?? 5_000),
|
|
182
|
-
dropDataFrameRate: Number(input.dropDataFrameRate ?? 0),
|
|
183
|
-
churnEveryMs: Number(input.churnEveryMs ?? 0),
|
|
184
|
-
churnDownMs: Number(input.churnDownMs ?? 0),
|
|
185
|
-
churnFraction: Number(input.churnFraction ?? 0),
|
|
186
|
-
assertMinJoinedPct: Number(input.assertMinJoinedPct ?? 0),
|
|
187
|
-
assertMinDeliveryPct: Number(input.assertMinDeliveryPct ?? 0),
|
|
188
|
-
assertMinDeadlineDeliveryPct: Number(input.assertMinDeadlineDeliveryPct ?? 0),
|
|
189
|
-
assertMaxUploadFracPct: Number(input.assertMaxUploadFracPct ?? 0),
|
|
190
|
-
assertMaxOverheadFactor: Number(input.assertMaxOverheadFactor ?? 0),
|
|
191
|
-
assertMaxControlBpp: Number(input.assertMaxControlBpp ?? 0),
|
|
192
|
-
assertMaxTrackerBpp: Number(input.assertMaxTrackerBpp ?? 0),
|
|
193
|
-
assertMaxTrackerBytes: Number(input.assertMaxTrackerBytes ?? 0),
|
|
194
|
-
assertMaxRepairBpp: Number(input.assertMaxRepairBpp ?? 0),
|
|
195
|
-
assertAttachP95Ms: Number(input.assertAttachP95Ms ?? 0),
|
|
196
|
-
assertMaxTreeLevelP95: Number(input.assertMaxTreeLevelP95 ?? 0),
|
|
197
|
-
assertMaxFormationScore: Number(input.assertMaxFormationScore ?? 0),
|
|
198
|
-
assertMinReparentUpgradeTotal: Number(input.assertMinReparentUpgradeTotal ?? 0),
|
|
199
|
-
assertMinActiveShadowPromoteTotal: Number(input.assertMinActiveShadowPromoteTotal ?? 0),
|
|
200
|
-
assertMaxOrphans: Number(input.assertMaxOrphans ?? 0),
|
|
201
|
-
assertRecoveryP95Ms: Number(input.assertRecoveryP95Ms ?? 0),
|
|
202
|
-
assertMaxReparentsPerMin: Number(input.assertMaxReparentsPerMin ?? 0),
|
|
203
|
-
assertMaxOrphanArea: Number(input.assertMaxOrphanArea ?? 0),
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
export const formatFanoutTreeSimResult = (r) => {
|
|
207
|
-
const p = r.params;
|
|
208
|
-
return [
|
|
209
|
-
"fanout-tree-sim",
|
|
210
|
-
`nodes=${p.nodes} bootstraps=${r.bootstrapCount} bootstrapMaxPeers=${p.bootstrapMaxPeers} subscribers=${r.subscriberCount} relays=${r.relayCount}`,
|
|
211
|
-
...(p.lateRootConnectAfterMs >= 0
|
|
212
|
-
? [
|
|
213
|
-
`lateRoot: afterMs=${p.lateRootConnectAfterMs} maxChildren=${p.lateRootMaxChildren} connectFraction=${p.lateRootConnectFraction}`,
|
|
214
|
-
]
|
|
215
|
-
: []),
|
|
216
|
-
`joined=${r.joinedCount}/${r.subscriberCount} (${r.joinedPct.toFixed(2)}%)`,
|
|
217
|
-
`join: ${(r.joinMs / 1000).toFixed(3)}s`,
|
|
218
|
-
`attachMs samples=${r.attachSamples} p50=${r.attachP50.toFixed(1)} p95=${r.attachP95.toFixed(1)} p99=${r.attachP99.toFixed(1)} max=${r.attachMax.toFixed(1)}`,
|
|
219
|
-
`formationPaths: underlayEdges=${r.formationUnderlayEdges} dist(p95/max)=${r.formationUnderlayDistP95.toFixed(1)}/${r.formationUnderlayDistMax.toFixed(1)} stretch(p95/max)=${r.formationStretchP95.toFixed(2)}/${r.formationStretchMax.toFixed(2)} score=${r.formationScore.toFixed(2)}`,
|
|
220
|
-
`formationTree: maxLevel=${r.formationTreeMaxLevel} p95Level=${r.formationTreeLevelP95.toFixed(1)} avgLevel=${r.formationTreeLevelAvg.toFixed(2)} orphans=${r.formationTreeOrphans} rootChildren=${r.formationTreeRootChildren} children(p95/max)=${r.formationTreeChildrenP95.toFixed(1)}/${r.formationTreeChildrenMax}`,
|
|
221
|
-
`publish: ${(r.publishMs / 1000).toFixed(3)}s intervalMs=${p.intervalMs}`,
|
|
222
|
-
...(p.secondBatchMessages > 0
|
|
223
|
-
? [
|
|
224
|
-
`secondBatch: messages=${p.secondBatchMessages} settleMs=${p.secondBatchSettleMs} delivered=${r.secondBatchDelivered}/${r.secondBatchExpected} (${r.secondBatchDeliveredPct.toFixed(2)}%) deadline=${r.secondBatchDeliveredWithinDeadline}/${r.secondBatchExpected} (${r.secondBatchDeliveredWithinDeadlinePct.toFixed(2)}%) latencyP95=${r.secondBatchLatencyP95.toFixed(1)}`,
|
|
225
|
-
]
|
|
226
|
-
: []),
|
|
227
|
-
`churn: everyMs=${p.churnEveryMs} downMs=${p.churnDownMs} fraction=${p.churnFraction} events=${r.churnEvents} peers=${r.churnedPeersTotal}`,
|
|
228
|
-
...(r.maintSamples > 0
|
|
229
|
-
? [
|
|
230
|
-
`maintenance: maxOrphans=${r.maintMaxOrphans} orphanArea=${r.maintOrphanArea.toFixed(1)}s recoveryMs p50=${r.maintRecoveryP50Ms.toFixed(1)} p95=${r.maintRecoveryP95Ms.toFixed(1)} reparentsPerMin=${r.maintReparentsPerMin.toFixed(2)} flapMax=${r.maintMaxReparentsPerPeer} driftP95(level/children)=${r.maintLevelP95DriftMax.toFixed(1)}/${r.maintChildrenP95DriftMax.toFixed(1)}`,
|
|
231
|
-
]
|
|
232
|
-
: []),
|
|
233
|
-
`delivered=${r.delivered}/${r.expected} (${r.deliveredPct.toFixed(2)}%) dup=${r.duplicates}`,
|
|
234
|
-
p.deadlineMs > 0
|
|
235
|
-
? `deadline=${p.deadlineMs}ms${p.maxDataAgeMs > 0 ? ` maxAgeMs=${p.maxDataAgeMs}` : ""} delivered=${r.deliveredWithinDeadline}/${r.expected} (${r.deliveredWithinDeadlinePct.toFixed(2)}%)`
|
|
236
|
-
: `deadline=off${p.maxDataAgeMs > 0 ? ` maxAgeMs=${p.maxDataAgeMs}` : ""}`,
|
|
237
|
-
`latencyMs p50=${r.latencyP50.toFixed(1)} p95=${r.latencyP95.toFixed(1)} p99=${r.latencyP99.toFixed(1)} max=${r.latencyMax.toFixed(1)}`,
|
|
238
|
-
`drops: forward total=${r.droppedForwardsTotal} max=${r.droppedForwardsMax} node=${r.droppedForwardsMaxNode ?? "-"} stale total=${r.staleForwardsDroppedTotal} max=${r.staleForwardsDroppedMax} node=${r.staleForwardsDroppedMaxNode ?? "-"} write total=${r.dataWriteDropsTotal} max=${r.dataWriteDropsMax} node=${r.dataWriteDropsMaxNode ?? "-"}`,
|
|
239
|
-
...(p.lateRootDuringPublish ||
|
|
240
|
-
r.publishActiveReparentUpgradeSkipDataTotal > 0
|
|
241
|
-
? [
|
|
242
|
-
`publishActiveParentUpgrade: upgrade=${r.publishActiveReparentUpgradeTotal} dataSkips=${r.publishActiveReparentUpgradeSkipDataTotal} repairSkips=${r.publishActiveReparentUpgradeSkipRepairTotal} quietSkips=${r.publishActiveReparentUpgradeSkipQuietTotal} probes=${r.publishActiveParentProbeReqSentTotal} shadowStart=${r.publishActiveParentShadowStartTotal} shadowPromote=${r.publishActiveParentShadowPromoteTotal}`,
|
|
243
|
-
]
|
|
244
|
-
: []),
|
|
245
|
-
`reparent: disconnect=${r.reparentDisconnectTotal} stale=${r.reparentStaleTotal} kicked=${r.reparentKickedTotal} upgrade=${r.reparentUpgradeTotal}`,
|
|
246
|
-
`reparentUpgradeSkipped: leaf=${r.reparentUpgradeSkipLeafTotal} repair=${r.reparentUpgradeSkipRepairTotal} data=${r.reparentUpgradeSkipDataTotal} cooldown=${r.reparentUpgradeSkipCooldownTotal} quiet=${r.reparentUpgradeSkipQuietTotal} budget=${r.reparentUpgradeSkipBudgetTotal} candidateLevel=${r.reparentUpgradeSkipCandidateLevelTotal} candidateSlots=${r.reparentUpgradeSkipCandidateSlotsTotal} candidatePressure=${r.reparentUpgradeSkipCandidatePressureTotal} rootPressure=${r.reparentUpgradeSkipRootPressureTotal}`,
|
|
247
|
-
`parentProbe: req=${r.parentProbeReqSentTotal}/${r.parentProbeReqReceivedTotal} reply=${r.parentProbeReplySentTotal}/${r.parentProbeReplyReceivedTotal} skipped noReply=${r.reparentUpgradeSkipProbeNoReplyTotal} notRooted=${r.reparentUpgradeSkipProbeNotRootedTotal} repair=${r.reparentUpgradeSkipProbeRepairTotal} lag=${r.reparentUpgradeSkipProbeLagTotal} overloaded=${r.reparentUpgradeSkipProbeOverloadedTotal} cooldown=${r.reparentUpgradeSkipProbeCooldownTotal}`,
|
|
248
|
-
`parentRootReservations: created=${r.parentUpgradeRootReservationCreatedTotal} consumed=${r.parentUpgradeRootReservationConsumedTotal} rejected=${r.parentUpgradeRootReservationRejectedTotal} marginRejected=${r.parentUpgradeRootReservationMarginRejectedTotal} blocked=${r.parentUpgradeRootReservationBlockedTotal} expired=${r.parentUpgradeRootReservationExpiredTotal}`,
|
|
249
|
-
`parentShadow: start=${r.parentShadowStartTotal} observe=${r.parentShadowObserveTotal} promote=${r.parentShadowPromoteTotal} reset=${r.parentShadowResetTotal} reject noReply=${r.parentShadowRejectNoReplyTotal} notRooted=${r.parentShadowRejectNotRootedTotal} capacity=${r.parentShadowRejectCapacityTotal} repair=${r.parentShadowRejectRepairTotal} lag=${r.parentShadowRejectLagTotal} overloaded=${r.parentShadowRejectOverloadedTotal} level=${r.parentShadowRejectLevelTotal}`,
|
|
250
|
-
`tree: maxLevel=${r.treeMaxLevel} p95Level=${r.treeLevelP95.toFixed(1)} avgLevel=${r.treeLevelAvg.toFixed(2)} orphans=${r.treeOrphans} rootChildren=${r.treeRootChildren} children(p95/max)=${r.treeChildrenP95.toFixed(1)}/${r.treeChildrenMax}`,
|
|
251
|
-
`upload: max=${r.maxUploadBps} B/s (${r.maxUploadFracPct.toFixed(1)}% of cap) node=${r.maxUploadNode ?? "-"} root=${r.rootUploadBps} B/s (${r.rootUploadFracPct.toFixed(1)}% of cap)`,
|
|
252
|
-
`stream: queuedBytes total=${r.streamQueuedBytesTotal} max=${r.streamQueuedBytesMax} p95=${r.streamQueuedBytesP95.toFixed(0)} node=${r.streamQueuedBytesMaxNode ?? "-"} lanes=${r.streamQueuedBytesByLane.join(",")}`,
|
|
253
|
-
`overhead: dataFactor=${r.overheadFactorData.toFixed(3)} (sentPayloadBytes / ideal)`,
|
|
254
|
-
`economics: earningsTotal=${r.earningsTotal} relayCount=${r.earningsRelayCount} p50=${r.earningsRelayP50} p95=${r.earningsRelayP95} max=${r.earningsRelayMax}`,
|
|
255
|
-
`protocol: controlBytesSent=${r.protocolControlBytesSent} (join=${r.protocolControlBytesSentJoin} tracker=${r.protocolControlBytesSentTracker} repair=${r.protocolControlBytesSentRepair}) bpp=${r.controlBpp.toFixed(4)} (tracker=${r.trackerBpp.toFixed(4)} repair=${r.repairBpp.toFixed(4)}) dataPayloadBytesSent=${r.protocolDataPayloadBytesSent} fetchReqSent=${r.protocolFetchReqSent} ihaveSent=${r.protocolIHaveSent} tracker(a/q/r/f)=${r.protocolTrackerAnnounceSent}/${r.protocolTrackerQuerySent}/${r.protocolTrackerReplySent}/${r.protocolTrackerFeedbackSent} holeFills=${r.protocolHoleFillsFromNeighbor} routeCache(h/m/x/e)=${r.protocolRouteCacheHits}/${r.protocolRouteCacheMisses}/${r.protocolRouteCacheExpirations}/${r.protocolRouteCacheEvictions} routeProxy(q/t/f)=${r.protocolRouteProxyQueries}/${r.protocolRouteProxyTimeouts}/${r.protocolRouteProxyFanout}`,
|
|
256
|
-
`network: dials=${r.network.dials} connsOpened=${r.network.connectionsOpened} streamsOpened=${r.network.streamsOpened} framesSent=${r.network.framesSent} bytesSent=${r.network.bytesSent} framesDropped=${r.network.framesDropped} bytesDropped=${r.network.bytesDropped}`,
|
|
257
|
-
...(r.profile
|
|
258
|
-
? [
|
|
259
|
-
`profile: cpuUserMs=${r.profile.cpuUserMs.toFixed(1)} cpuSystemMs=${r.profile.cpuSystemMs.toFixed(1)} rssMb=${r.profile.rssMb.toFixed(1)} heapUsedMb=${r.profile.heapUsedMb.toFixed(1)} eldP95Ms=${r.profile.eventLoopDelayP95Ms.toFixed(2)} eldMaxMs=${r.profile.eventLoopDelayMaxMs.toFixed(2)}`,
|
|
260
|
-
]
|
|
261
|
-
: []),
|
|
262
|
-
].join("\n");
|
|
263
|
-
};
|
|
264
|
-
export const runFanoutTreeSim = async (input) => {
|
|
265
|
-
const params = resolveFanoutTreeSimParams(input);
|
|
266
|
-
const timeoutMs = Math.max(0, Math.floor(params.timeoutMs));
|
|
267
|
-
const rng = mulberry32(params.seed);
|
|
268
|
-
const profileEnabled = params.profile === true;
|
|
269
|
-
let profileCpuStart;
|
|
270
|
-
let profileEld;
|
|
271
|
-
if (profileEnabled) {
|
|
272
|
-
profileCpuStart = process.cpuUsage();
|
|
273
|
-
try {
|
|
274
|
-
profileEld = monitorEventLoopDelay({ resolution: 20 });
|
|
275
|
-
profileEld.enable();
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
// ignore
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
const rootIndex = Math.max(0, Math.min(params.nodes - 1, params.rootIndex));
|
|
282
|
-
const bootstrapCount = Math.max(0, Math.min(params.nodes - 1, Math.floor(params.bootstraps)));
|
|
283
|
-
const bootstrapIndices = Array.from({ length: bootstrapCount }, (_, i) => {
|
|
284
|
-
const idx = i + 1;
|
|
285
|
-
return idx >= params.nodes ? 0 : idx;
|
|
286
|
-
}).filter((i) => i !== rootIndex);
|
|
287
|
-
const exclude = new Set([rootIndex, ...bootstrapIndices]);
|
|
288
|
-
const subscriberCount = Math.max(0, Math.min(params.nodes - exclude.size, Math.floor(params.subscribers)));
|
|
289
|
-
const subscriberIndices = pickDistinct(rng, params.nodes, subscriberCount, exclude);
|
|
290
|
-
// Ensure we have at least one relay when scale > root fanout.
|
|
291
|
-
const wantsRelays = subscriberCount > Math.max(0, params.rootMaxChildren);
|
|
292
|
-
let relayTarget = Math.floor(subscriberCount * Math.max(0, Math.min(1, params.relayFraction)));
|
|
293
|
-
if (wantsRelays && relayTarget === 0)
|
|
294
|
-
relayTarget = Math.min(1, subscriberCount);
|
|
295
|
-
const relaySet = new Set();
|
|
296
|
-
const shuffledSubscribers = [...subscriberIndices].sort(() => rng() - 0.5);
|
|
297
|
-
for (const idx of shuffledSubscribers) {
|
|
298
|
-
if (relaySet.size >= relayTarget)
|
|
299
|
-
break;
|
|
300
|
-
relaySet.add(idx);
|
|
301
|
-
}
|
|
302
|
-
const network = new InMemoryNetwork({
|
|
303
|
-
streamRxDelayMs: params.streamRxDelayMs,
|
|
304
|
-
streamHighWaterMarkBytes: params.streamHighWaterMarkBytes,
|
|
305
|
-
dialDelayMs: params.dialDelayMs,
|
|
306
|
-
dropDataFrameRate: params.dropDataFrameRate,
|
|
307
|
-
dropSeed: params.seed,
|
|
308
|
-
});
|
|
309
|
-
const bootstrapIndexSet = new Set(bootstrapIndices);
|
|
310
|
-
// Sim-only connection caps must account for tracker/bootstrap fan-in. Capping those
|
|
311
|
-
// peers near the overlay degree works for small runs, but at 5k+ it starves join
|
|
312
|
-
// formation long before the tree itself is saturated.
|
|
313
|
-
const rootConnectionCap = Math.max(256, params.rootMaxChildren * 8);
|
|
314
|
-
const relayConnectionCap = Math.max(64, params.relayMaxChildren * 4);
|
|
315
|
-
const bootstrapConnectionCap = Math.max(512, Math.min(2_048, Math.ceil((subscriberIndices.length + 1) / Math.max(1, bootstrapIndices.length))));
|
|
316
|
-
const maxConnectionsFor = (index) => {
|
|
317
|
-
if (index === rootIndex)
|
|
318
|
-
return rootConnectionCap;
|
|
319
|
-
if (bootstrapIndexSet.has(index))
|
|
320
|
-
return bootstrapConnectionCap;
|
|
321
|
-
if (relaySet.has(index))
|
|
322
|
-
return relayConnectionCap;
|
|
323
|
-
return 16;
|
|
324
|
-
};
|
|
325
|
-
const seenCacheMaxFor = (index) => {
|
|
326
|
-
if (index === rootIndex)
|
|
327
|
-
return 200_000;
|
|
328
|
-
if (bootstrapIndexSet.has(index))
|
|
329
|
-
return 100_000;
|
|
330
|
-
if (relaySet.has(index))
|
|
331
|
-
return 50_000;
|
|
332
|
-
return 20_000;
|
|
333
|
-
};
|
|
334
|
-
const seenCacheTtlMsFor = (index) => {
|
|
335
|
-
if (index === rootIndex || bootstrapIndexSet.has(index))
|
|
336
|
-
return 120_000;
|
|
337
|
-
return 60_000;
|
|
338
|
-
};
|
|
339
|
-
const session = await InMemorySession.disconnected(params.nodes, {
|
|
340
|
-
network,
|
|
341
|
-
basePort: 30_000,
|
|
342
|
-
services: {
|
|
343
|
-
fanout: (c) => {
|
|
344
|
-
const index = parseSimPeerIndex(c?.peerId);
|
|
345
|
-
return new SimFanoutTree(c, {
|
|
346
|
-
// Keep sims bounded: limit per-node connections to roughly the fanout degree,
|
|
347
|
-
// so large networks can run in a single process without OOM.
|
|
348
|
-
connectionManager: {
|
|
349
|
-
minConnections: 0,
|
|
350
|
-
maxConnections: maxConnectionsFor(index),
|
|
351
|
-
dialer: false,
|
|
352
|
-
pruner: { interval: 1_000 },
|
|
353
|
-
},
|
|
354
|
-
seenCacheMax: seenCacheMaxFor(index),
|
|
355
|
-
seenCacheTtlMs: seenCacheTtlMsFor(index),
|
|
356
|
-
random: mulberry32((params.seed >>> 0) ^ index),
|
|
357
|
-
});
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
});
|
|
361
|
-
let timer;
|
|
362
|
-
const timeoutController = new AbortController();
|
|
363
|
-
const timeoutSignal = timeoutController.signal;
|
|
364
|
-
try {
|
|
365
|
-
if (timeoutMs > 0) {
|
|
366
|
-
timer = setTimeout(() => {
|
|
367
|
-
timeoutController.abort(new Error(`fanout-tree-sim timed out after ${timeoutMs}ms (override with --timeoutMs)`));
|
|
368
|
-
}, timeoutMs);
|
|
369
|
-
}
|
|
370
|
-
const run = async () => {
|
|
371
|
-
const bootstrapAddrs = bootstrapIndices.flatMap((i) => session.peers[i].getMultiaddrs());
|
|
372
|
-
if (bootstrapAddrs.length === 0) {
|
|
373
|
-
throw new Error("No bootstrap addrs; pass --bootstraps >= 1");
|
|
374
|
-
}
|
|
375
|
-
for (const p of session.peers) {
|
|
376
|
-
p.services.fanout.setBootstraps(bootstrapAddrs);
|
|
377
|
-
}
|
|
378
|
-
const root = session.peers[rootIndex].services.fanout;
|
|
379
|
-
const rootId = root.publicKeyHash;
|
|
380
|
-
// Root opens channel and starts announcing capacity to trackers immediately.
|
|
381
|
-
root.openChannel(params.topic, rootId, {
|
|
382
|
-
role: "root",
|
|
383
|
-
msgRate: params.msgRate,
|
|
384
|
-
msgSize: params.msgSize,
|
|
385
|
-
...(params.maxDataAgeMs > 0
|
|
386
|
-
? { maxDataAgeMs: params.maxDataAgeMs }
|
|
387
|
-
: {}),
|
|
388
|
-
uploadLimitBps: params.rootUploadLimitBps,
|
|
389
|
-
maxChildren: params.rootMaxChildren,
|
|
390
|
-
bidPerByte: params.bidPerByte,
|
|
391
|
-
allowKick: params.allowKick,
|
|
392
|
-
repair: params.repair,
|
|
393
|
-
repairWindowMessages: params.repairWindowMessages,
|
|
394
|
-
...(params.repairMaxBackfillMessages >= 0
|
|
395
|
-
? { repairMaxBackfillMessages: params.repairMaxBackfillMessages }
|
|
396
|
-
: {}),
|
|
397
|
-
repairIntervalMs: params.repairIntervalMs,
|
|
398
|
-
repairMaxPerReq: params.repairMaxPerReq,
|
|
399
|
-
neighborRepair: params.neighborRepair,
|
|
400
|
-
neighborRepairPeers: params.neighborRepairPeers,
|
|
401
|
-
...(params.neighborMeshPeers >= 0
|
|
402
|
-
? { neighborMeshPeers: params.neighborMeshPeers }
|
|
403
|
-
: {}),
|
|
404
|
-
...(params.neighborAnnounceIntervalMs >= 0
|
|
405
|
-
? { neighborAnnounceIntervalMs: params.neighborAnnounceIntervalMs }
|
|
406
|
-
: {}),
|
|
407
|
-
...(params.neighborMeshRefreshIntervalMs >= 0
|
|
408
|
-
? {
|
|
409
|
-
neighborMeshRefreshIntervalMs: params.neighborMeshRefreshIntervalMs,
|
|
410
|
-
}
|
|
411
|
-
: {}),
|
|
412
|
-
...(params.neighborHaveTtlMs >= 0
|
|
413
|
-
? { neighborHaveTtlMs: params.neighborHaveTtlMs }
|
|
414
|
-
: {}),
|
|
415
|
-
...(params.neighborRepairBudgetBps >= 0
|
|
416
|
-
? { neighborRepairBudgetBps: params.neighborRepairBudgetBps }
|
|
417
|
-
: {}),
|
|
418
|
-
...(params.neighborRepairBurstMs >= 0
|
|
419
|
-
? { neighborRepairBurstMs: params.neighborRepairBurstMs }
|
|
420
|
-
: {}),
|
|
421
|
-
});
|
|
422
|
-
// Join subscribers (bounded concurrency).
|
|
423
|
-
const joinStart = Date.now();
|
|
424
|
-
const joined = new Array(subscriberIndices.length).fill(false);
|
|
425
|
-
const attachDurationsByPos = new Array(subscriberIndices.length).fill(-1);
|
|
426
|
-
let joinCompleted = 0;
|
|
427
|
-
let joinOk = 0;
|
|
428
|
-
const progressEveryMs = Math.max(250, Math.floor(params.progressEveryMs || 5_000));
|
|
429
|
-
let joinProgressTimer;
|
|
430
|
-
if (params.progress) {
|
|
431
|
-
console.log(`[fanout-tree-sim] phase=join subscribers=${subscriberIndices.length} relays=${relaySet.size} joinConcurrency=${params.joinConcurrency}`);
|
|
432
|
-
joinProgressTimer = setInterval(() => {
|
|
433
|
-
const mu = process.memoryUsage();
|
|
434
|
-
const rssMb = mu.rss / (1024 * 1024);
|
|
435
|
-
const heapUsedMb = mu.heapUsed / (1024 * 1024);
|
|
436
|
-
const openConns = Math.max(0, network.metrics.connectionsOpened -
|
|
437
|
-
network.metrics.connectionsClosed);
|
|
438
|
-
console.log(`[fanout-tree-sim] join progress ok=${joinOk}/${subscriberIndices.length} done=${joinCompleted}/${subscriberIndices.length} openConns=${openConns} dials=${network.metrics.dials} streamsOpened=${network.metrics.streamsOpened} rssMb=${rssMb.toFixed(1)} heapUsedMb=${heapUsedMb.toFixed(1)}`);
|
|
439
|
-
}, progressEveryMs);
|
|
440
|
-
joinProgressTimer.unref?.();
|
|
441
|
-
}
|
|
442
|
-
const joinOne = async (idx) => {
|
|
443
|
-
const node = session.peers[idx].services.fanout;
|
|
444
|
-
const isRelay = relaySet.has(idx);
|
|
445
|
-
try {
|
|
446
|
-
await node.joinChannel(params.topic, rootId, {
|
|
447
|
-
msgRate: params.msgRate,
|
|
448
|
-
msgSize: params.msgSize,
|
|
449
|
-
...(params.maxDataAgeMs > 0
|
|
450
|
-
? { maxDataAgeMs: params.maxDataAgeMs }
|
|
451
|
-
: {}),
|
|
452
|
-
uploadLimitBps: isRelay ? params.relayUploadLimitBps : 0,
|
|
453
|
-
maxChildren: isRelay ? params.relayMaxChildren : 0,
|
|
454
|
-
bidPerByte: isRelay
|
|
455
|
-
? params.bidPerByteRelay
|
|
456
|
-
: params.bidPerByteLeaf,
|
|
457
|
-
allowKick: params.allowKick,
|
|
458
|
-
repair: params.repair,
|
|
459
|
-
repairWindowMessages: params.repairWindowMessages,
|
|
460
|
-
...(params.repairMaxBackfillMessages >= 0
|
|
461
|
-
? {
|
|
462
|
-
repairMaxBackfillMessages: params.repairMaxBackfillMessages,
|
|
463
|
-
}
|
|
464
|
-
: {}),
|
|
465
|
-
repairIntervalMs: params.repairIntervalMs,
|
|
466
|
-
repairMaxPerReq: params.repairMaxPerReq,
|
|
467
|
-
neighborRepair: params.neighborRepair,
|
|
468
|
-
neighborRepairPeers: params.neighborRepairPeers,
|
|
469
|
-
...(params.neighborMeshPeers >= 0
|
|
470
|
-
? { neighborMeshPeers: params.neighborMeshPeers }
|
|
471
|
-
: {}),
|
|
472
|
-
...(params.neighborAnnounceIntervalMs >= 0
|
|
473
|
-
? {
|
|
474
|
-
neighborAnnounceIntervalMs: params.neighborAnnounceIntervalMs,
|
|
475
|
-
}
|
|
476
|
-
: {}),
|
|
477
|
-
...(params.neighborMeshRefreshIntervalMs >= 0
|
|
478
|
-
? {
|
|
479
|
-
neighborMeshRefreshIntervalMs: params.neighborMeshRefreshIntervalMs,
|
|
480
|
-
}
|
|
481
|
-
: {}),
|
|
482
|
-
...(params.neighborHaveTtlMs >= 0
|
|
483
|
-
? { neighborHaveTtlMs: params.neighborHaveTtlMs }
|
|
484
|
-
: {}),
|
|
485
|
-
...(params.neighborRepairBudgetBps >= 0
|
|
486
|
-
? { neighborRepairBudgetBps: params.neighborRepairBudgetBps }
|
|
487
|
-
: {}),
|
|
488
|
-
...(params.neighborRepairBurstMs >= 0
|
|
489
|
-
? { neighborRepairBurstMs: params.neighborRepairBurstMs }
|
|
490
|
-
: {}),
|
|
491
|
-
}, {
|
|
492
|
-
timeoutMs: Math.max(10_000, Math.min(120_000, timeoutMs || 120_000)),
|
|
493
|
-
...(params.maxDataAgeMs > 0
|
|
494
|
-
? { staleAfterMs: params.maxDataAgeMs }
|
|
495
|
-
: {}),
|
|
496
|
-
...(params.joinReqTimeoutMs >= 0
|
|
497
|
-
? { joinReqTimeoutMs: params.joinReqTimeoutMs }
|
|
498
|
-
: {}),
|
|
499
|
-
...(params.candidateShuffleTopK >= 0
|
|
500
|
-
? { candidateShuffleTopK: params.candidateShuffleTopK }
|
|
501
|
-
: {}),
|
|
502
|
-
candidateScoringMode: params.candidateScoringMode,
|
|
503
|
-
...parentUpgradeRuntimeOptions(params),
|
|
504
|
-
...(params.bootstrapEnsureIntervalMs >= 0
|
|
505
|
-
? {
|
|
506
|
-
bootstrapEnsureIntervalMs: params.bootstrapEnsureIntervalMs,
|
|
507
|
-
}
|
|
508
|
-
: {}),
|
|
509
|
-
...(params.trackerQueryIntervalMs >= 0
|
|
510
|
-
? { trackerQueryIntervalMs: params.trackerQueryIntervalMs }
|
|
511
|
-
: {}),
|
|
512
|
-
...(params.joinAttemptsPerRound >= 0
|
|
513
|
-
? { joinAttemptsPerRound: params.joinAttemptsPerRound }
|
|
514
|
-
: {}),
|
|
515
|
-
...(params.candidateCooldownMs >= 0
|
|
516
|
-
? { candidateCooldownMs: params.candidateCooldownMs }
|
|
517
|
-
: {}),
|
|
518
|
-
signal: timeoutSignal,
|
|
519
|
-
bootstrapMaxPeers: params.bootstrapMaxPeers,
|
|
520
|
-
});
|
|
521
|
-
return true;
|
|
522
|
-
}
|
|
523
|
-
catch {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
const runPhase = async (indices) => {
|
|
528
|
-
const tasks = indices.map((pos) => async () => {
|
|
529
|
-
const ok = await joinOne(subscriberIndices[pos]);
|
|
530
|
-
joined[pos] = ok;
|
|
531
|
-
if (ok) {
|
|
532
|
-
joinOk += 1;
|
|
533
|
-
attachDurationsByPos[pos] = Date.now() - joinStart;
|
|
534
|
-
}
|
|
535
|
-
joinCompleted += 1;
|
|
536
|
-
return ok;
|
|
537
|
-
});
|
|
538
|
-
await runWithConcurrency(tasks, params.joinConcurrency);
|
|
539
|
-
};
|
|
540
|
-
try {
|
|
541
|
-
if (params.joinPhases) {
|
|
542
|
-
const relayPositions = [];
|
|
543
|
-
const leafPositions = [];
|
|
544
|
-
for (let i = 0; i < subscriberIndices.length; i++) {
|
|
545
|
-
const idx = subscriberIndices[i];
|
|
546
|
-
if (relaySet.has(idx))
|
|
547
|
-
relayPositions.push(i);
|
|
548
|
-
else
|
|
549
|
-
leafPositions.push(i);
|
|
550
|
-
}
|
|
551
|
-
await runPhase(relayPositions);
|
|
552
|
-
const settleMs = Math.max(0, Math.floor(params.joinPhaseSettleMs));
|
|
553
|
-
if (settleMs > 0) {
|
|
554
|
-
await delay(settleMs, { signal: timeoutSignal });
|
|
555
|
-
}
|
|
556
|
-
await runPhase(leafPositions);
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
await runPhase(subscriberIndices.map((_, i) => i));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
finally {
|
|
563
|
-
if (joinProgressTimer)
|
|
564
|
-
clearInterval(joinProgressTimer);
|
|
565
|
-
}
|
|
566
|
-
const joinDone = Date.now();
|
|
567
|
-
if (params.progress) {
|
|
568
|
-
const mu = process.memoryUsage();
|
|
569
|
-
const rssMb = mu.rss / (1024 * 1024);
|
|
570
|
-
const heapUsedMb = mu.heapUsed / (1024 * 1024);
|
|
571
|
-
const openConns = Math.max(0, network.metrics.connectionsOpened - network.metrics.connectionsClosed);
|
|
572
|
-
console.log(`[fanout-tree-sim] phase=join_done ok=${joinOk}/${subscriberIndices.length} openConns=${openConns} rssMb=${rssMb.toFixed(1)} heapUsedMb=${heapUsedMb.toFixed(1)} joinMs=${joinDone - joinStart}`);
|
|
573
|
-
}
|
|
574
|
-
const attachDurations = attachDurationsByPos
|
|
575
|
-
.filter((d) => d >= 0)
|
|
576
|
-
.sort((a, b) => a - b);
|
|
577
|
-
const attachSamples = attachDurations.length;
|
|
578
|
-
const attachP50 = attachSamples > 0 ? quantile(attachDurations, 0.5) : NaN;
|
|
579
|
-
const attachP95 = attachSamples > 0 ? quantile(attachDurations, 0.95) : NaN;
|
|
580
|
-
const attachP99 = attachSamples > 0 ? quantile(attachDurations, 0.99) : NaN;
|
|
581
|
-
const attachMax = attachSamples > 0 ? attachDurations[attachSamples - 1] : NaN;
|
|
582
|
-
const joinedHashes = new Set(subscriberIndices
|
|
583
|
-
.filter((_, i) => joined[i])
|
|
584
|
-
.map((i) => session.peers[i].services.fanout.publicKeyHash));
|
|
585
|
-
const joinedCount = joinedHashes.size;
|
|
586
|
-
const joinedSubscriberIndices = subscriberIndices.filter((_, i) => joined[i]);
|
|
587
|
-
const computeTreeShapeStats = () => {
|
|
588
|
-
const levels = [];
|
|
589
|
-
const levelByIndex = new Array(params.nodes).fill(NaN);
|
|
590
|
-
const childrenByHash = new Map();
|
|
591
|
-
const childrenCounts = [];
|
|
592
|
-
let treeOrphans = 0;
|
|
593
|
-
let treeRootChildren = 0;
|
|
594
|
-
for (let i = 0; i < session.peers.length; i++) {
|
|
595
|
-
const hash = session.peers[i].services.fanout.publicKeyHash;
|
|
596
|
-
const s = session.peers[i].services.fanout.getChannelStats(params.topic, rootId);
|
|
597
|
-
if (!s)
|
|
598
|
-
continue;
|
|
599
|
-
if (!childrenByHash.has(hash))
|
|
600
|
-
childrenByHash.set(hash, []);
|
|
601
|
-
if (Number.isFinite(s.level)) {
|
|
602
|
-
levels.push(s.level);
|
|
603
|
-
levelByIndex[i] = s.level;
|
|
604
|
-
}
|
|
605
|
-
if (s.effectiveMaxChildren > 0) {
|
|
606
|
-
childrenCounts.push(s.children);
|
|
607
|
-
}
|
|
608
|
-
if (s.parent) {
|
|
609
|
-
const children = childrenByHash.get(s.parent) ?? [];
|
|
610
|
-
children.push(hash);
|
|
611
|
-
childrenByHash.set(s.parent, children);
|
|
612
|
-
}
|
|
613
|
-
if (s.level === 0) {
|
|
614
|
-
treeRootChildren = s.children;
|
|
615
|
-
}
|
|
616
|
-
else if (Number.isFinite(s.level) && !s.parent) {
|
|
617
|
-
treeOrphans += 1;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
levels.sort((a, b) => a - b);
|
|
621
|
-
childrenCounts.sort((a, b) => a - b);
|
|
622
|
-
const treeMaxLevel = levels.length > 0 ? levels[levels.length - 1] : 0;
|
|
623
|
-
const treeLevelP95 = levels.length > 0 ? quantile(levels, 0.95) : 0;
|
|
624
|
-
const treeLevelAvg = levels.length > 0
|
|
625
|
-
? levels.reduce((a, b) => a + b, 0) / levels.length
|
|
626
|
-
: 0;
|
|
627
|
-
const treeChildrenP95 = childrenCounts.length > 0 ? quantile(childrenCounts, 0.95) : 0;
|
|
628
|
-
const treeChildrenMax = childrenCounts.length > 0
|
|
629
|
-
? childrenCounts[childrenCounts.length - 1]
|
|
630
|
-
: 0;
|
|
631
|
-
return {
|
|
632
|
-
treeMaxLevel,
|
|
633
|
-
treeLevelP95,
|
|
634
|
-
treeLevelAvg,
|
|
635
|
-
treeOrphans,
|
|
636
|
-
treeChildrenP95,
|
|
637
|
-
treeChildrenMax,
|
|
638
|
-
treeRootChildren,
|
|
639
|
-
levelByIndex,
|
|
640
|
-
childrenByHash,
|
|
641
|
-
};
|
|
642
|
-
};
|
|
643
|
-
const formationTree = computeTreeShapeStats();
|
|
644
|
-
// Underlay (libp2p connection graph) shortest paths, used to spot wasted
|
|
645
|
-
// open connections that don't contribute to the overlay tree.
|
|
646
|
-
const underlayAdj = Array.from({ length: params.nodes }, () => new Set());
|
|
647
|
-
for (let i = 0; i < session.peers.length; i++) {
|
|
648
|
-
for (const c of session.peers[i].getConnections()) {
|
|
649
|
-
// @ts-ignore - bench shim uses the same field name as real libp2p connections
|
|
650
|
-
if (c.status && c.status !== "open")
|
|
651
|
-
continue;
|
|
652
|
-
const j = parseSimPeerIndex(c.remotePeer);
|
|
653
|
-
if (j === i)
|
|
654
|
-
continue;
|
|
655
|
-
if (j < 0 || j >= params.nodes)
|
|
656
|
-
continue;
|
|
657
|
-
underlayAdj[i].add(j);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
let formationUnderlayEdges = 0;
|
|
661
|
-
for (const s of underlayAdj)
|
|
662
|
-
formationUnderlayEdges += s.size;
|
|
663
|
-
formationUnderlayEdges = Math.floor(formationUnderlayEdges / 2);
|
|
664
|
-
const underlayDist = new Array(params.nodes).fill(Infinity);
|
|
665
|
-
const q = [];
|
|
666
|
-
underlayDist[rootIndex] = 0;
|
|
667
|
-
q.push(rootIndex);
|
|
668
|
-
for (let qi = 0; qi < q.length; qi++) {
|
|
669
|
-
const u = q[qi];
|
|
670
|
-
const du = underlayDist[u];
|
|
671
|
-
for (const v of underlayAdj[u]) {
|
|
672
|
-
if (underlayDist[v] !== Infinity)
|
|
673
|
-
continue;
|
|
674
|
-
underlayDist[v] = du + 1;
|
|
675
|
-
q.push(v);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
const formationUnderlayDists = joinedSubscriberIndices
|
|
679
|
-
.map((i) => underlayDist[i])
|
|
680
|
-
.filter((d) => Number.isFinite(d))
|
|
681
|
-
.sort((a, b) => a - b);
|
|
682
|
-
const formationUnderlayDistP95 = formationUnderlayDists.length > 0
|
|
683
|
-
? quantile(formationUnderlayDists, 0.95)
|
|
684
|
-
: NaN;
|
|
685
|
-
const formationUnderlayDistMax = formationUnderlayDists.length > 0
|
|
686
|
-
? formationUnderlayDists[formationUnderlayDists.length - 1]
|
|
687
|
-
: NaN;
|
|
688
|
-
const formationStretches = joinedSubscriberIndices
|
|
689
|
-
.map((i) => {
|
|
690
|
-
const overlay = formationTree.levelByIndex[i];
|
|
691
|
-
const under = underlayDist[i];
|
|
692
|
-
if (!Number.isFinite(overlay) ||
|
|
693
|
-
!Number.isFinite(under) ||
|
|
694
|
-
under <= 0)
|
|
695
|
-
return NaN;
|
|
696
|
-
return overlay / under;
|
|
697
|
-
})
|
|
698
|
-
.filter((x) => Number.isFinite(x))
|
|
699
|
-
.sort((a, b) => a - b);
|
|
700
|
-
const formationStretchP95 = formationStretches.length > 0
|
|
701
|
-
? quantile(formationStretches, 0.95)
|
|
702
|
-
: NaN;
|
|
703
|
-
const formationStretchMax = formationStretches.length > 0
|
|
704
|
-
? formationStretches[formationStretches.length - 1]
|
|
705
|
-
: NaN;
|
|
706
|
-
const formationOrphanPct = joinedCount === 0 ? 0 : (100 * formationTree.treeOrphans) / joinedCount;
|
|
707
|
-
const formationStretchPenalty = Number.isFinite(formationStretchP95)
|
|
708
|
-
? Math.max(0, formationStretchP95 - 1) * 10
|
|
709
|
-
: 0;
|
|
710
|
-
const formationScore = (Number.isFinite(attachP95) ? attachP95 / 1000 : 0) +
|
|
711
|
-
formationTree.treeLevelP95 +
|
|
712
|
-
formationOrphanPct +
|
|
713
|
-
formationStretchPenalty;
|
|
714
|
-
const churnController = new AbortController();
|
|
715
|
-
const churnSignal = anySignal([
|
|
716
|
-
timeoutSignal,
|
|
717
|
-
churnController.signal,
|
|
718
|
-
]);
|
|
719
|
-
let churnEvents = 0;
|
|
720
|
-
let churnedPeersTotal = 0;
|
|
721
|
-
const wantsMaintenance = (params.churnEveryMs > 0 &&
|
|
722
|
-
params.churnDownMs > 0 &&
|
|
723
|
-
params.churnFraction > 0) ||
|
|
724
|
-
params.parentUpgradeIntervalMs > 0 ||
|
|
725
|
-
params.assertMaxOrphans > 0 ||
|
|
726
|
-
params.assertMaxOrphanArea > 0 ||
|
|
727
|
-
params.assertRecoveryP95Ms > 0 ||
|
|
728
|
-
params.assertMaxReparentsPerMin > 0;
|
|
729
|
-
const maintenanceController = new AbortController();
|
|
730
|
-
const maintenanceSignal = anySignal([
|
|
731
|
-
timeoutSignal,
|
|
732
|
-
maintenanceController.signal,
|
|
733
|
-
]);
|
|
734
|
-
let maintDurationMs = 0;
|
|
735
|
-
let maintSamples = 0;
|
|
736
|
-
let maintMaxOrphans = 0;
|
|
737
|
-
let maintOrphanAreaMs = 0;
|
|
738
|
-
const pendingRecoveryStarts = [];
|
|
739
|
-
const recoveryDurations = [];
|
|
740
|
-
let maintLevelP95DriftMax = 0;
|
|
741
|
-
let maintChildrenP95DriftMax = 0;
|
|
742
|
-
const reparentBaselineByHash = new Map();
|
|
743
|
-
let maintReparentsTotal = 0;
|
|
744
|
-
let maintMaxReparentsPerPeer = 0;
|
|
745
|
-
// Delivery tracking
|
|
746
|
-
const firstBatchMessages = Math.max(0, Math.floor(params.messages));
|
|
747
|
-
const secondBatchMessages = Math.max(0, Math.floor(params.secondBatchMessages));
|
|
748
|
-
const totalMessages = firstBatchMessages + secondBatchMessages;
|
|
749
|
-
const secondBatchStartSeq = firstBatchMessages;
|
|
750
|
-
const publishAt = new Map();
|
|
751
|
-
const joinedHashList = [...joinedHashes];
|
|
752
|
-
const hashToIndex = new Map();
|
|
753
|
-
for (let i = 0; i < joinedHashList.length; i++) {
|
|
754
|
-
hashToIndex.set(joinedHashList[i], i);
|
|
755
|
-
}
|
|
756
|
-
const bitsetBytes = Math.ceil(totalMessages / 8);
|
|
757
|
-
const receivedBits = joinedHashList.map(() => new Uint8Array(bitsetBytes));
|
|
758
|
-
const receivedCounts = new Uint32Array(joinedHashList.length);
|
|
759
|
-
const secondBatchReceivedCounts = new Uint32Array(joinedHashList.length);
|
|
760
|
-
let duplicates = 0;
|
|
761
|
-
let deliveredWithinDeadline = 0;
|
|
762
|
-
let deliveredSamples = [];
|
|
763
|
-
let secondBatchDeliveredWithinDeadline = 0;
|
|
764
|
-
let secondBatchDeliveredSamples = [];
|
|
765
|
-
const secondBatchDeliveredSamplesByPeer = joinedHashList.map(() => []);
|
|
766
|
-
const sampleCap = Math.max(1, Math.floor(params.maxLatencySamples));
|
|
767
|
-
let sampleSeen = 0;
|
|
768
|
-
let secondBatchSampleSeen = 0;
|
|
769
|
-
const makeOnData = (localHash) => (ev) => {
|
|
770
|
-
const d = ev?.detail;
|
|
771
|
-
if (!d)
|
|
772
|
-
return;
|
|
773
|
-
if (d.topic !== params.topic)
|
|
774
|
-
return;
|
|
775
|
-
if (d.root !== rootId)
|
|
776
|
-
return;
|
|
777
|
-
const seq = d.seq >>> 0;
|
|
778
|
-
const index = hashToIndex.get(localHash);
|
|
779
|
-
if (index == null)
|
|
780
|
-
return; // not joined / not tracked
|
|
781
|
-
if (seq >= totalMessages)
|
|
782
|
-
return;
|
|
783
|
-
const bits = receivedBits[index];
|
|
784
|
-
const byteIndex = seq >>> 3;
|
|
785
|
-
const mask = 1 << (seq & 7);
|
|
786
|
-
if (byteIndex >= bits.length)
|
|
787
|
-
return;
|
|
788
|
-
if ((bits[byteIndex] & mask) !== 0) {
|
|
789
|
-
duplicates += 1;
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
bits[byteIndex] |= mask;
|
|
793
|
-
receivedCounts[index] += 1;
|
|
794
|
-
if (seq >= secondBatchStartSeq) {
|
|
795
|
-
secondBatchReceivedCounts[index] += 1;
|
|
796
|
-
}
|
|
797
|
-
const sentAt = publishAt.get(seq);
|
|
798
|
-
if (sentAt != null) {
|
|
799
|
-
const latency = Date.now() - sentAt;
|
|
800
|
-
if (params.deadlineMs > 0 && latency <= params.deadlineMs) {
|
|
801
|
-
deliveredWithinDeadline += 1;
|
|
802
|
-
}
|
|
803
|
-
sampleSeen += 1;
|
|
804
|
-
if (deliveredSamples.length < sampleCap) {
|
|
805
|
-
deliveredSamples.push(latency);
|
|
806
|
-
}
|
|
807
|
-
else {
|
|
808
|
-
const j = int(rng, sampleSeen);
|
|
809
|
-
if (j < sampleCap)
|
|
810
|
-
deliveredSamples[j] = latency;
|
|
811
|
-
}
|
|
812
|
-
if (seq >= secondBatchStartSeq) {
|
|
813
|
-
if (params.deadlineMs > 0 && latency <= params.deadlineMs) {
|
|
814
|
-
secondBatchDeliveredWithinDeadline += 1;
|
|
815
|
-
}
|
|
816
|
-
secondBatchDeliveredSamplesByPeer[index].push(latency);
|
|
817
|
-
secondBatchSampleSeen += 1;
|
|
818
|
-
if (secondBatchDeliveredSamples.length < sampleCap) {
|
|
819
|
-
secondBatchDeliveredSamples.push(latency);
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
const j = int(rng, secondBatchSampleSeen);
|
|
823
|
-
if (j < sampleCap)
|
|
824
|
-
secondBatchDeliveredSamples[j] = latency;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
for (let i = 0; i < subscriberIndices.length; i++) {
|
|
830
|
-
if (!joined[i])
|
|
831
|
-
continue;
|
|
832
|
-
const idx = subscriberIndices[i];
|
|
833
|
-
const node = session.peers[idx].services.fanout;
|
|
834
|
-
const localHash = node.publicKeyHash;
|
|
835
|
-
node.addEventListener("fanout:data", makeOnData(localHash));
|
|
836
|
-
}
|
|
837
|
-
// Publish
|
|
838
|
-
const payload = new Uint8Array(Math.max(0, params.msgSize));
|
|
839
|
-
for (let i = 0; i < payload.length; i++)
|
|
840
|
-
payload[i] = i & 0xff;
|
|
841
|
-
const quantileFromCounts = (counts, maxValue, total, q) => {
|
|
842
|
-
if (total <= 0)
|
|
843
|
-
return 0;
|
|
844
|
-
const target = Math.min(total - 1, Math.max(0, Math.floor(q * (total - 1))));
|
|
845
|
-
let seen = 0;
|
|
846
|
-
for (let v = 0; v <= maxValue; v++) {
|
|
847
|
-
seen += counts[v] ?? 0;
|
|
848
|
-
if (seen > target)
|
|
849
|
-
return v;
|
|
850
|
-
}
|
|
851
|
-
return maxValue;
|
|
852
|
-
};
|
|
853
|
-
const levelCounts = [];
|
|
854
|
-
const childCounts = [];
|
|
855
|
-
const sampleMaintenance = (now) => {
|
|
856
|
-
let onlineJoined = 0;
|
|
857
|
-
let orphansOnline = 0;
|
|
858
|
-
let levelsTotal = 0;
|
|
859
|
-
let levelsMax = 0;
|
|
860
|
-
levelCounts.fill(0);
|
|
861
|
-
let childrenTotal = 0;
|
|
862
|
-
let childrenMax = 0;
|
|
863
|
-
childCounts.fill(0);
|
|
864
|
-
for (const idx of joinedSubscriberIndices) {
|
|
865
|
-
const peer = session.peers[idx];
|
|
866
|
-
if (network.isPeerOffline(peer.peerId, now))
|
|
867
|
-
continue;
|
|
868
|
-
onlineJoined += 1;
|
|
869
|
-
const s = peer.services.fanout.getChannelStats(params.topic, rootId);
|
|
870
|
-
if (!s) {
|
|
871
|
-
orphansOnline += 1;
|
|
872
|
-
continue;
|
|
873
|
-
}
|
|
874
|
-
if (Number.isFinite(s.level)) {
|
|
875
|
-
const lvl = Math.max(0, Math.floor(s.level));
|
|
876
|
-
levelsMax = Math.max(levelsMax, lvl);
|
|
877
|
-
while (levelCounts.length <= lvl)
|
|
878
|
-
levelCounts.push(0);
|
|
879
|
-
levelCounts[lvl] = (levelCounts[lvl] ?? 0) + 1;
|
|
880
|
-
levelsTotal += 1;
|
|
881
|
-
}
|
|
882
|
-
if (s.effectiveMaxChildren > 0) {
|
|
883
|
-
const c = Math.max(0, Math.floor(s.children));
|
|
884
|
-
childrenMax = Math.max(childrenMax, c);
|
|
885
|
-
while (childCounts.length <= c)
|
|
886
|
-
childCounts.push(0);
|
|
887
|
-
childCounts[c] = (childCounts[c] ?? 0) + 1;
|
|
888
|
-
childrenTotal += 1;
|
|
889
|
-
}
|
|
890
|
-
if (s.level > 0 && !s.parent) {
|
|
891
|
-
orphansOnline += 1;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
const levelP95 = levelsTotal > 0
|
|
895
|
-
? quantileFromCounts(levelCounts, levelsMax, levelsTotal, 0.95)
|
|
896
|
-
: NaN;
|
|
897
|
-
const childrenP95 = childrenTotal > 0
|
|
898
|
-
? quantileFromCounts(childCounts, childrenMax, childrenTotal, 0.95)
|
|
899
|
-
: NaN;
|
|
900
|
-
return {
|
|
901
|
-
onlineJoined,
|
|
902
|
-
orphansOnline,
|
|
903
|
-
levelP95,
|
|
904
|
-
childrenP95,
|
|
905
|
-
};
|
|
906
|
-
};
|
|
907
|
-
const maintenanceLoop = async () => {
|
|
908
|
-
if (!wantsMaintenance)
|
|
909
|
-
return;
|
|
910
|
-
const sampleEveryMs = Math.max(100, Math.min(1_000, Math.floor(params.nodes / 10)));
|
|
911
|
-
const baselineOrphans = formationTree.treeOrphans;
|
|
912
|
-
const baselineLevelP95 = formationTree.treeLevelP95;
|
|
913
|
-
const baselineChildrenP95 = formationTree.treeChildrenP95;
|
|
914
|
-
let lastAt = Date.now();
|
|
915
|
-
let lastOrphans = 0;
|
|
916
|
-
const startAt = lastAt;
|
|
917
|
-
try {
|
|
918
|
-
for (;;) {
|
|
919
|
-
if (maintenanceSignal.aborted)
|
|
920
|
-
return;
|
|
921
|
-
const now = Date.now();
|
|
922
|
-
const dt = Math.max(0, now - lastAt);
|
|
923
|
-
if (dt > 0) {
|
|
924
|
-
maintOrphanAreaMs += lastOrphans * dt;
|
|
925
|
-
}
|
|
926
|
-
lastAt = now;
|
|
927
|
-
const snap = sampleMaintenance(now);
|
|
928
|
-
lastOrphans = snap.orphansOnline;
|
|
929
|
-
maintMaxOrphans = Math.max(maintMaxOrphans, snap.orphansOnline);
|
|
930
|
-
maintSamples += 1;
|
|
931
|
-
if (Number.isFinite(snap.levelP95)) {
|
|
932
|
-
maintLevelP95DriftMax = Math.max(maintLevelP95DriftMax, Math.abs(snap.levelP95 - baselineLevelP95));
|
|
933
|
-
}
|
|
934
|
-
if (Number.isFinite(snap.childrenP95)) {
|
|
935
|
-
maintChildrenP95DriftMax = Math.max(maintChildrenP95DriftMax, Math.abs(snap.childrenP95 - baselineChildrenP95));
|
|
936
|
-
}
|
|
937
|
-
if (snap.orphansOnline <= baselineOrphans &&
|
|
938
|
-
pendingRecoveryStarts.length > 0) {
|
|
939
|
-
for (const s of pendingRecoveryStarts)
|
|
940
|
-
recoveryDurations.push(now - s);
|
|
941
|
-
pendingRecoveryStarts.length = 0;
|
|
942
|
-
}
|
|
943
|
-
await delay(sampleEveryMs, { signal: maintenanceSignal });
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
finally {
|
|
947
|
-
const endAt = Date.now();
|
|
948
|
-
const dt = Math.max(0, endAt - lastAt);
|
|
949
|
-
if (dt > 0) {
|
|
950
|
-
maintOrphanAreaMs += lastOrphans * dt;
|
|
951
|
-
}
|
|
952
|
-
maintDurationMs = Math.max(0, endAt - startAt);
|
|
953
|
-
for (const s of pendingRecoveryStarts)
|
|
954
|
-
recoveryDurations.push(endAt - s);
|
|
955
|
-
pendingRecoveryStarts.length = 0;
|
|
956
|
-
}
|
|
957
|
-
};
|
|
958
|
-
const churnLoop = async () => {
|
|
959
|
-
const everyMs = Math.max(0, Math.floor(params.churnEveryMs));
|
|
960
|
-
const downMs = Math.max(0, Math.floor(params.churnDownMs));
|
|
961
|
-
const fraction = Math.max(0, Math.min(1, Number(params.churnFraction)));
|
|
962
|
-
if (everyMs <= 0 || downMs <= 0 || fraction <= 0)
|
|
963
|
-
return;
|
|
964
|
-
if (joinedSubscriberIndices.length === 0)
|
|
965
|
-
return;
|
|
966
|
-
for (;;) {
|
|
967
|
-
if (churnSignal.aborted)
|
|
968
|
-
return;
|
|
969
|
-
await delay(everyMs, { signal: churnSignal });
|
|
970
|
-
if (churnSignal.aborted)
|
|
971
|
-
return;
|
|
972
|
-
const target = Math.min(joinedSubscriberIndices.length, Math.max(1, Math.floor(joinedSubscriberIndices.length * fraction)));
|
|
973
|
-
const chosen = new Set();
|
|
974
|
-
const maxAttempts = Math.max(10, target * 20);
|
|
975
|
-
for (let tries = 0; chosen.size < target && tries < maxAttempts; tries++) {
|
|
976
|
-
const idx = joinedSubscriberIndices[int(rng, joinedSubscriberIndices.length)];
|
|
977
|
-
const peer = session.peers[idx];
|
|
978
|
-
if (network.isPeerOffline(peer.peerId))
|
|
979
|
-
continue;
|
|
980
|
-
chosen.add(idx);
|
|
981
|
-
}
|
|
982
|
-
if (chosen.size === 0)
|
|
983
|
-
continue;
|
|
984
|
-
churnEvents += 1;
|
|
985
|
-
churnedPeersTotal += chosen.size;
|
|
986
|
-
const now = Date.now();
|
|
987
|
-
if (wantsMaintenance)
|
|
988
|
-
pendingRecoveryStarts.push(now);
|
|
989
|
-
await Promise.all([...chosen].map(async (idx) => {
|
|
990
|
-
const peer = session.peers[idx];
|
|
991
|
-
network.setPeerOffline(peer.peerId, downMs, now);
|
|
992
|
-
await network.disconnectPeer(peer.peerId);
|
|
993
|
-
}));
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
const collectParentUpgradeActivity = () => {
|
|
997
|
-
let reparentUpgrade = 0;
|
|
998
|
-
let reparentUpgradeSkipData = 0;
|
|
999
|
-
let reparentUpgradeSkipRepair = 0;
|
|
1000
|
-
let reparentUpgradeSkipQuiet = 0;
|
|
1001
|
-
let parentProbeReqSent = 0;
|
|
1002
|
-
let parentShadowStart = 0;
|
|
1003
|
-
let parentShadowPromote = 0;
|
|
1004
|
-
for (const p of session.peers) {
|
|
1005
|
-
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
1006
|
-
reparentUpgrade += m.reparentUpgrade;
|
|
1007
|
-
reparentUpgradeSkipData += m.reparentUpgradeSkipData;
|
|
1008
|
-
reparentUpgradeSkipRepair += m.reparentUpgradeSkipRepair;
|
|
1009
|
-
reparentUpgradeSkipQuiet += m.reparentUpgradeSkipQuiet;
|
|
1010
|
-
parentProbeReqSent += m.parentProbeReqSent;
|
|
1011
|
-
parentShadowStart += m.parentShadowStart;
|
|
1012
|
-
parentShadowPromote += m.parentShadowPromote;
|
|
1013
|
-
}
|
|
1014
|
-
return {
|
|
1015
|
-
reparentUpgrade,
|
|
1016
|
-
reparentUpgradeSkipData,
|
|
1017
|
-
reparentUpgradeSkipRepair,
|
|
1018
|
-
reparentUpgradeSkipQuiet,
|
|
1019
|
-
parentProbeReqSent,
|
|
1020
|
-
parentShadowStart,
|
|
1021
|
-
parentShadowPromote,
|
|
1022
|
-
};
|
|
1023
|
-
};
|
|
1024
|
-
const diffParentUpgradeActivity = (after, before) => ({
|
|
1025
|
-
reparentUpgrade: Math.max(0, after.reparentUpgrade - before.reparentUpgrade),
|
|
1026
|
-
reparentUpgradeSkipData: Math.max(0, after.reparentUpgradeSkipData - before.reparentUpgradeSkipData),
|
|
1027
|
-
reparentUpgradeSkipRepair: Math.max(0, after.reparentUpgradeSkipRepair - before.reparentUpgradeSkipRepair),
|
|
1028
|
-
reparentUpgradeSkipQuiet: Math.max(0, after.reparentUpgradeSkipQuiet - before.reparentUpgradeSkipQuiet),
|
|
1029
|
-
parentProbeReqSent: Math.max(0, after.parentProbeReqSent - before.parentProbeReqSent),
|
|
1030
|
-
parentShadowStart: Math.max(0, after.parentShadowStart - before.parentShadowStart),
|
|
1031
|
-
parentShadowPromote: Math.max(0, after.parentShadowPromote - before.parentShadowPromote),
|
|
1032
|
-
});
|
|
1033
|
-
const publishStartParentUpgrade = collectParentUpgradeActivity();
|
|
1034
|
-
let publishActiveParentUpgrade = diffParentUpgradeActivity(publishStartParentUpgrade, publishStartParentUpgrade);
|
|
1035
|
-
const publishStart = Date.now();
|
|
1036
|
-
let publishActiveMs = 0;
|
|
1037
|
-
if (wantsMaintenance) {
|
|
1038
|
-
for (const p of session.peers) {
|
|
1039
|
-
const nodeHash = p.services.fanout.publicKeyHash;
|
|
1040
|
-
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
1041
|
-
reparentBaselineByHash.set(nodeHash, m.reparentDisconnect +
|
|
1042
|
-
m.reparentStale +
|
|
1043
|
-
m.reparentKicked +
|
|
1044
|
-
m.reparentUpgrade);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
const maintenancePromise = maintenanceLoop().catch(() => { });
|
|
1048
|
-
const churnPromise = churnLoop().catch(() => { });
|
|
1049
|
-
const lateAfterMs = Math.max(-1, Math.floor(params.lateRootConnectAfterMs));
|
|
1050
|
-
let lateRootApplied = false;
|
|
1051
|
-
const applyLateRootTopology = async () => {
|
|
1052
|
-
if (lateRootApplied || lateAfterMs < 0)
|
|
1053
|
-
return;
|
|
1054
|
-
lateRootApplied = true;
|
|
1055
|
-
const lateMaxChildren = Math.max(0, Math.floor(params.lateRootMaxChildren));
|
|
1056
|
-
if (lateMaxChildren > 0) {
|
|
1057
|
-
const id = root.getChannelId(params.topic, rootId);
|
|
1058
|
-
const ch = root.channelsBySuffixKey?.get?.(id.suffixKey);
|
|
1059
|
-
if (ch) {
|
|
1060
|
-
ch.maxChildren = Math.max(ch.maxChildren ?? 0, lateMaxChildren);
|
|
1061
|
-
const uploadBoundedMaxChildren = effectiveMaxChildrenForUpload(params, ch, lateMaxChildren);
|
|
1062
|
-
ch.effectiveMaxChildren = Math.max(ch.effectiveMaxChildren ?? 0, uploadBoundedMaxChildren);
|
|
1063
|
-
void root
|
|
1064
|
-
.announceToTrackers?.(ch, timeoutSignal)
|
|
1065
|
-
?.catch?.(() => { });
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
const fraction = Math.max(0, Math.min(1, Number(params.lateRootConnectFraction)));
|
|
1069
|
-
const target = Math.min(joinedSubscriberIndices.length, Math.max(0, Math.ceil(joinedSubscriberIndices.length * fraction)));
|
|
1070
|
-
for (let i = 0; i < target; i++) {
|
|
1071
|
-
const idx = joinedSubscriberIndices[i];
|
|
1072
|
-
if (idx === rootIndex)
|
|
1073
|
-
continue;
|
|
1074
|
-
try {
|
|
1075
|
-
await session.peers[idx].dial(session.peers[rootIndex].getMultiaddrs());
|
|
1076
|
-
}
|
|
1077
|
-
catch {
|
|
1078
|
-
// best-effort late underlay shortcut
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
};
|
|
1082
|
-
const lateRootDuringPublishPromise = params.lateRootDuringPublish && lateAfterMs >= 0
|
|
1083
|
-
? (async () => {
|
|
1084
|
-
if (lateAfterMs > 0) {
|
|
1085
|
-
await delay(lateAfterMs, { signal: timeoutSignal });
|
|
1086
|
-
}
|
|
1087
|
-
await applyLateRootTopology();
|
|
1088
|
-
})().catch((error) => {
|
|
1089
|
-
if (!timeoutSignal.aborted)
|
|
1090
|
-
throw error;
|
|
1091
|
-
})
|
|
1092
|
-
: undefined;
|
|
1093
|
-
try {
|
|
1094
|
-
for (let seq = 0; seq < firstBatchMessages; seq++) {
|
|
1095
|
-
if (timeoutSignal.aborted) {
|
|
1096
|
-
throw timeoutSignal.reason ?? new Error("fanout-tree-sim aborted");
|
|
1097
|
-
}
|
|
1098
|
-
publishAt.set(seq, Date.now());
|
|
1099
|
-
await root.publishData(params.topic, rootId, payload);
|
|
1100
|
-
if (params.intervalMs > 0) {
|
|
1101
|
-
await delay(params.intervalMs, { signal: timeoutSignal });
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
finally {
|
|
1106
|
-
if (firstBatchMessages > 0) {
|
|
1107
|
-
publishActiveMs += Math.max(0, Date.now() - publishStart);
|
|
1108
|
-
}
|
|
1109
|
-
publishActiveParentUpgrade = diffParentUpgradeActivity(collectParentUpgradeActivity(), publishStartParentUpgrade);
|
|
1110
|
-
churnController.abort();
|
|
1111
|
-
await churnPromise;
|
|
1112
|
-
churnSignal.clear?.();
|
|
1113
|
-
await lateRootDuringPublishPromise;
|
|
1114
|
-
}
|
|
1115
|
-
let publishDone = Date.now();
|
|
1116
|
-
// Signal end-of-stream so subscribers can detect tail gaps and repair.
|
|
1117
|
-
if (params.repair && firstBatchMessages > 0) {
|
|
1118
|
-
await root.publishEnd(params.topic, rootId, firstBatchMessages);
|
|
1119
|
-
}
|
|
1120
|
-
if (params.settleMs > 0) {
|
|
1121
|
-
if (!params.lateRootDuringPublish &&
|
|
1122
|
-
lateAfterMs >= 0 &&
|
|
1123
|
-
lateAfterMs < params.settleMs) {
|
|
1124
|
-
if (lateAfterMs > 0) {
|
|
1125
|
-
await delay(lateAfterMs, { signal: timeoutSignal });
|
|
1126
|
-
}
|
|
1127
|
-
await applyLateRootTopology();
|
|
1128
|
-
const remaining = Math.max(0, params.settleMs - lateAfterMs);
|
|
1129
|
-
if (remaining > 0) {
|
|
1130
|
-
await delay(remaining, { signal: timeoutSignal });
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
else {
|
|
1134
|
-
await delay(params.settleMs, { signal: timeoutSignal });
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
if (secondBatchMessages > 0) {
|
|
1138
|
-
const secondPublishStart = Date.now();
|
|
1139
|
-
for (let seq = secondBatchStartSeq; seq < totalMessages; seq++) {
|
|
1140
|
-
if (timeoutSignal.aborted) {
|
|
1141
|
-
throw timeoutSignal.reason ?? new Error("fanout-tree-sim aborted");
|
|
1142
|
-
}
|
|
1143
|
-
publishAt.set(seq, Date.now());
|
|
1144
|
-
await root.publishData(params.topic, rootId, payload);
|
|
1145
|
-
if (params.intervalMs > 0) {
|
|
1146
|
-
await delay(params.intervalMs, { signal: timeoutSignal });
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
publishDone = Date.now();
|
|
1150
|
-
publishActiveMs += Math.max(0, publishDone - secondPublishStart);
|
|
1151
|
-
if (params.repair) {
|
|
1152
|
-
await root.publishEnd(params.topic, rootId, totalMessages);
|
|
1153
|
-
}
|
|
1154
|
-
const secondSettleMs = Math.max(0, Math.floor(params.secondBatchSettleMs));
|
|
1155
|
-
if (secondSettleMs > 0) {
|
|
1156
|
-
await delay(secondSettleMs, { signal: timeoutSignal });
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
maintenanceController.abort();
|
|
1160
|
-
await maintenancePromise;
|
|
1161
|
-
maintenanceSignal.clear?.();
|
|
1162
|
-
recoveryDurations.sort((a, b) => a - b);
|
|
1163
|
-
const maintRecoveryCount = recoveryDurations.length;
|
|
1164
|
-
const maintRecoveryP50Ms = maintRecoveryCount > 0 ? quantile(recoveryDurations, 0.5) : 0;
|
|
1165
|
-
const maintRecoveryP95Ms = maintRecoveryCount > 0 ? quantile(recoveryDurations, 0.95) : 0;
|
|
1166
|
-
if (wantsMaintenance) {
|
|
1167
|
-
for (const p of session.peers) {
|
|
1168
|
-
const nodeHash = p.services.fanout.publicKeyHash;
|
|
1169
|
-
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
1170
|
-
const total = m.reparentDisconnect +
|
|
1171
|
-
m.reparentStale +
|
|
1172
|
-
m.reparentKicked +
|
|
1173
|
-
m.reparentUpgrade;
|
|
1174
|
-
const base = reparentBaselineByHash.get(nodeHash) ?? 0;
|
|
1175
|
-
const delta = Math.max(0, total - base);
|
|
1176
|
-
maintReparentsTotal += delta;
|
|
1177
|
-
maintMaxReparentsPerPeer = Math.max(maintMaxReparentsPerPeer, delta);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
const maintDurationMin = maintDurationMs / 60_000;
|
|
1181
|
-
const maintReparentsPerMin = maintDurationMin > 0 ? maintReparentsTotal / maintDurationMin : 0;
|
|
1182
|
-
const maintOrphanArea = maintOrphanAreaMs / 1_000;
|
|
1183
|
-
// Compute delivery
|
|
1184
|
-
const expected = joinedCount * totalMessages;
|
|
1185
|
-
let delivered = 0;
|
|
1186
|
-
for (const c of receivedCounts)
|
|
1187
|
-
delivered += c;
|
|
1188
|
-
const secondBatchExpected = joinedCount * secondBatchMessages;
|
|
1189
|
-
let secondBatchDelivered = 0;
|
|
1190
|
-
for (const c of secondBatchReceivedCounts)
|
|
1191
|
-
secondBatchDelivered += c;
|
|
1192
|
-
const joinedPct = subscriberCount === 0 ? 100 : (100 * joinedCount) / subscriberCount;
|
|
1193
|
-
const deliveredPct = expected === 0 ? 100 : (100 * delivered) / expected;
|
|
1194
|
-
const deliveredWithinDeadlinePct = expected === 0 ? 100 : (100 * deliveredWithinDeadline) / expected;
|
|
1195
|
-
const secondBatchDeliveredPct = secondBatchExpected === 0
|
|
1196
|
-
? 100
|
|
1197
|
-
: (100 * secondBatchDelivered) / secondBatchExpected;
|
|
1198
|
-
const secondBatchDeliveredWithinDeadlinePct = secondBatchExpected === 0
|
|
1199
|
-
? 100
|
|
1200
|
-
: (100 * secondBatchDeliveredWithinDeadline) / secondBatchExpected;
|
|
1201
|
-
// Internal protocol drops (from upload shaping / overload logic)
|
|
1202
|
-
let droppedForwardsTotal = 0;
|
|
1203
|
-
let droppedForwardsMax = 0;
|
|
1204
|
-
let droppedForwardsMaxNode;
|
|
1205
|
-
for (const p of session.peers) {
|
|
1206
|
-
const s = p.services.fanout.getChannelStats(params.topic, rootId);
|
|
1207
|
-
if (!s)
|
|
1208
|
-
continue;
|
|
1209
|
-
droppedForwardsTotal += s.droppedForwards;
|
|
1210
|
-
if (s.droppedForwards > droppedForwardsMax) {
|
|
1211
|
-
droppedForwardsMax = s.droppedForwards;
|
|
1212
|
-
droppedForwardsMaxNode = p.services.fanout.publicKeyHash;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
// Tree shape stats (best-effort)
|
|
1216
|
-
const tree = computeTreeShapeStats();
|
|
1217
|
-
const treeMaxLevel = tree.treeMaxLevel;
|
|
1218
|
-
const treeLevelP95 = tree.treeLevelP95;
|
|
1219
|
-
const treeLevelAvg = tree.treeLevelAvg;
|
|
1220
|
-
const treeOrphans = tree.treeOrphans;
|
|
1221
|
-
const treeChildrenP95 = tree.treeChildrenP95;
|
|
1222
|
-
const treeChildrenMax = tree.treeChildrenMax;
|
|
1223
|
-
const treeRootChildren = tree.treeRootChildren;
|
|
1224
|
-
// Stream backpressure stats (queued bytes)
|
|
1225
|
-
const queuedBytesSamples = [];
|
|
1226
|
-
let streamQueuedBytesTotal = 0;
|
|
1227
|
-
let streamQueuedBytesMax = 0;
|
|
1228
|
-
let streamQueuedBytesMaxNode;
|
|
1229
|
-
for (const p of session.peers) {
|
|
1230
|
-
const q = Math.max(0, Math.floor(p.services.fanout.getQueuedBytes()));
|
|
1231
|
-
streamQueuedBytesTotal += q;
|
|
1232
|
-
queuedBytesSamples.push(q);
|
|
1233
|
-
if (q > streamQueuedBytesMax) {
|
|
1234
|
-
streamQueuedBytesMax = q;
|
|
1235
|
-
streamQueuedBytesMaxNode = p.services.fanout.publicKeyHash;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
queuedBytesSamples.sort((a, b) => a - b);
|
|
1239
|
-
const streamQueuedBytesP95 = queuedBytesSamples.length > 0 ? quantile(queuedBytesSamples, 0.95) : 0;
|
|
1240
|
-
const streamQueuedBytesByLane = [];
|
|
1241
|
-
for (const p of session.peers) {
|
|
1242
|
-
for (const ps of p.services.fanout.peers.values()) {
|
|
1243
|
-
const byLane =
|
|
1244
|
-
// @ts-ignore - optional debug helper (may not exist in built typings yet)
|
|
1245
|
-
ps.getOutboundQueuedBytesByLane?.() ?? [0, 0, 0, 0];
|
|
1246
|
-
for (let lane = 0; lane < byLane.length; lane++) {
|
|
1247
|
-
streamQueuedBytesByLane[lane] =
|
|
1248
|
-
(streamQueuedBytesByLane[lane] ?? 0) + (byLane[lane] ?? 0);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
for (let lane = 0; lane < 4; lane++) {
|
|
1253
|
-
streamQueuedBytesByLane[lane] = streamQueuedBytesByLane[lane] ?? 0;
|
|
1254
|
-
}
|
|
1255
|
-
// Peak upload vs cap (best-effort; counts framed bytes, including overhead)
|
|
1256
|
-
const uploadCapByHash = new Map();
|
|
1257
|
-
if (params.rootUploadLimitBps > 0) {
|
|
1258
|
-
uploadCapByHash.set(root.publicKeyHash, params.rootUploadLimitBps);
|
|
1259
|
-
}
|
|
1260
|
-
for (let i = 0; i < subscriberIndices.length; i++) {
|
|
1261
|
-
if (!joined[i])
|
|
1262
|
-
continue;
|
|
1263
|
-
const idx = subscriberIndices[i];
|
|
1264
|
-
if (!relaySet.has(idx))
|
|
1265
|
-
continue;
|
|
1266
|
-
if (params.relayUploadLimitBps <= 0)
|
|
1267
|
-
continue;
|
|
1268
|
-
const h = session.peers[idx].services.fanout.publicKeyHash;
|
|
1269
|
-
uploadCapByHash.set(h, params.relayUploadLimitBps);
|
|
1270
|
-
}
|
|
1271
|
-
let maxUploadFracPct = 0;
|
|
1272
|
-
let maxUploadNode;
|
|
1273
|
-
let maxUploadBps = 0;
|
|
1274
|
-
const rootUploadBps = session.network.peerMetricsByHash.get(root.publicKeyHash)
|
|
1275
|
-
?.maxBytesPerSecond ?? 0;
|
|
1276
|
-
const rootUploadFracPct = params.rootUploadLimitBps > 0
|
|
1277
|
-
? (100 * rootUploadBps) / params.rootUploadLimitBps
|
|
1278
|
-
: 0;
|
|
1279
|
-
for (const [hash, pm] of session.network.peerMetricsByHash) {
|
|
1280
|
-
const cap = uploadCapByHash.get(hash);
|
|
1281
|
-
if (!cap || cap <= 0)
|
|
1282
|
-
continue;
|
|
1283
|
-
const frac = (100 * pm.maxBytesPerSecond) / cap;
|
|
1284
|
-
if (frac > maxUploadFracPct) {
|
|
1285
|
-
maxUploadFracPct = frac;
|
|
1286
|
-
maxUploadNode = hash;
|
|
1287
|
-
maxUploadBps = pm.maxBytesPerSecond;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
deliveredSamples.sort((a, b) => a - b);
|
|
1291
|
-
secondBatchDeliveredSamples.sort((a, b) => a - b);
|
|
1292
|
-
const secondBatchLatencyP95ByHash = {};
|
|
1293
|
-
for (let i = 0; i < joinedHashList.length; i++) {
|
|
1294
|
-
const samples = secondBatchDeliveredSamplesByPeer[i];
|
|
1295
|
-
if (samples.length === 0)
|
|
1296
|
-
continue;
|
|
1297
|
-
samples.sort((a, b) => a - b);
|
|
1298
|
-
secondBatchLatencyP95ByHash[joinedHashList[i]] = quantile(samples, 0.95);
|
|
1299
|
-
}
|
|
1300
|
-
let protocolControlSends = 0;
|
|
1301
|
-
let protocolControlBytesSent = 0;
|
|
1302
|
-
let protocolControlBytesSentJoin = 0;
|
|
1303
|
-
let protocolControlBytesSentRepair = 0;
|
|
1304
|
-
let protocolControlBytesSentTracker = 0;
|
|
1305
|
-
let protocolControlReceives = 0;
|
|
1306
|
-
let protocolControlBytesReceived = 0;
|
|
1307
|
-
let protocolDataSends = 0;
|
|
1308
|
-
let protocolDataPayloadBytesSent = 0;
|
|
1309
|
-
let protocolDataReceives = 0;
|
|
1310
|
-
let protocolDataPayloadBytesReceived = 0;
|
|
1311
|
-
let protocolRepairReqSent = 0;
|
|
1312
|
-
let protocolFetchReqSent = 0;
|
|
1313
|
-
let protocolIHaveSent = 0;
|
|
1314
|
-
let protocolTrackerAnnounceSent = 0;
|
|
1315
|
-
let protocolTrackerQuerySent = 0;
|
|
1316
|
-
let protocolTrackerReplySent = 0;
|
|
1317
|
-
let protocolTrackerFeedbackSent = 0;
|
|
1318
|
-
let protocolCacheHitsServed = 0;
|
|
1319
|
-
let protocolHoleFillsFromNeighbor = 0;
|
|
1320
|
-
let protocolRouteCacheHits = 0;
|
|
1321
|
-
let protocolRouteCacheMisses = 0;
|
|
1322
|
-
let protocolRouteCacheExpirations = 0;
|
|
1323
|
-
let protocolRouteCacheEvictions = 0;
|
|
1324
|
-
let protocolRouteProxyQueries = 0;
|
|
1325
|
-
let protocolRouteProxyTimeouts = 0;
|
|
1326
|
-
let protocolRouteProxyFanout = 0;
|
|
1327
|
-
let staleForwardsDroppedTotal = 0;
|
|
1328
|
-
let staleForwardsDroppedMax = 0;
|
|
1329
|
-
let staleForwardsDroppedMaxNode;
|
|
1330
|
-
let dataWriteDropsTotal = 0;
|
|
1331
|
-
let dataWriteDropsMax = 0;
|
|
1332
|
-
let dataWriteDropsMaxNode;
|
|
1333
|
-
let reparentDisconnectTotal = 0;
|
|
1334
|
-
let reparentStaleTotal = 0;
|
|
1335
|
-
let reparentKickedTotal = 0;
|
|
1336
|
-
let reparentUpgradeTotal = 0;
|
|
1337
|
-
const upgradedPeerHashes = [];
|
|
1338
|
-
let reparentUpgradeSkipLeafTotal = 0;
|
|
1339
|
-
let reparentUpgradeSkipRepairTotal = 0;
|
|
1340
|
-
let reparentUpgradeSkipDataTotal = 0;
|
|
1341
|
-
let reparentUpgradeSkipCooldownTotal = 0;
|
|
1342
|
-
let reparentUpgradeSkipQuietTotal = 0;
|
|
1343
|
-
let reparentUpgradeSkipBudgetTotal = 0;
|
|
1344
|
-
let reparentUpgradeSkipCandidateLevelTotal = 0;
|
|
1345
|
-
let reparentUpgradeSkipCandidateSlotsTotal = 0;
|
|
1346
|
-
let reparentUpgradeSkipCandidatePressureTotal = 0;
|
|
1347
|
-
let reparentUpgradeSkipRootPressureTotal = 0;
|
|
1348
|
-
let reparentUpgradeSkipProbeNoReplyTotal = 0;
|
|
1349
|
-
let reparentUpgradeSkipProbeNotRootedTotal = 0;
|
|
1350
|
-
let reparentUpgradeSkipProbeRepairTotal = 0;
|
|
1351
|
-
let reparentUpgradeSkipProbeLagTotal = 0;
|
|
1352
|
-
let reparentUpgradeSkipProbeOverloadedTotal = 0;
|
|
1353
|
-
let reparentUpgradeSkipProbeCooldownTotal = 0;
|
|
1354
|
-
let parentProbeReqSentTotal = 0;
|
|
1355
|
-
let parentProbeReqReceivedTotal = 0;
|
|
1356
|
-
let parentProbeReplySentTotal = 0;
|
|
1357
|
-
let parentProbeReplyReceivedTotal = 0;
|
|
1358
|
-
let parentUpgradeRootReservationCreatedTotal = 0;
|
|
1359
|
-
let parentUpgradeRootReservationConsumedTotal = 0;
|
|
1360
|
-
let parentUpgradeRootReservationRejectedTotal = 0;
|
|
1361
|
-
let parentUpgradeRootReservationMarginRejectedTotal = 0;
|
|
1362
|
-
let parentUpgradeRootReservationBlockedTotal = 0;
|
|
1363
|
-
let parentUpgradeRootReservationExpiredTotal = 0;
|
|
1364
|
-
let parentShadowStartTotal = 0;
|
|
1365
|
-
let parentShadowObserveTotal = 0;
|
|
1366
|
-
let parentShadowPromoteTotal = 0;
|
|
1367
|
-
let parentShadowResetTotal = 0;
|
|
1368
|
-
let parentShadowRejectNoReplyTotal = 0;
|
|
1369
|
-
let parentShadowRejectNotRootedTotal = 0;
|
|
1370
|
-
let parentShadowRejectCapacityTotal = 0;
|
|
1371
|
-
let parentShadowRejectRepairTotal = 0;
|
|
1372
|
-
let parentShadowRejectLagTotal = 0;
|
|
1373
|
-
let parentShadowRejectOverloadedTotal = 0;
|
|
1374
|
-
let parentShadowRejectLevelTotal = 0;
|
|
1375
|
-
let earningsTotal = 0;
|
|
1376
|
-
const earningsByHash = new Map();
|
|
1377
|
-
for (const p of session.peers) {
|
|
1378
|
-
const nodeHash = p.services.fanout.publicKeyHash;
|
|
1379
|
-
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
1380
|
-
staleForwardsDroppedTotal += m.staleForwardsDropped;
|
|
1381
|
-
if (m.staleForwardsDropped > staleForwardsDroppedMax) {
|
|
1382
|
-
staleForwardsDroppedMax = m.staleForwardsDropped;
|
|
1383
|
-
staleForwardsDroppedMaxNode = nodeHash;
|
|
1384
|
-
}
|
|
1385
|
-
dataWriteDropsTotal += m.dataWriteDrops;
|
|
1386
|
-
if (m.dataWriteDrops > dataWriteDropsMax) {
|
|
1387
|
-
dataWriteDropsMax = m.dataWriteDrops;
|
|
1388
|
-
dataWriteDropsMaxNode = nodeHash;
|
|
1389
|
-
}
|
|
1390
|
-
reparentDisconnectTotal += m.reparentDisconnect;
|
|
1391
|
-
reparentStaleTotal += m.reparentStale;
|
|
1392
|
-
reparentKickedTotal += m.reparentKicked;
|
|
1393
|
-
reparentUpgradeTotal += m.reparentUpgrade;
|
|
1394
|
-
if (m.reparentUpgrade > 0)
|
|
1395
|
-
upgradedPeerHashes.push(nodeHash);
|
|
1396
|
-
reparentUpgradeSkipLeafTotal += m.reparentUpgradeSkipLeaf;
|
|
1397
|
-
reparentUpgradeSkipRepairTotal += m.reparentUpgradeSkipRepair;
|
|
1398
|
-
reparentUpgradeSkipDataTotal += m.reparentUpgradeSkipData;
|
|
1399
|
-
reparentUpgradeSkipCooldownTotal += m.reparentUpgradeSkipCooldown;
|
|
1400
|
-
reparentUpgradeSkipQuietTotal += m.reparentUpgradeSkipQuiet;
|
|
1401
|
-
reparentUpgradeSkipBudgetTotal += m.reparentUpgradeSkipBudget;
|
|
1402
|
-
reparentUpgradeSkipCandidateLevelTotal +=
|
|
1403
|
-
m.reparentUpgradeSkipCandidateLevel;
|
|
1404
|
-
reparentUpgradeSkipCandidateSlotsTotal +=
|
|
1405
|
-
m.reparentUpgradeSkipCandidateSlots;
|
|
1406
|
-
reparentUpgradeSkipCandidatePressureTotal +=
|
|
1407
|
-
m.reparentUpgradeSkipCandidatePressure;
|
|
1408
|
-
reparentUpgradeSkipRootPressureTotal +=
|
|
1409
|
-
m.reparentUpgradeSkipRootPressure;
|
|
1410
|
-
reparentUpgradeSkipProbeNoReplyTotal +=
|
|
1411
|
-
m.reparentUpgradeSkipProbeNoReply;
|
|
1412
|
-
reparentUpgradeSkipProbeNotRootedTotal +=
|
|
1413
|
-
m.reparentUpgradeSkipProbeNotRooted;
|
|
1414
|
-
reparentUpgradeSkipProbeRepairTotal += m.reparentUpgradeSkipProbeRepair;
|
|
1415
|
-
reparentUpgradeSkipProbeLagTotal += m.reparentUpgradeSkipProbeLag;
|
|
1416
|
-
reparentUpgradeSkipProbeOverloadedTotal +=
|
|
1417
|
-
m.reparentUpgradeSkipProbeOverloaded;
|
|
1418
|
-
reparentUpgradeSkipProbeCooldownTotal +=
|
|
1419
|
-
m.reparentUpgradeSkipProbeCooldown;
|
|
1420
|
-
parentProbeReqSentTotal += m.parentProbeReqSent;
|
|
1421
|
-
parentProbeReqReceivedTotal += m.parentProbeReqReceived;
|
|
1422
|
-
parentProbeReplySentTotal += m.parentProbeReplySent;
|
|
1423
|
-
parentProbeReplyReceivedTotal += m.parentProbeReplyReceived;
|
|
1424
|
-
parentUpgradeRootReservationCreatedTotal +=
|
|
1425
|
-
m.parentUpgradeRootReservationCreated;
|
|
1426
|
-
parentUpgradeRootReservationConsumedTotal +=
|
|
1427
|
-
m.parentUpgradeRootReservationConsumed;
|
|
1428
|
-
parentUpgradeRootReservationRejectedTotal +=
|
|
1429
|
-
m.parentUpgradeRootReservationRejected;
|
|
1430
|
-
parentUpgradeRootReservationMarginRejectedTotal +=
|
|
1431
|
-
m.parentUpgradeRootReservationMarginRejected;
|
|
1432
|
-
parentUpgradeRootReservationBlockedTotal +=
|
|
1433
|
-
m.parentUpgradeRootReservationBlocked;
|
|
1434
|
-
parentUpgradeRootReservationExpiredTotal +=
|
|
1435
|
-
m.parentUpgradeRootReservationExpired;
|
|
1436
|
-
parentShadowStartTotal += m.parentShadowStart;
|
|
1437
|
-
parentShadowObserveTotal += m.parentShadowObserve;
|
|
1438
|
-
parentShadowPromoteTotal += m.parentShadowPromote;
|
|
1439
|
-
parentShadowResetTotal += m.parentShadowReset;
|
|
1440
|
-
parentShadowRejectNoReplyTotal += m.parentShadowRejectNoReply;
|
|
1441
|
-
parentShadowRejectNotRootedTotal += m.parentShadowRejectNotRooted;
|
|
1442
|
-
parentShadowRejectCapacityTotal += m.parentShadowRejectCapacity;
|
|
1443
|
-
parentShadowRejectRepairTotal += m.parentShadowRejectRepair;
|
|
1444
|
-
parentShadowRejectLagTotal += m.parentShadowRejectLag;
|
|
1445
|
-
parentShadowRejectOverloadedTotal += m.parentShadowRejectOverloaded;
|
|
1446
|
-
parentShadowRejectLevelTotal += m.parentShadowRejectLevel;
|
|
1447
|
-
earningsTotal += m.earnings;
|
|
1448
|
-
earningsByHash.set(nodeHash, m.earnings);
|
|
1449
|
-
protocolControlSends += m.controlSends;
|
|
1450
|
-
protocolControlBytesSent += m.controlBytesSent;
|
|
1451
|
-
protocolControlBytesSentJoin += m.controlBytesSentJoin;
|
|
1452
|
-
protocolControlBytesSentRepair += m.controlBytesSentRepair;
|
|
1453
|
-
protocolControlBytesSentTracker += m.controlBytesSentTracker;
|
|
1454
|
-
protocolControlReceives += m.controlReceives;
|
|
1455
|
-
protocolControlBytesReceived += m.controlBytesReceived;
|
|
1456
|
-
protocolDataSends += m.dataSends;
|
|
1457
|
-
protocolDataPayloadBytesSent += m.dataPayloadBytesSent;
|
|
1458
|
-
protocolDataReceives += m.dataReceives;
|
|
1459
|
-
protocolDataPayloadBytesReceived += m.dataPayloadBytesReceived;
|
|
1460
|
-
protocolRepairReqSent += m.repairReqSent;
|
|
1461
|
-
protocolFetchReqSent += m.fetchReqSent;
|
|
1462
|
-
protocolIHaveSent += m.ihaveSent;
|
|
1463
|
-
protocolTrackerAnnounceSent += m.trackerAnnounceSent;
|
|
1464
|
-
protocolTrackerQuerySent += m.trackerQuerySent;
|
|
1465
|
-
protocolTrackerReplySent += m.trackerReplySent;
|
|
1466
|
-
protocolTrackerFeedbackSent += m.trackerFeedbackSent;
|
|
1467
|
-
protocolCacheHitsServed += m.cacheHitsServed;
|
|
1468
|
-
protocolHoleFillsFromNeighbor += m.holeFillsFromNeighbor;
|
|
1469
|
-
protocolRouteCacheHits += m.routeCacheHits;
|
|
1470
|
-
protocolRouteCacheMisses += m.routeCacheMisses;
|
|
1471
|
-
protocolRouteCacheExpirations += m.routeCacheExpirations;
|
|
1472
|
-
protocolRouteCacheEvictions += m.routeCacheEvictions;
|
|
1473
|
-
protocolRouteProxyQueries += m.routeProxyQueries;
|
|
1474
|
-
protocolRouteProxyTimeouts += m.routeProxyTimeouts;
|
|
1475
|
-
protocolRouteProxyFanout += m.routeProxyFanout;
|
|
1476
|
-
}
|
|
1477
|
-
const upgradedBranchHashSet = new Set();
|
|
1478
|
-
for (const hash of upgradedPeerHashes) {
|
|
1479
|
-
const stack = [hash];
|
|
1480
|
-
while (stack.length > 0) {
|
|
1481
|
-
const next = stack.pop();
|
|
1482
|
-
if (upgradedBranchHashSet.has(next))
|
|
1483
|
-
continue;
|
|
1484
|
-
if (joinedHashes.has(next))
|
|
1485
|
-
upgradedBranchHashSet.add(next);
|
|
1486
|
-
for (const child of tree.childrenByHash.get(next) ?? []) {
|
|
1487
|
-
stack.push(child);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
const upgradedBranchPeerHashes = [...upgradedBranchHashSet];
|
|
1492
|
-
const relayHashes = [...uploadCapByHash.keys()];
|
|
1493
|
-
const relayEarnings = relayHashes
|
|
1494
|
-
.map((h) => earningsByHash.get(h) ?? 0)
|
|
1495
|
-
.sort((a, b) => a - b);
|
|
1496
|
-
const earningsRelayCount = relayEarnings.length;
|
|
1497
|
-
const earningsRelayP50 = earningsRelayCount > 0 ? quantile(relayEarnings, 0.5) : 0;
|
|
1498
|
-
const earningsRelayP95 = earningsRelayCount > 0 ? quantile(relayEarnings, 0.95) : 0;
|
|
1499
|
-
const earningsRelayMax = earningsRelayCount > 0 ? relayEarnings[relayEarnings.length - 1] : 0;
|
|
1500
|
-
const idealPayloadBytes = expected * Math.max(0, params.msgSize);
|
|
1501
|
-
const overheadFactorData = idealPayloadBytes <= 0
|
|
1502
|
-
? 1
|
|
1503
|
-
: protocolDataPayloadBytesSent / idealPayloadBytes;
|
|
1504
|
-
const deliveredPayloadBytes = delivered * Math.max(0, params.msgSize);
|
|
1505
|
-
const controlBpp = deliveredPayloadBytes <= 0
|
|
1506
|
-
? 0
|
|
1507
|
-
: protocolControlBytesSent / deliveredPayloadBytes;
|
|
1508
|
-
const trackerBpp = deliveredPayloadBytes <= 0
|
|
1509
|
-
? 0
|
|
1510
|
-
: protocolControlBytesSentTracker / deliveredPayloadBytes;
|
|
1511
|
-
const repairBpp = deliveredPayloadBytes <= 0
|
|
1512
|
-
? 0
|
|
1513
|
-
: protocolControlBytesSentRepair / deliveredPayloadBytes;
|
|
1514
|
-
return {
|
|
1515
|
-
params,
|
|
1516
|
-
bootstrapCount: bootstrapIndices.length,
|
|
1517
|
-
subscriberCount,
|
|
1518
|
-
relayCount: relaySet.size,
|
|
1519
|
-
joinedCount,
|
|
1520
|
-
joinedPct,
|
|
1521
|
-
joinMs: joinDone - joinStart,
|
|
1522
|
-
attachSamples,
|
|
1523
|
-
attachP50,
|
|
1524
|
-
attachP95,
|
|
1525
|
-
attachP99,
|
|
1526
|
-
attachMax,
|
|
1527
|
-
formationTreeMaxLevel: formationTree.treeMaxLevel,
|
|
1528
|
-
formationTreeLevelP95: formationTree.treeLevelP95,
|
|
1529
|
-
formationTreeLevelAvg: formationTree.treeLevelAvg,
|
|
1530
|
-
formationTreeOrphans: formationTree.treeOrphans,
|
|
1531
|
-
formationTreeChildrenP95: formationTree.treeChildrenP95,
|
|
1532
|
-
formationTreeChildrenMax: formationTree.treeChildrenMax,
|
|
1533
|
-
formationTreeRootChildren: formationTree.treeRootChildren,
|
|
1534
|
-
formationUnderlayEdges,
|
|
1535
|
-
formationUnderlayDistP95,
|
|
1536
|
-
formationUnderlayDistMax,
|
|
1537
|
-
formationStretchP95,
|
|
1538
|
-
formationStretchMax,
|
|
1539
|
-
formationScore,
|
|
1540
|
-
publishMs: publishActiveMs,
|
|
1541
|
-
expected,
|
|
1542
|
-
delivered,
|
|
1543
|
-
deliveredPct,
|
|
1544
|
-
deliveredWithinDeadline,
|
|
1545
|
-
deliveredWithinDeadlinePct,
|
|
1546
|
-
duplicates,
|
|
1547
|
-
secondBatchExpected,
|
|
1548
|
-
secondBatchDelivered,
|
|
1549
|
-
secondBatchDeliveredPct,
|
|
1550
|
-
secondBatchDeliveredWithinDeadline,
|
|
1551
|
-
secondBatchDeliveredWithinDeadlinePct,
|
|
1552
|
-
secondBatchLatencySamples: secondBatchDeliveredSamples.length,
|
|
1553
|
-
secondBatchLatencyP50: quantile(secondBatchDeliveredSamples, 0.5),
|
|
1554
|
-
secondBatchLatencyP95: quantile(secondBatchDeliveredSamples, 0.95),
|
|
1555
|
-
secondBatchLatencyP99: quantile(secondBatchDeliveredSamples, 0.99),
|
|
1556
|
-
secondBatchLatencyMax: secondBatchDeliveredSamples[secondBatchDeliveredSamples.length - 1] ??
|
|
1557
|
-
NaN,
|
|
1558
|
-
secondBatchLatencyP95ByHash,
|
|
1559
|
-
latencySamples: deliveredSamples.length,
|
|
1560
|
-
latencyP50: quantile(deliveredSamples, 0.5),
|
|
1561
|
-
latencyP95: quantile(deliveredSamples, 0.95),
|
|
1562
|
-
latencyP99: quantile(deliveredSamples, 0.99),
|
|
1563
|
-
latencyMax: deliveredSamples[deliveredSamples.length - 1] ?? NaN,
|
|
1564
|
-
droppedForwardsTotal,
|
|
1565
|
-
droppedForwardsMax,
|
|
1566
|
-
droppedForwardsMaxNode,
|
|
1567
|
-
staleForwardsDroppedTotal,
|
|
1568
|
-
staleForwardsDroppedMax,
|
|
1569
|
-
staleForwardsDroppedMaxNode,
|
|
1570
|
-
dataWriteDropsTotal,
|
|
1571
|
-
dataWriteDropsMax,
|
|
1572
|
-
dataWriteDropsMaxNode,
|
|
1573
|
-
reparentDisconnectTotal,
|
|
1574
|
-
reparentStaleTotal,
|
|
1575
|
-
reparentKickedTotal,
|
|
1576
|
-
publishActiveReparentUpgradeTotal: publishActiveParentUpgrade.reparentUpgrade,
|
|
1577
|
-
publishActiveReparentUpgradeSkipDataTotal: publishActiveParentUpgrade.reparentUpgradeSkipData,
|
|
1578
|
-
publishActiveReparentUpgradeSkipRepairTotal: publishActiveParentUpgrade.reparentUpgradeSkipRepair,
|
|
1579
|
-
publishActiveReparentUpgradeSkipQuietTotal: publishActiveParentUpgrade.reparentUpgradeSkipQuiet,
|
|
1580
|
-
publishActiveParentProbeReqSentTotal: publishActiveParentUpgrade.parentProbeReqSent,
|
|
1581
|
-
publishActiveParentShadowStartTotal: publishActiveParentUpgrade.parentShadowStart,
|
|
1582
|
-
publishActiveParentShadowPromoteTotal: publishActiveParentUpgrade.parentShadowPromote,
|
|
1583
|
-
reparentUpgradeTotal,
|
|
1584
|
-
upgradedPeerHashes,
|
|
1585
|
-
upgradedBranchPeerHashes,
|
|
1586
|
-
reparentUpgradeSkipLeafTotal,
|
|
1587
|
-
reparentUpgradeSkipRepairTotal,
|
|
1588
|
-
reparentUpgradeSkipDataTotal,
|
|
1589
|
-
reparentUpgradeSkipCooldownTotal,
|
|
1590
|
-
reparentUpgradeSkipQuietTotal,
|
|
1591
|
-
reparentUpgradeSkipBudgetTotal,
|
|
1592
|
-
reparentUpgradeSkipCandidateLevelTotal,
|
|
1593
|
-
reparentUpgradeSkipCandidateSlotsTotal,
|
|
1594
|
-
reparentUpgradeSkipCandidatePressureTotal,
|
|
1595
|
-
reparentUpgradeSkipRootPressureTotal,
|
|
1596
|
-
reparentUpgradeSkipProbeNoReplyTotal,
|
|
1597
|
-
reparentUpgradeSkipProbeNotRootedTotal,
|
|
1598
|
-
reparentUpgradeSkipProbeRepairTotal,
|
|
1599
|
-
reparentUpgradeSkipProbeLagTotal,
|
|
1600
|
-
reparentUpgradeSkipProbeOverloadedTotal,
|
|
1601
|
-
reparentUpgradeSkipProbeCooldownTotal,
|
|
1602
|
-
parentProbeReqSentTotal,
|
|
1603
|
-
parentProbeReqReceivedTotal,
|
|
1604
|
-
parentProbeReplySentTotal,
|
|
1605
|
-
parentProbeReplyReceivedTotal,
|
|
1606
|
-
parentUpgradeRootReservationCreatedTotal,
|
|
1607
|
-
parentUpgradeRootReservationConsumedTotal,
|
|
1608
|
-
parentUpgradeRootReservationRejectedTotal,
|
|
1609
|
-
parentUpgradeRootReservationMarginRejectedTotal,
|
|
1610
|
-
parentUpgradeRootReservationBlockedTotal,
|
|
1611
|
-
parentUpgradeRootReservationExpiredTotal,
|
|
1612
|
-
parentShadowStartTotal,
|
|
1613
|
-
parentShadowObserveTotal,
|
|
1614
|
-
parentShadowPromoteTotal,
|
|
1615
|
-
parentShadowResetTotal,
|
|
1616
|
-
parentShadowRejectNoReplyTotal,
|
|
1617
|
-
parentShadowRejectNotRootedTotal,
|
|
1618
|
-
parentShadowRejectCapacityTotal,
|
|
1619
|
-
parentShadowRejectRepairTotal,
|
|
1620
|
-
parentShadowRejectLagTotal,
|
|
1621
|
-
parentShadowRejectOverloadedTotal,
|
|
1622
|
-
parentShadowRejectLevelTotal,
|
|
1623
|
-
treeMaxLevel,
|
|
1624
|
-
treeLevelP95,
|
|
1625
|
-
treeLevelAvg,
|
|
1626
|
-
treeOrphans,
|
|
1627
|
-
treeChildrenP95,
|
|
1628
|
-
treeChildrenMax,
|
|
1629
|
-
treeRootChildren,
|
|
1630
|
-
maxUploadBps,
|
|
1631
|
-
maxUploadFracPct,
|
|
1632
|
-
maxUploadNode,
|
|
1633
|
-
rootUploadBps,
|
|
1634
|
-
rootUploadFracPct,
|
|
1635
|
-
streamQueuedBytesTotal,
|
|
1636
|
-
streamQueuedBytesMax,
|
|
1637
|
-
streamQueuedBytesP95,
|
|
1638
|
-
streamQueuedBytesMaxNode,
|
|
1639
|
-
streamQueuedBytesByLane,
|
|
1640
|
-
churnEvents,
|
|
1641
|
-
churnedPeersTotal,
|
|
1642
|
-
maintDurationMs,
|
|
1643
|
-
maintSamples,
|
|
1644
|
-
maintMaxOrphans,
|
|
1645
|
-
maintOrphanArea,
|
|
1646
|
-
maintRecoveryCount,
|
|
1647
|
-
maintRecoveryP50Ms,
|
|
1648
|
-
maintRecoveryP95Ms,
|
|
1649
|
-
maintReparentsPerMin,
|
|
1650
|
-
maintMaxReparentsPerPeer,
|
|
1651
|
-
maintLevelP95DriftMax,
|
|
1652
|
-
maintChildrenP95DriftMax,
|
|
1653
|
-
overheadFactorData,
|
|
1654
|
-
controlBpp,
|
|
1655
|
-
trackerBpp,
|
|
1656
|
-
repairBpp,
|
|
1657
|
-
earningsTotal,
|
|
1658
|
-
earningsRelayCount,
|
|
1659
|
-
earningsRelayP50,
|
|
1660
|
-
earningsRelayP95,
|
|
1661
|
-
earningsRelayMax,
|
|
1662
|
-
protocolControlSends,
|
|
1663
|
-
protocolControlBytesSent,
|
|
1664
|
-
protocolControlBytesSentJoin,
|
|
1665
|
-
protocolControlBytesSentRepair,
|
|
1666
|
-
protocolControlBytesSentTracker,
|
|
1667
|
-
protocolControlReceives,
|
|
1668
|
-
protocolControlBytesReceived,
|
|
1669
|
-
protocolDataSends,
|
|
1670
|
-
protocolDataPayloadBytesSent,
|
|
1671
|
-
protocolDataReceives,
|
|
1672
|
-
protocolDataPayloadBytesReceived,
|
|
1673
|
-
protocolRepairReqSent,
|
|
1674
|
-
protocolFetchReqSent,
|
|
1675
|
-
protocolIHaveSent,
|
|
1676
|
-
protocolTrackerAnnounceSent,
|
|
1677
|
-
protocolTrackerQuerySent,
|
|
1678
|
-
protocolTrackerReplySent,
|
|
1679
|
-
protocolTrackerFeedbackSent,
|
|
1680
|
-
protocolCacheHitsServed,
|
|
1681
|
-
protocolHoleFillsFromNeighbor,
|
|
1682
|
-
protocolRouteCacheHits,
|
|
1683
|
-
protocolRouteCacheMisses,
|
|
1684
|
-
protocolRouteCacheExpirations,
|
|
1685
|
-
protocolRouteCacheEvictions,
|
|
1686
|
-
protocolRouteProxyQueries,
|
|
1687
|
-
protocolRouteProxyTimeouts,
|
|
1688
|
-
protocolRouteProxyFanout,
|
|
1689
|
-
network: session.network.metrics,
|
|
1690
|
-
};
|
|
1691
|
-
};
|
|
1692
|
-
const abortPromise = timeoutMs > 0
|
|
1693
|
-
? new Promise((_, reject) => {
|
|
1694
|
-
const onAbort = () => {
|
|
1695
|
-
timeoutSignal.removeEventListener("abort", onAbort);
|
|
1696
|
-
const reason = timeoutSignal.reason;
|
|
1697
|
-
reject(reason instanceof Error
|
|
1698
|
-
? reason
|
|
1699
|
-
: new Error(typeof reason === "string"
|
|
1700
|
-
? reason
|
|
1701
|
-
: "fanout-tree-sim aborted"));
|
|
1702
|
-
};
|
|
1703
|
-
if (timeoutSignal.aborted) {
|
|
1704
|
-
onAbort();
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
timeoutSignal.addEventListener("abort", onAbort, { once: true });
|
|
1708
|
-
})
|
|
1709
|
-
: undefined;
|
|
1710
|
-
const result = abortPromise
|
|
1711
|
-
? await Promise.race([run(), abortPromise])
|
|
1712
|
-
: await run();
|
|
1713
|
-
if (profileEnabled && profileCpuStart) {
|
|
1714
|
-
try {
|
|
1715
|
-
profileEld?.disable();
|
|
1716
|
-
}
|
|
1717
|
-
catch {
|
|
1718
|
-
// ignore
|
|
1719
|
-
}
|
|
1720
|
-
const cpu = process.cpuUsage(profileCpuStart);
|
|
1721
|
-
const mem = process.memoryUsage();
|
|
1722
|
-
const p95 = profileEld ? profileEld.percentile(95) / 1e6 : 0;
|
|
1723
|
-
const max = profileEld ? profileEld.max / 1e6 : 0;
|
|
1724
|
-
result.profile = {
|
|
1725
|
-
cpuUserMs: cpu.user / 1_000,
|
|
1726
|
-
cpuSystemMs: cpu.system / 1_000,
|
|
1727
|
-
rssMb: mem.rss / 1e6,
|
|
1728
|
-
heapUsedMb: mem.heapUsed / 1e6,
|
|
1729
|
-
eventLoopDelayP95Ms: p95,
|
|
1730
|
-
eventLoopDelayMaxMs: max,
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
return result;
|
|
1734
|
-
}
|
|
1735
|
-
finally {
|
|
1736
|
-
if (timer)
|
|
1737
|
-
clearTimeout(timer);
|
|
1738
|
-
try {
|
|
1739
|
-
profileEld?.disable();
|
|
1740
|
-
}
|
|
1741
|
-
catch {
|
|
1742
|
-
// ignore
|
|
1743
|
-
}
|
|
1744
|
-
try {
|
|
1745
|
-
await session.stop();
|
|
1746
|
-
}
|
|
1747
|
-
catch {
|
|
1748
|
-
// ignore teardown aborts in the shim
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
};
|
|
1752
|
-
//# sourceMappingURL=fanout-tree-sim-lib.js.map
|