@peerbit/pubsub 5.2.11 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts +3 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js +90 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js +712 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js +1591 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js +297 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts +73 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js +183 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js.map +1 -0
- package/dist/benchmark/fanout-tree-sim-lib.d.ts +101 -0
- package/dist/benchmark/fanout-tree-sim-lib.d.ts.map +1 -1
- package/dist/benchmark/fanout-tree-sim-lib.js +540 -38
- package/dist/benchmark/fanout-tree-sim-lib.js.map +1 -1
- package/dist/benchmark/fanout-tree-sim.d.ts +0 -9
- package/dist/benchmark/fanout-tree-sim.d.ts.map +1 -1
- package/dist/benchmark/fanout-tree-sim.js +369 -28
- package/dist/benchmark/fanout-tree-sim.js.map +1 -1
- package/dist/benchmark/index.js +20 -0
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts +4 -0
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts.map +1 -1
- package/dist/benchmark/pubsub-topic-sim-lib.js +58 -3
- package/dist/benchmark/pubsub-topic-sim-lib.js.map +1 -1
- package/dist/benchmark/pubsub-topic-sim.js +18 -0
- package/dist/benchmark/pubsub-topic-sim.js.map +1 -1
- package/dist/src/debounced-set.d.ts +1 -1
- package/dist/src/debounced-set.d.ts.map +1 -1
- package/dist/src/debounced-set.js +5 -1
- package/dist/src/debounced-set.js.map +1 -1
- package/dist/src/fanout-tree-codec.d.ts +330 -0
- package/dist/src/fanout-tree-codec.d.ts.map +1 -0
- package/dist/src/fanout-tree-codec.js +1209 -0
- package/dist/src/fanout-tree-codec.js.map +1 -0
- package/dist/src/fanout-tree-parent-upgrade.d.ts +117 -0
- package/dist/src/fanout-tree-parent-upgrade.d.ts.map +1 -0
- package/dist/src/fanout-tree-parent-upgrade.js +135 -0
- package/dist/src/fanout-tree-parent-upgrade.js.map +1 -0
- package/dist/src/fanout-tree.d.ts +241 -73
- package/dist/src/fanout-tree.d.ts.map +1 -1
- package/dist/src/fanout-tree.js +2224 -1050
- package/dist/src/fanout-tree.js.map +1 -1
- package/dist/src/index.d.ts +57 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +293 -41
- package/dist/src/index.js.map +1 -1
- package/dist/src/topic-root-control-plane.d.ts +13 -0
- package/dist/src/topic-root-control-plane.d.ts.map +1 -1
- package/dist/src/topic-root-control-plane.js +45 -0
- package/dist/src/topic-root-control-plane.js.map +1 -1
- package/package.json +7 -7
- package/src/debounced-set.ts +5 -0
- package/src/fanout-tree-codec.ts +1580 -0
- package/src/fanout-tree-parent-upgrade.ts +353 -0
- package/src/fanout-tree.ts +4467 -2204
- package/src/index.ts +392 -56
- package/src/topic-root-control-plane.ts +48 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { PreHash, SignatureWithKey } from "@peerbit/crypto";
|
|
2
|
+
import { InMemoryNetwork, InMemorySession, } from "@peerbit/libp2p-test-utils/inmemory-libp2p.js";
|
|
2
3
|
import { delay } from "@peerbit/time";
|
|
3
4
|
import { anySignal } from "any-signal";
|
|
4
5
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
5
6
|
import { FanoutTree } from "../src/index.js";
|
|
6
|
-
import {
|
|
7
|
-
import { int, mulberry32, quantile, runWithConcurrency } from "./sim/bench-utils.js";
|
|
7
|
+
import { parentUpgradeRuntimeOptions } from "./fanout-tree-parent-upgrade-preset.js";
|
|
8
|
+
import { int, mulberry32, quantile, runWithConcurrency, } from "./sim/bench-utils.js";
|
|
8
9
|
class SimFanoutTree extends FanoutTree {
|
|
9
10
|
constructor(c, opts) {
|
|
10
11
|
super(c, opts);
|
|
@@ -25,6 +26,18 @@ class SimFanoutTree extends FanoutTree {
|
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
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
|
+
};
|
|
28
41
|
const pickDistinct = (rng, n, k, exclude) => {
|
|
29
42
|
if (k <= 0)
|
|
30
43
|
return [];
|
|
@@ -56,6 +69,14 @@ export const resolveFanoutTreeSimParams = (input) => {
|
|
|
56
69
|
: msgRate > 0
|
|
57
70
|
? Math.floor(1000 / msgRate)
|
|
58
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";
|
|
59
80
|
return {
|
|
60
81
|
nodes,
|
|
61
82
|
rootIndex: Number(input.rootIndex ?? 0),
|
|
@@ -64,6 +85,8 @@ export const resolveFanoutTreeSimParams = (input) => {
|
|
|
64
85
|
subscribers: Number(input.subscribers ?? subscribersDefault),
|
|
65
86
|
relayFraction: Number(input.relayFraction ?? 0.25),
|
|
66
87
|
messages: Number(input.messages ?? 200),
|
|
88
|
+
secondBatchMessages: Number(input.secondBatchMessages ?? 0),
|
|
89
|
+
secondBatchSettleMs: Number(input.secondBatchSettleMs ?? 0),
|
|
67
90
|
msgRate,
|
|
68
91
|
msgSize: Number(input.msgSize ?? 1024),
|
|
69
92
|
intervalMs,
|
|
@@ -105,12 +128,53 @@ export const resolveFanoutTreeSimParams = (input) => {
|
|
|
105
128
|
input.candidateScoringMode === "ranked-shuffle"
|
|
106
129
|
? input.candidateScoringMode
|
|
107
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)),
|
|
108
168
|
bootstrapEnsureIntervalMs: Number(input.bootstrapEnsureIntervalMs ?? -1),
|
|
109
169
|
trackerQueryIntervalMs: Number(input.trackerQueryIntervalMs ?? -1),
|
|
110
170
|
joinAttemptsPerRound: Number(input.joinAttemptsPerRound ?? -1),
|
|
111
171
|
candidateCooldownMs: Number(input.candidateCooldownMs ?? -1),
|
|
112
172
|
joinPhases: Boolean(input.joinPhases ?? false),
|
|
113
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),
|
|
114
178
|
maxLatencySamples: Number(input.maxLatencySamples ?? 1_000_000),
|
|
115
179
|
profile: Boolean(input.profile ?? false),
|
|
116
180
|
progress: Boolean(input.progress ?? false),
|
|
@@ -131,6 +195,8 @@ export const resolveFanoutTreeSimParams = (input) => {
|
|
|
131
195
|
assertAttachP95Ms: Number(input.assertAttachP95Ms ?? 0),
|
|
132
196
|
assertMaxTreeLevelP95: Number(input.assertMaxTreeLevelP95 ?? 0),
|
|
133
197
|
assertMaxFormationScore: Number(input.assertMaxFormationScore ?? 0),
|
|
198
|
+
assertMinReparentUpgradeTotal: Number(input.assertMinReparentUpgradeTotal ?? 0),
|
|
199
|
+
assertMinActiveShadowPromoteTotal: Number(input.assertMinActiveShadowPromoteTotal ?? 0),
|
|
134
200
|
assertMaxOrphans: Number(input.assertMaxOrphans ?? 0),
|
|
135
201
|
assertRecoveryP95Ms: Number(input.assertRecoveryP95Ms ?? 0),
|
|
136
202
|
assertMaxReparentsPerMin: Number(input.assertMaxReparentsPerMin ?? 0),
|
|
@@ -142,12 +208,22 @@ export const formatFanoutTreeSimResult = (r) => {
|
|
|
142
208
|
return [
|
|
143
209
|
"fanout-tree-sim",
|
|
144
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
|
+
: []),
|
|
145
216
|
`joined=${r.joinedCount}/${r.subscriberCount} (${r.joinedPct.toFixed(2)}%)`,
|
|
146
217
|
`join: ${(r.joinMs / 1000).toFixed(3)}s`,
|
|
147
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)}`,
|
|
148
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)}`,
|
|
149
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}`,
|
|
150
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
|
+
: []),
|
|
151
227
|
`churn: everyMs=${p.churnEveryMs} downMs=${p.churnDownMs} fraction=${p.churnFraction} events=${r.churnEvents} peers=${r.churnedPeersTotal}`,
|
|
152
228
|
...(r.maintSamples > 0
|
|
153
229
|
? [
|
|
@@ -160,13 +236,23 @@ export const formatFanoutTreeSimResult = (r) => {
|
|
|
160
236
|
: `deadline=off${p.maxDataAgeMs > 0 ? ` maxAgeMs=${p.maxDataAgeMs}` : ""}`,
|
|
161
237
|
`latencyMs p50=${r.latencyP50.toFixed(1)} p95=${r.latencyP95.toFixed(1)} p99=${r.latencyP99.toFixed(1)} max=${r.latencyMax.toFixed(1)}`,
|
|
162
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 ?? "-"}`,
|
|
163
|
-
|
|
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}`,
|
|
164
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}`,
|
|
165
|
-
`upload: max=${r.maxUploadBps} B/s (${r.maxUploadFracPct.toFixed(1)}% of cap) node=${r.maxUploadNode ?? "-"}`,
|
|
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)`,
|
|
166
252
|
`stream: queuedBytes total=${r.streamQueuedBytesTotal} max=${r.streamQueuedBytesMax} p95=${r.streamQueuedBytesP95.toFixed(0)} node=${r.streamQueuedBytesMaxNode ?? "-"} lanes=${r.streamQueuedBytesByLane.join(",")}`,
|
|
167
253
|
`overhead: dataFactor=${r.overheadFactorData.toFixed(3)} (sentPayloadBytes / ideal)`,
|
|
168
254
|
`economics: earningsTotal=${r.earningsTotal} relayCount=${r.earningsRelayCount} p50=${r.earningsRelayP50} p95=${r.earningsRelayP95} max=${r.earningsRelayMax}`,
|
|
169
|
-
`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}
|
|
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}`,
|
|
170
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}`,
|
|
171
257
|
...(r.profile
|
|
172
258
|
? [
|
|
@@ -296,7 +382,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
296
382
|
role: "root",
|
|
297
383
|
msgRate: params.msgRate,
|
|
298
384
|
msgSize: params.msgSize,
|
|
299
|
-
...(params.maxDataAgeMs > 0
|
|
385
|
+
...(params.maxDataAgeMs > 0
|
|
386
|
+
? { maxDataAgeMs: params.maxDataAgeMs }
|
|
387
|
+
: {}),
|
|
300
388
|
uploadLimitBps: params.rootUploadLimitBps,
|
|
301
389
|
maxChildren: params.rootMaxChildren,
|
|
302
390
|
bidPerByte: params.bidPerByte,
|
|
@@ -317,7 +405,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
317
405
|
? { neighborAnnounceIntervalMs: params.neighborAnnounceIntervalMs }
|
|
318
406
|
: {}),
|
|
319
407
|
...(params.neighborMeshRefreshIntervalMs >= 0
|
|
320
|
-
? {
|
|
408
|
+
? {
|
|
409
|
+
neighborMeshRefreshIntervalMs: params.neighborMeshRefreshIntervalMs,
|
|
410
|
+
}
|
|
321
411
|
: {}),
|
|
322
412
|
...(params.neighborHaveTtlMs >= 0
|
|
323
413
|
? { neighborHaveTtlMs: params.neighborHaveTtlMs }
|
|
@@ -343,7 +433,8 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
343
433
|
const mu = process.memoryUsage();
|
|
344
434
|
const rssMb = mu.rss / (1024 * 1024);
|
|
345
435
|
const heapUsedMb = mu.heapUsed / (1024 * 1024);
|
|
346
|
-
const openConns = Math.max(0, network.metrics.connectionsOpened -
|
|
436
|
+
const openConns = Math.max(0, network.metrics.connectionsOpened -
|
|
437
|
+
network.metrics.connectionsClosed);
|
|
347
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)}`);
|
|
348
439
|
}, progressEveryMs);
|
|
349
440
|
joinProgressTimer.unref?.();
|
|
@@ -355,15 +446,21 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
355
446
|
await node.joinChannel(params.topic, rootId, {
|
|
356
447
|
msgRate: params.msgRate,
|
|
357
448
|
msgSize: params.msgSize,
|
|
358
|
-
...(params.maxDataAgeMs > 0
|
|
449
|
+
...(params.maxDataAgeMs > 0
|
|
450
|
+
? { maxDataAgeMs: params.maxDataAgeMs }
|
|
451
|
+
: {}),
|
|
359
452
|
uploadLimitBps: isRelay ? params.relayUploadLimitBps : 0,
|
|
360
453
|
maxChildren: isRelay ? params.relayMaxChildren : 0,
|
|
361
|
-
bidPerByte: isRelay
|
|
454
|
+
bidPerByte: isRelay
|
|
455
|
+
? params.bidPerByteRelay
|
|
456
|
+
: params.bidPerByteLeaf,
|
|
362
457
|
allowKick: params.allowKick,
|
|
363
458
|
repair: params.repair,
|
|
364
459
|
repairWindowMessages: params.repairWindowMessages,
|
|
365
460
|
...(params.repairMaxBackfillMessages >= 0
|
|
366
|
-
? {
|
|
461
|
+
? {
|
|
462
|
+
repairMaxBackfillMessages: params.repairMaxBackfillMessages,
|
|
463
|
+
}
|
|
367
464
|
: {}),
|
|
368
465
|
repairIntervalMs: params.repairIntervalMs,
|
|
369
466
|
repairMaxPerReq: params.repairMaxPerReq,
|
|
@@ -373,10 +470,14 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
373
470
|
? { neighborMeshPeers: params.neighborMeshPeers }
|
|
374
471
|
: {}),
|
|
375
472
|
...(params.neighborAnnounceIntervalMs >= 0
|
|
376
|
-
? {
|
|
473
|
+
? {
|
|
474
|
+
neighborAnnounceIntervalMs: params.neighborAnnounceIntervalMs,
|
|
475
|
+
}
|
|
377
476
|
: {}),
|
|
378
477
|
...(params.neighborMeshRefreshIntervalMs >= 0
|
|
379
|
-
? {
|
|
478
|
+
? {
|
|
479
|
+
neighborMeshRefreshIntervalMs: params.neighborMeshRefreshIntervalMs,
|
|
480
|
+
}
|
|
380
481
|
: {}),
|
|
381
482
|
...(params.neighborHaveTtlMs >= 0
|
|
382
483
|
? { neighborHaveTtlMs: params.neighborHaveTtlMs }
|
|
@@ -389,7 +490,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
389
490
|
: {}),
|
|
390
491
|
}, {
|
|
391
492
|
timeoutMs: Math.max(10_000, Math.min(120_000, timeoutMs || 120_000)),
|
|
392
|
-
...(params.maxDataAgeMs > 0
|
|
493
|
+
...(params.maxDataAgeMs > 0
|
|
494
|
+
? { staleAfterMs: params.maxDataAgeMs }
|
|
495
|
+
: {}),
|
|
393
496
|
...(params.joinReqTimeoutMs >= 0
|
|
394
497
|
? { joinReqTimeoutMs: params.joinReqTimeoutMs }
|
|
395
498
|
: {}),
|
|
@@ -397,8 +500,11 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
397
500
|
? { candidateShuffleTopK: params.candidateShuffleTopK }
|
|
398
501
|
: {}),
|
|
399
502
|
candidateScoringMode: params.candidateScoringMode,
|
|
503
|
+
...parentUpgradeRuntimeOptions(params),
|
|
400
504
|
...(params.bootstrapEnsureIntervalMs >= 0
|
|
401
|
-
? {
|
|
505
|
+
? {
|
|
506
|
+
bootstrapEnsureIntervalMs: params.bootstrapEnsureIntervalMs,
|
|
507
|
+
}
|
|
402
508
|
: {}),
|
|
403
509
|
...(params.trackerQueryIntervalMs >= 0
|
|
404
510
|
? { trackerQueryIntervalMs: params.trackerQueryIntervalMs }
|
|
@@ -465,7 +571,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
465
571
|
const openConns = Math.max(0, network.metrics.connectionsOpened - network.metrics.connectionsClosed);
|
|
466
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}`);
|
|
467
573
|
}
|
|
468
|
-
const attachDurations = attachDurationsByPos
|
|
574
|
+
const attachDurations = attachDurationsByPos
|
|
575
|
+
.filter((d) => d >= 0)
|
|
576
|
+
.sort((a, b) => a - b);
|
|
469
577
|
const attachSamples = attachDurations.length;
|
|
470
578
|
const attachP50 = attachSamples > 0 ? quantile(attachDurations, 0.5) : NaN;
|
|
471
579
|
const attachP95 = attachSamples > 0 ? quantile(attachDurations, 0.95) : NaN;
|
|
@@ -479,13 +587,17 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
479
587
|
const computeTreeShapeStats = () => {
|
|
480
588
|
const levels = [];
|
|
481
589
|
const levelByIndex = new Array(params.nodes).fill(NaN);
|
|
590
|
+
const childrenByHash = new Map();
|
|
482
591
|
const childrenCounts = [];
|
|
483
592
|
let treeOrphans = 0;
|
|
484
593
|
let treeRootChildren = 0;
|
|
485
594
|
for (let i = 0; i < session.peers.length; i++) {
|
|
595
|
+
const hash = session.peers[i].services.fanout.publicKeyHash;
|
|
486
596
|
const s = session.peers[i].services.fanout.getChannelStats(params.topic, rootId);
|
|
487
597
|
if (!s)
|
|
488
598
|
continue;
|
|
599
|
+
if (!childrenByHash.has(hash))
|
|
600
|
+
childrenByHash.set(hash, []);
|
|
489
601
|
if (Number.isFinite(s.level)) {
|
|
490
602
|
levels.push(s.level);
|
|
491
603
|
levelByIndex[i] = s.level;
|
|
@@ -493,6 +605,11 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
493
605
|
if (s.effectiveMaxChildren > 0) {
|
|
494
606
|
childrenCounts.push(s.children);
|
|
495
607
|
}
|
|
608
|
+
if (s.parent) {
|
|
609
|
+
const children = childrenByHash.get(s.parent) ?? [];
|
|
610
|
+
children.push(hash);
|
|
611
|
+
childrenByHash.set(s.parent, children);
|
|
612
|
+
}
|
|
496
613
|
if (s.level === 0) {
|
|
497
614
|
treeRootChildren = s.children;
|
|
498
615
|
}
|
|
@@ -504,9 +621,13 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
504
621
|
childrenCounts.sort((a, b) => a - b);
|
|
505
622
|
const treeMaxLevel = levels.length > 0 ? levels[levels.length - 1] : 0;
|
|
506
623
|
const treeLevelP95 = levels.length > 0 ? quantile(levels, 0.95) : 0;
|
|
507
|
-
const treeLevelAvg = levels.length > 0
|
|
624
|
+
const treeLevelAvg = levels.length > 0
|
|
625
|
+
? levels.reduce((a, b) => a + b, 0) / levels.length
|
|
626
|
+
: 0;
|
|
508
627
|
const treeChildrenP95 = childrenCounts.length > 0 ? quantile(childrenCounts, 0.95) : 0;
|
|
509
|
-
const treeChildrenMax = childrenCounts.length > 0
|
|
628
|
+
const treeChildrenMax = childrenCounts.length > 0
|
|
629
|
+
? childrenCounts[childrenCounts.length - 1]
|
|
630
|
+
: 0;
|
|
510
631
|
return {
|
|
511
632
|
treeMaxLevel,
|
|
512
633
|
treeLevelP95,
|
|
@@ -516,6 +637,7 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
516
637
|
treeChildrenMax,
|
|
517
638
|
treeRootChildren,
|
|
518
639
|
levelByIndex,
|
|
640
|
+
childrenByHash,
|
|
519
641
|
};
|
|
520
642
|
};
|
|
521
643
|
const formationTree = computeTreeShapeStats();
|
|
@@ -557,7 +679,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
557
679
|
.map((i) => underlayDist[i])
|
|
558
680
|
.filter((d) => Number.isFinite(d))
|
|
559
681
|
.sort((a, b) => a - b);
|
|
560
|
-
const formationUnderlayDistP95 = formationUnderlayDists.length > 0
|
|
682
|
+
const formationUnderlayDistP95 = formationUnderlayDists.length > 0
|
|
683
|
+
? quantile(formationUnderlayDists, 0.95)
|
|
684
|
+
: NaN;
|
|
561
685
|
const formationUnderlayDistMax = formationUnderlayDists.length > 0
|
|
562
686
|
? formationUnderlayDists[formationUnderlayDists.length - 1]
|
|
563
687
|
: NaN;
|
|
@@ -565,14 +689,20 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
565
689
|
.map((i) => {
|
|
566
690
|
const overlay = formationTree.levelByIndex[i];
|
|
567
691
|
const under = underlayDist[i];
|
|
568
|
-
if (!Number.isFinite(overlay) ||
|
|
692
|
+
if (!Number.isFinite(overlay) ||
|
|
693
|
+
!Number.isFinite(under) ||
|
|
694
|
+
under <= 0)
|
|
569
695
|
return NaN;
|
|
570
696
|
return overlay / under;
|
|
571
697
|
})
|
|
572
698
|
.filter((x) => Number.isFinite(x))
|
|
573
699
|
.sort((a, b) => a - b);
|
|
574
|
-
const formationStretchP95 = formationStretches.length > 0
|
|
575
|
-
|
|
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;
|
|
576
706
|
const formationOrphanPct = joinedCount === 0 ? 0 : (100 * formationTree.treeOrphans) / joinedCount;
|
|
577
707
|
const formationStretchPenalty = Number.isFinite(formationStretchP95)
|
|
578
708
|
? Math.max(0, formationStretchP95 - 1) * 10
|
|
@@ -582,10 +712,16 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
582
712
|
formationOrphanPct +
|
|
583
713
|
formationStretchPenalty;
|
|
584
714
|
const churnController = new AbortController();
|
|
585
|
-
const churnSignal = anySignal([
|
|
715
|
+
const churnSignal = anySignal([
|
|
716
|
+
timeoutSignal,
|
|
717
|
+
churnController.signal,
|
|
718
|
+
]);
|
|
586
719
|
let churnEvents = 0;
|
|
587
720
|
let churnedPeersTotal = 0;
|
|
588
|
-
const wantsMaintenance = (params.churnEveryMs > 0 &&
|
|
721
|
+
const wantsMaintenance = (params.churnEveryMs > 0 &&
|
|
722
|
+
params.churnDownMs > 0 &&
|
|
723
|
+
params.churnFraction > 0) ||
|
|
724
|
+
params.parentUpgradeIntervalMs > 0 ||
|
|
589
725
|
params.assertMaxOrphans > 0 ||
|
|
590
726
|
params.assertMaxOrphanArea > 0 ||
|
|
591
727
|
params.assertRecoveryP95Ms > 0 ||
|
|
@@ -607,20 +743,29 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
607
743
|
let maintReparentsTotal = 0;
|
|
608
744
|
let maintMaxReparentsPerPeer = 0;
|
|
609
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;
|
|
610
750
|
const publishAt = new Map();
|
|
611
751
|
const joinedHashList = [...joinedHashes];
|
|
612
752
|
const hashToIndex = new Map();
|
|
613
753
|
for (let i = 0; i < joinedHashList.length; i++) {
|
|
614
754
|
hashToIndex.set(joinedHashList[i], i);
|
|
615
755
|
}
|
|
616
|
-
const bitsetBytes = Math.ceil(
|
|
756
|
+
const bitsetBytes = Math.ceil(totalMessages / 8);
|
|
617
757
|
const receivedBits = joinedHashList.map(() => new Uint8Array(bitsetBytes));
|
|
618
758
|
const receivedCounts = new Uint32Array(joinedHashList.length);
|
|
759
|
+
const secondBatchReceivedCounts = new Uint32Array(joinedHashList.length);
|
|
619
760
|
let duplicates = 0;
|
|
620
761
|
let deliveredWithinDeadline = 0;
|
|
621
762
|
let deliveredSamples = [];
|
|
763
|
+
let secondBatchDeliveredWithinDeadline = 0;
|
|
764
|
+
let secondBatchDeliveredSamples = [];
|
|
765
|
+
const secondBatchDeliveredSamplesByPeer = joinedHashList.map(() => []);
|
|
622
766
|
const sampleCap = Math.max(1, Math.floor(params.maxLatencySamples));
|
|
623
767
|
let sampleSeen = 0;
|
|
768
|
+
let secondBatchSampleSeen = 0;
|
|
624
769
|
const makeOnData = (localHash) => (ev) => {
|
|
625
770
|
const d = ev?.detail;
|
|
626
771
|
if (!d)
|
|
@@ -633,7 +778,7 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
633
778
|
const index = hashToIndex.get(localHash);
|
|
634
779
|
if (index == null)
|
|
635
780
|
return; // not joined / not tracked
|
|
636
|
-
if (seq >=
|
|
781
|
+
if (seq >= totalMessages)
|
|
637
782
|
return;
|
|
638
783
|
const bits = receivedBits[index];
|
|
639
784
|
const byteIndex = seq >>> 3;
|
|
@@ -646,6 +791,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
646
791
|
}
|
|
647
792
|
bits[byteIndex] |= mask;
|
|
648
793
|
receivedCounts[index] += 1;
|
|
794
|
+
if (seq >= secondBatchStartSeq) {
|
|
795
|
+
secondBatchReceivedCounts[index] += 1;
|
|
796
|
+
}
|
|
649
797
|
const sentAt = publishAt.get(seq);
|
|
650
798
|
if (sentAt != null) {
|
|
651
799
|
const latency = Date.now() - sentAt;
|
|
@@ -661,6 +809,21 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
661
809
|
if (j < sampleCap)
|
|
662
810
|
deliveredSamples[j] = latency;
|
|
663
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
|
+
}
|
|
664
827
|
}
|
|
665
828
|
};
|
|
666
829
|
for (let i = 0; i < subscriberIndices.length; i++) {
|
|
@@ -830,18 +993,105 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
830
993
|
}));
|
|
831
994
|
}
|
|
832
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);
|
|
833
1035
|
const publishStart = Date.now();
|
|
1036
|
+
let publishActiveMs = 0;
|
|
834
1037
|
if (wantsMaintenance) {
|
|
835
1038
|
for (const p of session.peers) {
|
|
836
1039
|
const nodeHash = p.services.fanout.publicKeyHash;
|
|
837
1040
|
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
838
|
-
reparentBaselineByHash.set(nodeHash, m.reparentDisconnect +
|
|
1041
|
+
reparentBaselineByHash.set(nodeHash, m.reparentDisconnect +
|
|
1042
|
+
m.reparentStale +
|
|
1043
|
+
m.reparentKicked +
|
|
1044
|
+
m.reparentUpgrade);
|
|
839
1045
|
}
|
|
840
1046
|
}
|
|
841
1047
|
const maintenancePromise = maintenanceLoop().catch(() => { });
|
|
842
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;
|
|
843
1093
|
try {
|
|
844
|
-
for (let seq = 0; seq <
|
|
1094
|
+
for (let seq = 0; seq < firstBatchMessages; seq++) {
|
|
845
1095
|
if (timeoutSignal.aborted) {
|
|
846
1096
|
throw timeoutSignal.reason ?? new Error("fanout-tree-sim aborted");
|
|
847
1097
|
}
|
|
@@ -853,17 +1103,58 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
853
1103
|
}
|
|
854
1104
|
}
|
|
855
1105
|
finally {
|
|
1106
|
+
if (firstBatchMessages > 0) {
|
|
1107
|
+
publishActiveMs += Math.max(0, Date.now() - publishStart);
|
|
1108
|
+
}
|
|
1109
|
+
publishActiveParentUpgrade = diffParentUpgradeActivity(collectParentUpgradeActivity(), publishStartParentUpgrade);
|
|
856
1110
|
churnController.abort();
|
|
857
1111
|
await churnPromise;
|
|
858
1112
|
churnSignal.clear?.();
|
|
1113
|
+
await lateRootDuringPublishPromise;
|
|
859
1114
|
}
|
|
860
|
-
|
|
1115
|
+
let publishDone = Date.now();
|
|
861
1116
|
// Signal end-of-stream so subscribers can detect tail gaps and repair.
|
|
862
|
-
if (params.repair &&
|
|
863
|
-
await root.publishEnd(params.topic, rootId,
|
|
1117
|
+
if (params.repair && firstBatchMessages > 0) {
|
|
1118
|
+
await root.publishEnd(params.topic, rootId, firstBatchMessages);
|
|
864
1119
|
}
|
|
865
1120
|
if (params.settleMs > 0) {
|
|
866
|
-
|
|
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
|
+
}
|
|
867
1158
|
}
|
|
868
1159
|
maintenanceController.abort();
|
|
869
1160
|
await maintenancePromise;
|
|
@@ -876,7 +1167,10 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
876
1167
|
for (const p of session.peers) {
|
|
877
1168
|
const nodeHash = p.services.fanout.publicKeyHash;
|
|
878
1169
|
const m = p.services.fanout.getChannelMetrics(params.topic, rootId);
|
|
879
|
-
const total = m.reparentDisconnect +
|
|
1170
|
+
const total = m.reparentDisconnect +
|
|
1171
|
+
m.reparentStale +
|
|
1172
|
+
m.reparentKicked +
|
|
1173
|
+
m.reparentUpgrade;
|
|
880
1174
|
const base = reparentBaselineByHash.get(nodeHash) ?? 0;
|
|
881
1175
|
const delta = Math.max(0, total - base);
|
|
882
1176
|
maintReparentsTotal += delta;
|
|
@@ -887,13 +1181,23 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
887
1181
|
const maintReparentsPerMin = maintDurationMin > 0 ? maintReparentsTotal / maintDurationMin : 0;
|
|
888
1182
|
const maintOrphanArea = maintOrphanAreaMs / 1_000;
|
|
889
1183
|
// Compute delivery
|
|
890
|
-
const expected = joinedCount *
|
|
1184
|
+
const expected = joinedCount * totalMessages;
|
|
891
1185
|
let delivered = 0;
|
|
892
1186
|
for (const c of receivedCounts)
|
|
893
1187
|
delivered += c;
|
|
1188
|
+
const secondBatchExpected = joinedCount * secondBatchMessages;
|
|
1189
|
+
let secondBatchDelivered = 0;
|
|
1190
|
+
for (const c of secondBatchReceivedCounts)
|
|
1191
|
+
secondBatchDelivered += c;
|
|
894
1192
|
const joinedPct = subscriberCount === 0 ? 100 : (100 * joinedCount) / subscriberCount;
|
|
895
1193
|
const deliveredPct = expected === 0 ? 100 : (100 * delivered) / expected;
|
|
896
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;
|
|
897
1201
|
// Internal protocol drops (from upload shaping / overload logic)
|
|
898
1202
|
let droppedForwardsTotal = 0;
|
|
899
1203
|
let droppedForwardsMax = 0;
|
|
@@ -967,6 +1271,11 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
967
1271
|
let maxUploadFracPct = 0;
|
|
968
1272
|
let maxUploadNode;
|
|
969
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;
|
|
970
1279
|
for (const [hash, pm] of session.network.peerMetricsByHash) {
|
|
971
1280
|
const cap = uploadCapByHash.get(hash);
|
|
972
1281
|
if (!cap || cap <= 0)
|
|
@@ -979,6 +1288,15 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
979
1288
|
}
|
|
980
1289
|
}
|
|
981
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
|
+
}
|
|
982
1300
|
let protocolControlSends = 0;
|
|
983
1301
|
let protocolControlBytesSent = 0;
|
|
984
1302
|
let protocolControlBytesSentJoin = 0;
|
|
@@ -993,6 +1311,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
993
1311
|
let protocolRepairReqSent = 0;
|
|
994
1312
|
let protocolFetchReqSent = 0;
|
|
995
1313
|
let protocolIHaveSent = 0;
|
|
1314
|
+
let protocolTrackerAnnounceSent = 0;
|
|
1315
|
+
let protocolTrackerQuerySent = 0;
|
|
1316
|
+
let protocolTrackerReplySent = 0;
|
|
996
1317
|
let protocolTrackerFeedbackSent = 0;
|
|
997
1318
|
let protocolCacheHitsServed = 0;
|
|
998
1319
|
let protocolHoleFillsFromNeighbor = 0;
|
|
@@ -1012,6 +1333,45 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1012
1333
|
let reparentDisconnectTotal = 0;
|
|
1013
1334
|
let reparentStaleTotal = 0;
|
|
1014
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;
|
|
1015
1375
|
let earningsTotal = 0;
|
|
1016
1376
|
const earningsByHash = new Map();
|
|
1017
1377
|
for (const p of session.peers) {
|
|
@@ -1030,6 +1390,60 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1030
1390
|
reparentDisconnectTotal += m.reparentDisconnect;
|
|
1031
1391
|
reparentStaleTotal += m.reparentStale;
|
|
1032
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;
|
|
1033
1447
|
earningsTotal += m.earnings;
|
|
1034
1448
|
earningsByHash.set(nodeHash, m.earnings);
|
|
1035
1449
|
protocolControlSends += m.controlSends;
|
|
@@ -1046,6 +1460,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1046
1460
|
protocolRepairReqSent += m.repairReqSent;
|
|
1047
1461
|
protocolFetchReqSent += m.fetchReqSent;
|
|
1048
1462
|
protocolIHaveSent += m.ihaveSent;
|
|
1463
|
+
protocolTrackerAnnounceSent += m.trackerAnnounceSent;
|
|
1464
|
+
protocolTrackerQuerySent += m.trackerQuerySent;
|
|
1465
|
+
protocolTrackerReplySent += m.trackerReplySent;
|
|
1049
1466
|
protocolTrackerFeedbackSent += m.trackerFeedbackSent;
|
|
1050
1467
|
protocolCacheHitsServed += m.cacheHitsServed;
|
|
1051
1468
|
protocolHoleFillsFromNeighbor += m.holeFillsFromNeighbor;
|
|
@@ -1057,8 +1474,25 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1057
1474
|
protocolRouteProxyTimeouts += m.routeProxyTimeouts;
|
|
1058
1475
|
protocolRouteProxyFanout += m.routeProxyFanout;
|
|
1059
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];
|
|
1060
1492
|
const relayHashes = [...uploadCapByHash.keys()];
|
|
1061
|
-
const relayEarnings = relayHashes
|
|
1493
|
+
const relayEarnings = relayHashes
|
|
1494
|
+
.map((h) => earningsByHash.get(h) ?? 0)
|
|
1495
|
+
.sort((a, b) => a - b);
|
|
1062
1496
|
const earningsRelayCount = relayEarnings.length;
|
|
1063
1497
|
const earningsRelayP50 = earningsRelayCount > 0 ? quantile(relayEarnings, 0.5) : 0;
|
|
1064
1498
|
const earningsRelayP95 = earningsRelayCount > 0 ? quantile(relayEarnings, 0.95) : 0;
|
|
@@ -1068,7 +1502,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1068
1502
|
? 1
|
|
1069
1503
|
: protocolDataPayloadBytesSent / idealPayloadBytes;
|
|
1070
1504
|
const deliveredPayloadBytes = delivered * Math.max(0, params.msgSize);
|
|
1071
|
-
const controlBpp = deliveredPayloadBytes <= 0
|
|
1505
|
+
const controlBpp = deliveredPayloadBytes <= 0
|
|
1506
|
+
? 0
|
|
1507
|
+
: protocolControlBytesSent / deliveredPayloadBytes;
|
|
1072
1508
|
const trackerBpp = deliveredPayloadBytes <= 0
|
|
1073
1509
|
? 0
|
|
1074
1510
|
: protocolControlBytesSentTracker / deliveredPayloadBytes;
|
|
@@ -1101,13 +1537,25 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1101
1537
|
formationStretchP95,
|
|
1102
1538
|
formationStretchMax,
|
|
1103
1539
|
formationScore,
|
|
1104
|
-
publishMs:
|
|
1540
|
+
publishMs: publishActiveMs,
|
|
1105
1541
|
expected,
|
|
1106
1542
|
delivered,
|
|
1107
1543
|
deliveredPct,
|
|
1108
1544
|
deliveredWithinDeadline,
|
|
1109
1545
|
deliveredWithinDeadlinePct,
|
|
1110
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,
|
|
1111
1559
|
latencySamples: deliveredSamples.length,
|
|
1112
1560
|
latencyP50: quantile(deliveredSamples, 0.5),
|
|
1113
1561
|
latencyP95: quantile(deliveredSamples, 0.95),
|
|
@@ -1125,6 +1573,53 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1125
1573
|
reparentDisconnectTotal,
|
|
1126
1574
|
reparentStaleTotal,
|
|
1127
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,
|
|
1128
1623
|
treeMaxLevel,
|
|
1129
1624
|
treeLevelP95,
|
|
1130
1625
|
treeLevelAvg,
|
|
@@ -1135,6 +1630,8 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1135
1630
|
maxUploadBps,
|
|
1136
1631
|
maxUploadFracPct,
|
|
1137
1632
|
maxUploadNode,
|
|
1633
|
+
rootUploadBps,
|
|
1634
|
+
rootUploadFracPct,
|
|
1138
1635
|
streamQueuedBytesTotal,
|
|
1139
1636
|
streamQueuedBytesMax,
|
|
1140
1637
|
streamQueuedBytesP95,
|
|
@@ -1176,6 +1673,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1176
1673
|
protocolRepairReqSent,
|
|
1177
1674
|
protocolFetchReqSent,
|
|
1178
1675
|
protocolIHaveSent,
|
|
1676
|
+
protocolTrackerAnnounceSent,
|
|
1677
|
+
protocolTrackerQuerySent,
|
|
1678
|
+
protocolTrackerReplySent,
|
|
1179
1679
|
protocolTrackerFeedbackSent,
|
|
1180
1680
|
protocolCacheHitsServed,
|
|
1181
1681
|
protocolHoleFillsFromNeighbor,
|
|
@@ -1207,7 +1707,9 @@ export const runFanoutTreeSim = async (input) => {
|
|
|
1207
1707
|
timeoutSignal.addEventListener("abort", onAbort, { once: true });
|
|
1208
1708
|
})
|
|
1209
1709
|
: undefined;
|
|
1210
|
-
const result = abortPromise
|
|
1710
|
+
const result = abortPromise
|
|
1711
|
+
? await Promise.race([run(), abortPromise])
|
|
1712
|
+
: await run();
|
|
1211
1713
|
if (profileEnabled && profileCpuStart) {
|
|
1212
1714
|
try {
|
|
1213
1715
|
profileEld?.disable();
|