@streamr/dht 103.6.0-rc.0 → 103.8.0-rc.3
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/exports-browser.cjs +214 -4
- package/dist/exports-browser.cjs.map +1 -1
- package/dist/exports-browser.d.ts +15 -10
- package/dist/exports-browser.js +213 -5
- package/dist/exports-browser.js.map +1 -1
- package/dist/exports-nodejs.cjs +66 -2
- package/dist/exports-nodejs.cjs.map +1 -1
- package/dist/exports-nodejs.d.ts +15 -10
- package/dist/exports-nodejs.js +65 -3
- package/dist/exports-nodejs.js.map +1 -1
- package/package.json +7 -7
package/dist/exports-browser.cjs
CHANGED
|
@@ -261,6 +261,68 @@ class SendFailed extends Err {
|
|
|
261
261
|
constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
let enabled = true;
|
|
265
|
+
function setGapDiagnosticsEnabled(val) {
|
|
266
|
+
enabled = val;
|
|
267
|
+
globalThis.__dhtGapDiagEnabled = val;
|
|
268
|
+
}
|
|
269
|
+
function isGapDiagnosticsEnabled() {
|
|
270
|
+
return enabled;
|
|
271
|
+
}
|
|
272
|
+
const SUMMARY_INTERVAL_MS = 2000;
|
|
273
|
+
const accumulators = new Map();
|
|
274
|
+
function logGapDiagnosticSampled(layer, opts = {}) {
|
|
275
|
+
if (!enabled && !globalThis.__dhtGapDiagEnabled)
|
|
276
|
+
return;
|
|
277
|
+
const now = performance.now();
|
|
278
|
+
const threshold = opts.outlierThresholdMs ?? 30;
|
|
279
|
+
let acc = accumulators.get(layer);
|
|
280
|
+
if (acc === undefined) {
|
|
281
|
+
acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
|
|
282
|
+
accumulators.set(layer, acc);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const deltaMs = now - acc.lastEventMs;
|
|
286
|
+
acc.lastEventMs = now;
|
|
287
|
+
acc.count++;
|
|
288
|
+
if (deltaMs > acc.maxDeltaMs)
|
|
289
|
+
acc.maxDeltaMs = deltaMs;
|
|
290
|
+
acc.sumDeltaMs += deltaMs;
|
|
291
|
+
if (deltaMs > threshold)
|
|
292
|
+
acc.outlierCount++;
|
|
293
|
+
if (deltaMs > threshold) {
|
|
294
|
+
const payload = {
|
|
295
|
+
layer,
|
|
296
|
+
timestampMs: now,
|
|
297
|
+
deltaMs: +deltaMs.toFixed(2),
|
|
298
|
+
detail: opts.detail,
|
|
299
|
+
};
|
|
300
|
+
// eslint-disable-next-line no-console
|
|
301
|
+
console.log('[gap-diagnostics]', JSON.stringify(payload));
|
|
302
|
+
}
|
|
303
|
+
if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
|
|
304
|
+
const summaryDetail = {
|
|
305
|
+
count: acc.count,
|
|
306
|
+
meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
|
|
307
|
+
maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
|
|
308
|
+
outlierCount: acc.outlierCount,
|
|
309
|
+
periodMs: +(now - acc.lastReportMs).toFixed(1),
|
|
310
|
+
};
|
|
311
|
+
const summary = {
|
|
312
|
+
layer: `${layer}.summary`,
|
|
313
|
+
timestampMs: now,
|
|
314
|
+
detail: summaryDetail,
|
|
315
|
+
};
|
|
316
|
+
// eslint-disable-next-line no-console
|
|
317
|
+
console.log('[gap-diagnostics]', JSON.stringify(summary));
|
|
318
|
+
acc.count = 0;
|
|
319
|
+
acc.sumDeltaMs = 0;
|
|
320
|
+
acc.maxDeltaMs = 0;
|
|
321
|
+
acc.outlierCount = 0;
|
|
322
|
+
acc.lastReportMs = now;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
264
326
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
265
327
|
class Empty$Type extends runtime.MessageType {
|
|
266
328
|
constructor() {
|
|
@@ -2305,6 +2367,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2305
2367
|
if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
|
|
2306
2368
|
return;
|
|
2307
2369
|
}
|
|
2370
|
+
logGapDiagnosticSampled('dht.connMgr.send');
|
|
2308
2371
|
const peerDescriptor = message.targetDescriptor;
|
|
2309
2372
|
if (this.isConnectionToSelf(peerDescriptor)) {
|
|
2310
2373
|
throw new CannotConnectToSelf('Cannot send to self');
|
|
@@ -2380,6 +2443,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2380
2443
|
this.rpcCommunicator?.handleMessageFromPeer(message);
|
|
2381
2444
|
}
|
|
2382
2445
|
else {
|
|
2446
|
+
logGapDiagnosticSampled('dht.connMgr.emitMessage');
|
|
2383
2447
|
logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
|
|
2384
2448
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
2385
2449
|
this.emit('message', message);
|
|
@@ -2389,6 +2453,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2389
2453
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
2390
2454
|
return;
|
|
2391
2455
|
}
|
|
2456
|
+
logGapDiagnosticSampled('dht.connMgr.onData');
|
|
2392
2457
|
this.metrics.receiveBytesPerSecond.record(data.byteLength);
|
|
2393
2458
|
this.metrics.receiveMessagesPerSecond.record(1);
|
|
2394
2459
|
let message;
|
|
@@ -2822,7 +2887,7 @@ const parseVersion = (version) => {
|
|
|
2822
2887
|
}
|
|
2823
2888
|
};
|
|
2824
2889
|
|
|
2825
|
-
var version = "103.
|
|
2890
|
+
var version = "103.8.0-rc.3";
|
|
2826
2891
|
|
|
2827
2892
|
const logger$y = new utils.Logger('Handshaker');
|
|
2828
2893
|
// Optimally the Outgoing and Incoming Handshakers could be their own separate classes
|
|
@@ -3280,6 +3345,9 @@ class DirectWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3280
3345
|
}
|
|
3281
3346
|
send(data) {
|
|
3282
3347
|
if (this.lastState === 'connected') {
|
|
3348
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3349
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3350
|
+
});
|
|
3283
3351
|
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3284
3352
|
this.messageQueue.push(data);
|
|
3285
3353
|
}
|
|
@@ -3308,6 +3376,7 @@ class DirectWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3308
3376
|
};
|
|
3309
3377
|
dataChannel.onmessage = (msg) => {
|
|
3310
3378
|
logger$v.trace('dc.onmessage');
|
|
3379
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3311
3380
|
this.emit('data', new Uint8Array(msg.data));
|
|
3312
3381
|
};
|
|
3313
3382
|
dataChannel.onbufferedamountlow = () => {
|
|
@@ -3520,6 +3589,69 @@ function installWebrtcBridge(worker) {
|
|
|
3520
3589
|
* onbufferedamountlow) fire in the worker's event loop. The main thread
|
|
3521
3590
|
* is never involved in the data path.
|
|
3522
3591
|
*/
|
|
3592
|
+
// ── agent log: layer0-vs-layer2 contention probe ────────────────────
|
|
3593
|
+
// Every DHT connection's datachannel `onmessage` runs in THIS one worker, and
|
|
3594
|
+
// `emit('data')` synchronously drives the downstream routing / RPC dispatch.
|
|
3595
|
+
// So timing each emit captures the per-message synchronous processing cost —
|
|
3596
|
+
// and aggregating across ALL connections tells us how much of the worker's
|
|
3597
|
+
// event loop is consumed servicing GLOBAL-DHT layer0 traffic (routing for the
|
|
3598
|
+
// hundreds of nodes we forward for) vs our handful of media messages. If a
|
|
3599
|
+
// media arrival gap coincides with a window of high non-media processing, that
|
|
3600
|
+
// is layer0 starving layer2. Emitted in the same `[gap-diagnostics]` line
|
|
3601
|
+
// shape the analyzer already parses.
|
|
3602
|
+
let dcWinStart = performance.now();
|
|
3603
|
+
let dcWinCount = 0;
|
|
3604
|
+
let dcWinBusyMs = 0;
|
|
3605
|
+
let dcWinMaxMs = 0;
|
|
3606
|
+
function recordDcProcessing(procMs) {
|
|
3607
|
+
if (!isGapDiagnosticsEnabled() && !globalThis.__dhtGapDiagEnabled) {
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
dcWinCount++;
|
|
3611
|
+
dcWinBusyMs += procMs;
|
|
3612
|
+
if (procMs > dcWinMaxMs)
|
|
3613
|
+
dcWinMaxMs = procMs;
|
|
3614
|
+
// Individual event for a single message whose inline processing blocked the
|
|
3615
|
+
// loop unusually long (one expensive routing/RPC dispatch).
|
|
3616
|
+
if (procMs > 15) {
|
|
3617
|
+
try {
|
|
3618
|
+
console.log('[gap-diagnostics]', JSON.stringify({
|
|
3619
|
+
layer: 'dht.dc.processing.slow',
|
|
3620
|
+
timestampMs: performance.now(),
|
|
3621
|
+
deltaMs: +procMs.toFixed(2),
|
|
3622
|
+
}));
|
|
3623
|
+
}
|
|
3624
|
+
catch {
|
|
3625
|
+
/* ignore */
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
const now = performance.now();
|
|
3629
|
+
const winElapsed = now - dcWinStart;
|
|
3630
|
+
if (winElapsed >= 1000) {
|
|
3631
|
+
try {
|
|
3632
|
+
console.log('[gap-diagnostics]', JSON.stringify({
|
|
3633
|
+
layer: 'dht.dc.processing.window',
|
|
3634
|
+
timestampMs: now,
|
|
3635
|
+
detail: {
|
|
3636
|
+
windowMs: +winElapsed.toFixed(0),
|
|
3637
|
+
count: dcWinCount,
|
|
3638
|
+
msgPerSec: +((dcWinCount / winElapsed) * 1000).toFixed(0),
|
|
3639
|
+
busyMs: +dcWinBusyMs.toFixed(1),
|
|
3640
|
+
occupancyPct: +((dcWinBusyMs / winElapsed) *
|
|
3641
|
+
100).toFixed(1),
|
|
3642
|
+
maxMs: +dcWinMaxMs.toFixed(1),
|
|
3643
|
+
},
|
|
3644
|
+
}));
|
|
3645
|
+
}
|
|
3646
|
+
catch {
|
|
3647
|
+
/* ignore */
|
|
3648
|
+
}
|
|
3649
|
+
dcWinStart = now;
|
|
3650
|
+
dcWinCount = 0;
|
|
3651
|
+
dcWinBusyMs = 0;
|
|
3652
|
+
dcWinMaxMs = 0;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3523
3655
|
// ── Module-level bridge client (initialized once per worker) ────────
|
|
3524
3656
|
let resolveBridgeProxy;
|
|
3525
3657
|
const bridgeProxyPromise = new Promise((resolve) => {
|
|
@@ -3562,6 +3694,19 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3562
3694
|
earlyTimeout;
|
|
3563
3695
|
messageQueue = [];
|
|
3564
3696
|
startPromise;
|
|
3697
|
+
// agent log: PER-CONNECTION datachannel receive cadence. The global
|
|
3698
|
+
// `dht.dc.onmessage` accumulator can't isolate one stream; this tracks
|
|
3699
|
+
// inter-message arrival on THIS connection so we can pick the connection
|
|
3700
|
+
// carrying the composite media (highest count/bytes) and compare its
|
|
3701
|
+
// datachannel-level gaps against messageArrival (post-routing) and
|
|
3702
|
+
// videoFrameArrival (post-decrypt) — i.e. localize WHERE the gap is born.
|
|
3703
|
+
lastRecvMs;
|
|
3704
|
+
recvWinStart = 0;
|
|
3705
|
+
recvCount = 0;
|
|
3706
|
+
recvSumDelta = 0;
|
|
3707
|
+
recvMaxDelta = 0;
|
|
3708
|
+
recvBytes = 0;
|
|
3709
|
+
recvMaxBytes = 0;
|
|
3565
3710
|
constructor(params) {
|
|
3566
3711
|
super();
|
|
3567
3712
|
this.connectionId = createRandomConnectionId();
|
|
@@ -3602,7 +3747,7 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3602
3747
|
if (state === DisconnectedState.CLOSED ||
|
|
3603
3748
|
state === DisconnectedState.DISCONNECTED ||
|
|
3604
3749
|
state === DisconnectedState.FAILED) {
|
|
3605
|
-
this.doClose(false);
|
|
3750
|
+
this.doClose(false, `pcState=${state}`);
|
|
3606
3751
|
}
|
|
3607
3752
|
},
|
|
3608
3753
|
onDataChannel: (channel) => {
|
|
@@ -3655,6 +3800,9 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3655
3800
|
}
|
|
3656
3801
|
send(data) {
|
|
3657
3802
|
if (this.connected && this.dataChannel) {
|
|
3803
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3804
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3805
|
+
});
|
|
3658
3806
|
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3659
3807
|
this.messageQueue.push(data);
|
|
3660
3808
|
}
|
|
@@ -3674,6 +3822,62 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3674
3822
|
}
|
|
3675
3823
|
}
|
|
3676
3824
|
// ── DataChannel handling (runs entirely in the worker) ──────
|
|
3825
|
+
recordRecv(bytes) {
|
|
3826
|
+
if (!isGapDiagnosticsEnabled() && !globalThis.__dhtGapDiagEnabled) {
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
const now = performance.now();
|
|
3830
|
+
const conn = String(this.connectionId).slice(0, 8);
|
|
3831
|
+
if (this.lastRecvMs !== undefined) {
|
|
3832
|
+
const delta = now - this.lastRecvMs;
|
|
3833
|
+
this.recvSumDelta += delta;
|
|
3834
|
+
if (delta > this.recvMaxDelta)
|
|
3835
|
+
this.recvMaxDelta = delta;
|
|
3836
|
+
if (delta > 60) {
|
|
3837
|
+
try {
|
|
3838
|
+
console.log('[gap-diagnostics]', JSON.stringify({
|
|
3839
|
+
layer: 'dht.dc.recvGap',
|
|
3840
|
+
timestampMs: now,
|
|
3841
|
+
deltaMs: +delta.toFixed(1),
|
|
3842
|
+
detail: { conn, bytes },
|
|
3843
|
+
}));
|
|
3844
|
+
}
|
|
3845
|
+
catch (_e) { /* ignore */ }
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
this.lastRecvMs = now;
|
|
3849
|
+
this.recvCount++;
|
|
3850
|
+
this.recvBytes += bytes;
|
|
3851
|
+
if (bytes > this.recvMaxBytes)
|
|
3852
|
+
this.recvMaxBytes = bytes;
|
|
3853
|
+
if (this.recvWinStart === 0)
|
|
3854
|
+
this.recvWinStart = now;
|
|
3855
|
+
const elapsed = now - this.recvWinStart;
|
|
3856
|
+
if (elapsed >= 1000) {
|
|
3857
|
+
try {
|
|
3858
|
+
console.log('[gap-diagnostics]', JSON.stringify({
|
|
3859
|
+
layer: 'dht.dc.recv',
|
|
3860
|
+
timestampMs: now,
|
|
3861
|
+
detail: {
|
|
3862
|
+
conn,
|
|
3863
|
+
count: this.recvCount,
|
|
3864
|
+
perSec: Math.round((this.recvCount / elapsed) * 1000),
|
|
3865
|
+
meanMs: +(this.recvSumDelta / Math.max(1, this.recvCount)).toFixed(1),
|
|
3866
|
+
maxMs: +this.recvMaxDelta.toFixed(1),
|
|
3867
|
+
bytesPerSec: Math.round((this.recvBytes / elapsed) * 1000),
|
|
3868
|
+
maxBytes: this.recvMaxBytes,
|
|
3869
|
+
},
|
|
3870
|
+
}));
|
|
3871
|
+
}
|
|
3872
|
+
catch (_e) { /* ignore */ }
|
|
3873
|
+
this.recvWinStart = now;
|
|
3874
|
+
this.recvCount = 0;
|
|
3875
|
+
this.recvSumDelta = 0;
|
|
3876
|
+
this.recvMaxDelta = 0;
|
|
3877
|
+
this.recvBytes = 0;
|
|
3878
|
+
this.recvMaxBytes = 0;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3677
3881
|
setupDataChannel(dataChannel) {
|
|
3678
3882
|
this.dataChannel = dataChannel;
|
|
3679
3883
|
this.dataChannel.binaryType = 'arraybuffer';
|
|
@@ -3684,14 +3888,18 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3684
3888
|
};
|
|
3685
3889
|
dataChannel.onclose = () => {
|
|
3686
3890
|
logger$u.trace('dc.onClosed (worker)');
|
|
3687
|
-
this.doClose(false);
|
|
3891
|
+
this.doClose(false, 'dataChannel.onclose');
|
|
3688
3892
|
};
|
|
3689
3893
|
dataChannel.onerror = (err) => {
|
|
3690
3894
|
logger$u.warn('Data channel error (worker)', { err });
|
|
3691
3895
|
};
|
|
3692
3896
|
dataChannel.onmessage = (msg) => {
|
|
3693
3897
|
logger$u.trace('dc.onmessage (worker)');
|
|
3898
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3899
|
+
this.recordRecv(msg.data instanceof ArrayBuffer ? msg.data.byteLength : 0);
|
|
3900
|
+
const t0 = performance.now();
|
|
3694
3901
|
this.emit('data', new Uint8Array(msg.data));
|
|
3902
|
+
recordDcProcessing(performance.now() - t0);
|
|
3695
3903
|
};
|
|
3696
3904
|
dataChannel.onbufferedamountlow = () => {
|
|
3697
3905
|
logger$u.trace('dc.onBufferedAmountLow (worker)');
|
|
@@ -5960,7 +6168,7 @@ class PeerDiscovery {
|
|
|
5960
6168
|
logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
|
|
5961
6169
|
}
|
|
5962
6170
|
finally {
|
|
5963
|
-
sessions.forEach((session) => this.
|
|
6171
|
+
sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
|
|
5964
6172
|
}
|
|
5965
6173
|
}
|
|
5966
6174
|
async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
|
|
@@ -8078,7 +8286,9 @@ exports.createOutgoingHandshaker = createOutgoingHandshaker;
|
|
|
8078
8286
|
exports.getRandomRegion = getRandomRegion;
|
|
8079
8287
|
exports.getRegionDelayMatrix = getRegionDelayMatrix;
|
|
8080
8288
|
exports.installWebrtcBridge = installWebrtcBridge;
|
|
8289
|
+
exports.logGapDiagnosticSampled = logGapDiagnosticSampled;
|
|
8081
8290
|
exports.randomDhtAddress = randomDhtAddress;
|
|
8291
|
+
exports.setGapDiagnosticsEnabled = setGapDiagnosticsEnabled;
|
|
8082
8292
|
exports.toDhtAddress = toDhtAddress;
|
|
8083
8293
|
exports.toDhtAddressRaw = toDhtAddressRaw;
|
|
8084
8294
|
exports.toNodeId = toNodeId;
|