@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.
@@ -344,8 +344,8 @@ declare class Timestamp$Type extends MessageType<Timestamp> {
344
344
  * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
345
345
  * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
346
346
  * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
347
- * is required. A ProtoJSON serializer should always use UTC (as indicated by
348
- * "Z") when printing the Timestamp type and a ProtoJSON parser should be
347
+ * is required. A proto3 JSON serializer should always use UTC (as indicated by
348
+ * "Z") when printing the Timestamp type and a proto3 JSON parser should be
349
349
  * able to accept both UTC and other timezones (as indicated by an offset).
350
350
  *
351
351
  * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
@@ -367,18 +367,17 @@ declare class Timestamp$Type extends MessageType<Timestamp> {
367
367
  */
368
368
  interface Timestamp {
369
369
  /**
370
- * Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must
371
- * be between -62135596800 and 253402300799 inclusive (which corresponds to
372
- * 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z).
370
+ * Represents seconds of UTC time since Unix epoch
371
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
372
+ * 9999-12-31T23:59:59Z inclusive.
373
373
  *
374
374
  * @generated from protobuf field: int64 seconds = 1
375
375
  */
376
376
  seconds: number;
377
377
  /**
378
- * Non-negative fractions of a second at nanosecond resolution. This field is
379
- * the nanosecond portion of the duration, not an alternative to seconds.
380
- * Negative second values with fractions must still have non-negative nanos
381
- * values that count forward in time. Must be between 0 and 999,999,999
378
+ * Non-negative fractions of a second at nanosecond resolution. Negative
379
+ * second values with fractions must still have non-negative nanos values
380
+ * that count forward in time. Must be from 0 to 999,999,999
382
381
  * inclusive.
383
382
  *
384
383
  * @generated from protobuf field: int32 nanos = 2
@@ -1319,5 +1318,11 @@ declare class Handshaker extends EventEmitter<HandshakerEvents> {
1319
1318
 
1320
1319
  declare function installWebrtcBridge(worker: Worker): void;
1321
1320
 
1322
- export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, randomDhtAddress, toDhtAddress, toDhtAddressRaw, toNodeId };
1321
+ declare function setGapDiagnosticsEnabled(val: boolean): void;
1322
+ declare function logGapDiagnosticSampled(layer: string, opts?: {
1323
+ detail?: Record<string, unknown>;
1324
+ outlierThresholdMs?: number;
1325
+ }): void;
1326
+
1327
+ export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, logGapDiagnosticSampled, randomDhtAddress, setGapDiagnosticsEnabled, toDhtAddress, toDhtAddressRaw, toNodeId };
1323
1328
  export type { ConnectionLocker, ConnectionsView, DhtAddress, DhtAddressRaw, DhtNodeEvents, DhtNodeOptions, DhtRpcOptions, IConnection, ITransport, IceServer, LockID, PortRange, RingContacts, ServiceID, TlsCertificate, TransportEvents };
@@ -240,6 +240,68 @@ class SendFailed extends Err {
240
240
  constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
241
241
  }
242
242
 
243
+ let enabled = true;
244
+ function setGapDiagnosticsEnabled(val) {
245
+ enabled = val;
246
+ globalThis.__dhtGapDiagEnabled = val;
247
+ }
248
+ function isGapDiagnosticsEnabled() {
249
+ return enabled;
250
+ }
251
+ const SUMMARY_INTERVAL_MS = 2000;
252
+ const accumulators = new Map();
253
+ function logGapDiagnosticSampled(layer, opts = {}) {
254
+ if (!enabled && !globalThis.__dhtGapDiagEnabled)
255
+ return;
256
+ const now = performance.now();
257
+ const threshold = opts.outlierThresholdMs ?? 30;
258
+ let acc = accumulators.get(layer);
259
+ if (acc === undefined) {
260
+ acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
261
+ accumulators.set(layer, acc);
262
+ return;
263
+ }
264
+ const deltaMs = now - acc.lastEventMs;
265
+ acc.lastEventMs = now;
266
+ acc.count++;
267
+ if (deltaMs > acc.maxDeltaMs)
268
+ acc.maxDeltaMs = deltaMs;
269
+ acc.sumDeltaMs += deltaMs;
270
+ if (deltaMs > threshold)
271
+ acc.outlierCount++;
272
+ if (deltaMs > threshold) {
273
+ const payload = {
274
+ layer,
275
+ timestampMs: now,
276
+ deltaMs: +deltaMs.toFixed(2),
277
+ detail: opts.detail,
278
+ };
279
+ // eslint-disable-next-line no-console
280
+ console.log('[gap-diagnostics]', JSON.stringify(payload));
281
+ }
282
+ if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
283
+ const summaryDetail = {
284
+ count: acc.count,
285
+ meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
286
+ maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
287
+ outlierCount: acc.outlierCount,
288
+ periodMs: +(now - acc.lastReportMs).toFixed(1),
289
+ };
290
+ const summary = {
291
+ layer: `${layer}.summary`,
292
+ timestampMs: now,
293
+ detail: summaryDetail,
294
+ };
295
+ // eslint-disable-next-line no-console
296
+ console.log('[gap-diagnostics]', JSON.stringify(summary));
297
+ acc.count = 0;
298
+ acc.sumDeltaMs = 0;
299
+ acc.maxDeltaMs = 0;
300
+ acc.outlierCount = 0;
301
+ acc.lastReportMs = now;
302
+ }
303
+ }
304
+
243
305
  // @generated message type with reflection information, may provide speed optimized methods
244
306
  class Empty$Type extends MessageType {
245
307
  constructor() {
@@ -2284,6 +2346,7 @@ class ConnectionManager extends EventEmitter {
2284
2346
  if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
2285
2347
  return;
2286
2348
  }
2349
+ logGapDiagnosticSampled('dht.connMgr.send');
2287
2350
  const peerDescriptor = message.targetDescriptor;
2288
2351
  if (this.isConnectionToSelf(peerDescriptor)) {
2289
2352
  throw new CannotConnectToSelf('Cannot send to self');
@@ -2359,6 +2422,7 @@ class ConnectionManager extends EventEmitter {
2359
2422
  this.rpcCommunicator?.handleMessageFromPeer(message);
2360
2423
  }
2361
2424
  else {
2425
+ logGapDiagnosticSampled('dht.connMgr.emitMessage');
2362
2426
  logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2363
2427
  + ' ' + message.serviceId + ' ' + message.messageId);
2364
2428
  this.emit('message', message);
@@ -2368,6 +2432,7 @@ class ConnectionManager extends EventEmitter {
2368
2432
  if (this.state === ConnectionManagerState.STOPPED) {
2369
2433
  return;
2370
2434
  }
2435
+ logGapDiagnosticSampled('dht.connMgr.onData');
2371
2436
  this.metrics.receiveBytesPerSecond.record(data.byteLength);
2372
2437
  this.metrics.receiveMessagesPerSecond.record(1);
2373
2438
  let message;
@@ -2801,7 +2866,7 @@ const parseVersion = (version) => {
2801
2866
  }
2802
2867
  };
2803
2868
 
2804
- var version = "103.6.0-rc.0";
2869
+ var version = "103.8.0-rc.3";
2805
2870
 
2806
2871
  const logger$y = new Logger('Handshaker');
2807
2872
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
@@ -3259,6 +3324,9 @@ class DirectWebrtcConnection extends EventEmitter {
3259
3324
  }
3260
3325
  send(data) {
3261
3326
  if (this.lastState === 'connected') {
3327
+ logGapDiagnosticSampled('dht.dc.send', {
3328
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3329
+ });
3262
3330
  if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3263
3331
  this.messageQueue.push(data);
3264
3332
  }
@@ -3287,6 +3355,7 @@ class DirectWebrtcConnection extends EventEmitter {
3287
3355
  };
3288
3356
  dataChannel.onmessage = (msg) => {
3289
3357
  logger$v.trace('dc.onmessage');
3358
+ logGapDiagnosticSampled('dht.dc.onmessage');
3290
3359
  this.emit('data', new Uint8Array(msg.data));
3291
3360
  };
3292
3361
  dataChannel.onbufferedamountlow = () => {
@@ -3499,6 +3568,69 @@ function installWebrtcBridge(worker) {
3499
3568
  * onbufferedamountlow) fire in the worker's event loop. The main thread
3500
3569
  * is never involved in the data path.
3501
3570
  */
3571
+ // ── agent log: layer0-vs-layer2 contention probe ────────────────────
3572
+ // Every DHT connection's datachannel `onmessage` runs in THIS one worker, and
3573
+ // `emit('data')` synchronously drives the downstream routing / RPC dispatch.
3574
+ // So timing each emit captures the per-message synchronous processing cost —
3575
+ // and aggregating across ALL connections tells us how much of the worker's
3576
+ // event loop is consumed servicing GLOBAL-DHT layer0 traffic (routing for the
3577
+ // hundreds of nodes we forward for) vs our handful of media messages. If a
3578
+ // media arrival gap coincides with a window of high non-media processing, that
3579
+ // is layer0 starving layer2. Emitted in the same `[gap-diagnostics]` line
3580
+ // shape the analyzer already parses.
3581
+ let dcWinStart = performance.now();
3582
+ let dcWinCount = 0;
3583
+ let dcWinBusyMs = 0;
3584
+ let dcWinMaxMs = 0;
3585
+ function recordDcProcessing(procMs) {
3586
+ if (!isGapDiagnosticsEnabled() && !globalThis.__dhtGapDiagEnabled) {
3587
+ return;
3588
+ }
3589
+ dcWinCount++;
3590
+ dcWinBusyMs += procMs;
3591
+ if (procMs > dcWinMaxMs)
3592
+ dcWinMaxMs = procMs;
3593
+ // Individual event for a single message whose inline processing blocked the
3594
+ // loop unusually long (one expensive routing/RPC dispatch).
3595
+ if (procMs > 15) {
3596
+ try {
3597
+ console.log('[gap-diagnostics]', JSON.stringify({
3598
+ layer: 'dht.dc.processing.slow',
3599
+ timestampMs: performance.now(),
3600
+ deltaMs: +procMs.toFixed(2),
3601
+ }));
3602
+ }
3603
+ catch {
3604
+ /* ignore */
3605
+ }
3606
+ }
3607
+ const now = performance.now();
3608
+ const winElapsed = now - dcWinStart;
3609
+ if (winElapsed >= 1000) {
3610
+ try {
3611
+ console.log('[gap-diagnostics]', JSON.stringify({
3612
+ layer: 'dht.dc.processing.window',
3613
+ timestampMs: now,
3614
+ detail: {
3615
+ windowMs: +winElapsed.toFixed(0),
3616
+ count: dcWinCount,
3617
+ msgPerSec: +((dcWinCount / winElapsed) * 1000).toFixed(0),
3618
+ busyMs: +dcWinBusyMs.toFixed(1),
3619
+ occupancyPct: +((dcWinBusyMs / winElapsed) *
3620
+ 100).toFixed(1),
3621
+ maxMs: +dcWinMaxMs.toFixed(1),
3622
+ },
3623
+ }));
3624
+ }
3625
+ catch {
3626
+ /* ignore */
3627
+ }
3628
+ dcWinStart = now;
3629
+ dcWinCount = 0;
3630
+ dcWinBusyMs = 0;
3631
+ dcWinMaxMs = 0;
3632
+ }
3633
+ }
3502
3634
  // ── Module-level bridge client (initialized once per worker) ────────
3503
3635
  let resolveBridgeProxy;
3504
3636
  const bridgeProxyPromise = new Promise((resolve) => {
@@ -3541,6 +3673,19 @@ class WorkerWebrtcConnection extends EventEmitter {
3541
3673
  earlyTimeout;
3542
3674
  messageQueue = [];
3543
3675
  startPromise;
3676
+ // agent log: PER-CONNECTION datachannel receive cadence. The global
3677
+ // `dht.dc.onmessage` accumulator can't isolate one stream; this tracks
3678
+ // inter-message arrival on THIS connection so we can pick the connection
3679
+ // carrying the composite media (highest count/bytes) and compare its
3680
+ // datachannel-level gaps against messageArrival (post-routing) and
3681
+ // videoFrameArrival (post-decrypt) — i.e. localize WHERE the gap is born.
3682
+ lastRecvMs;
3683
+ recvWinStart = 0;
3684
+ recvCount = 0;
3685
+ recvSumDelta = 0;
3686
+ recvMaxDelta = 0;
3687
+ recvBytes = 0;
3688
+ recvMaxBytes = 0;
3544
3689
  constructor(params) {
3545
3690
  super();
3546
3691
  this.connectionId = createRandomConnectionId();
@@ -3581,7 +3726,7 @@ class WorkerWebrtcConnection extends EventEmitter {
3581
3726
  if (state === DisconnectedState.CLOSED ||
3582
3727
  state === DisconnectedState.DISCONNECTED ||
3583
3728
  state === DisconnectedState.FAILED) {
3584
- this.doClose(false);
3729
+ this.doClose(false, `pcState=${state}`);
3585
3730
  }
3586
3731
  },
3587
3732
  onDataChannel: (channel) => {
@@ -3634,6 +3779,9 @@ class WorkerWebrtcConnection extends EventEmitter {
3634
3779
  }
3635
3780
  send(data) {
3636
3781
  if (this.connected && this.dataChannel) {
3782
+ logGapDiagnosticSampled('dht.dc.send', {
3783
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3784
+ });
3637
3785
  if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3638
3786
  this.messageQueue.push(data);
3639
3787
  }
@@ -3653,6 +3801,62 @@ class WorkerWebrtcConnection extends EventEmitter {
3653
3801
  }
3654
3802
  }
3655
3803
  // ── DataChannel handling (runs entirely in the worker) ──────
3804
+ recordRecv(bytes) {
3805
+ if (!isGapDiagnosticsEnabled() && !globalThis.__dhtGapDiagEnabled) {
3806
+ return;
3807
+ }
3808
+ const now = performance.now();
3809
+ const conn = String(this.connectionId).slice(0, 8);
3810
+ if (this.lastRecvMs !== undefined) {
3811
+ const delta = now - this.lastRecvMs;
3812
+ this.recvSumDelta += delta;
3813
+ if (delta > this.recvMaxDelta)
3814
+ this.recvMaxDelta = delta;
3815
+ if (delta > 60) {
3816
+ try {
3817
+ console.log('[gap-diagnostics]', JSON.stringify({
3818
+ layer: 'dht.dc.recvGap',
3819
+ timestampMs: now,
3820
+ deltaMs: +delta.toFixed(1),
3821
+ detail: { conn, bytes },
3822
+ }));
3823
+ }
3824
+ catch (_e) { /* ignore */ }
3825
+ }
3826
+ }
3827
+ this.lastRecvMs = now;
3828
+ this.recvCount++;
3829
+ this.recvBytes += bytes;
3830
+ if (bytes > this.recvMaxBytes)
3831
+ this.recvMaxBytes = bytes;
3832
+ if (this.recvWinStart === 0)
3833
+ this.recvWinStart = now;
3834
+ const elapsed = now - this.recvWinStart;
3835
+ if (elapsed >= 1000) {
3836
+ try {
3837
+ console.log('[gap-diagnostics]', JSON.stringify({
3838
+ layer: 'dht.dc.recv',
3839
+ timestampMs: now,
3840
+ detail: {
3841
+ conn,
3842
+ count: this.recvCount,
3843
+ perSec: Math.round((this.recvCount / elapsed) * 1000),
3844
+ meanMs: +(this.recvSumDelta / Math.max(1, this.recvCount)).toFixed(1),
3845
+ maxMs: +this.recvMaxDelta.toFixed(1),
3846
+ bytesPerSec: Math.round((this.recvBytes / elapsed) * 1000),
3847
+ maxBytes: this.recvMaxBytes,
3848
+ },
3849
+ }));
3850
+ }
3851
+ catch (_e) { /* ignore */ }
3852
+ this.recvWinStart = now;
3853
+ this.recvCount = 0;
3854
+ this.recvSumDelta = 0;
3855
+ this.recvMaxDelta = 0;
3856
+ this.recvBytes = 0;
3857
+ this.recvMaxBytes = 0;
3858
+ }
3859
+ }
3656
3860
  setupDataChannel(dataChannel) {
3657
3861
  this.dataChannel = dataChannel;
3658
3862
  this.dataChannel.binaryType = 'arraybuffer';
@@ -3663,14 +3867,18 @@ class WorkerWebrtcConnection extends EventEmitter {
3663
3867
  };
3664
3868
  dataChannel.onclose = () => {
3665
3869
  logger$u.trace('dc.onClosed (worker)');
3666
- this.doClose(false);
3870
+ this.doClose(false, 'dataChannel.onclose');
3667
3871
  };
3668
3872
  dataChannel.onerror = (err) => {
3669
3873
  logger$u.warn('Data channel error (worker)', { err });
3670
3874
  };
3671
3875
  dataChannel.onmessage = (msg) => {
3672
3876
  logger$u.trace('dc.onmessage (worker)');
3877
+ logGapDiagnosticSampled('dht.dc.onmessage');
3878
+ this.recordRecv(msg.data instanceof ArrayBuffer ? msg.data.byteLength : 0);
3879
+ const t0 = performance.now();
3673
3880
  this.emit('data', new Uint8Array(msg.data));
3881
+ recordDcProcessing(performance.now() - t0);
3674
3882
  };
3675
3883
  dataChannel.onbufferedamountlow = () => {
3676
3884
  logger$u.trace('dc.onBufferedAmountLow (worker)');
@@ -5939,7 +6147,7 @@ class PeerDiscovery {
5939
6147
  logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
5940
6148
  }
5941
6149
  finally {
5942
- sessions.forEach((session) => this.ongoingDiscoverySessions.delete(session.id));
6150
+ sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
5943
6151
  }
5944
6152
  }
5945
6153
  async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
@@ -8036,5 +8244,5 @@ class SimulatorTransport extends ConnectionManager {
8036
8244
  }
8037
8245
  }
8038
8246
 
8039
- export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, randomDhtAddress, toDhtAddress, toDhtAddressRaw, toNodeId };
8247
+ export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, logGapDiagnosticSampled, randomDhtAddress, setGapDiagnosticsEnabled, toDhtAddress, toDhtAddressRaw, toNodeId };
8040
8248
  //# sourceMappingURL=exports-browser.js.map