@streamr/dht 103.7.0-rc.2 → 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.
@@ -261,15 +261,18 @@ class SendFailed extends Err {
261
261
  constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
262
262
  }
263
263
 
264
- let enabled = false;
264
+ let enabled = true;
265
265
  function setGapDiagnosticsEnabled(val) {
266
266
  enabled = val;
267
267
  globalThis.__dhtGapDiagEnabled = val;
268
268
  }
269
+ function isGapDiagnosticsEnabled() {
270
+ return enabled;
271
+ }
269
272
  const SUMMARY_INTERVAL_MS = 2000;
270
273
  const accumulators = new Map();
271
274
  function logGapDiagnosticSampled(layer, opts = {}) {
272
- if (!enabled)
275
+ if (!enabled && !globalThis.__dhtGapDiagEnabled)
273
276
  return;
274
277
  const now = performance.now();
275
278
  const threshold = opts.outlierThresholdMs ?? 30;
@@ -2884,7 +2887,7 @@ const parseVersion = (version) => {
2884
2887
  }
2885
2888
  };
2886
2889
 
2887
- var version = "103.7.0-rc.2";
2890
+ var version = "103.8.0-rc.3";
2888
2891
 
2889
2892
  const logger$y = new utils.Logger('Handshaker');
2890
2893
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
@@ -3586,6 +3589,69 @@ function installWebrtcBridge(worker) {
3586
3589
  * onbufferedamountlow) fire in the worker's event loop. The main thread
3587
3590
  * is never involved in the data path.
3588
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
+ }
3589
3655
  // ── Module-level bridge client (initialized once per worker) ────────
3590
3656
  let resolveBridgeProxy;
3591
3657
  const bridgeProxyPromise = new Promise((resolve) => {
@@ -3628,6 +3694,19 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
3628
3694
  earlyTimeout;
3629
3695
  messageQueue = [];
3630
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;
3631
3710
  constructor(params) {
3632
3711
  super();
3633
3712
  this.connectionId = createRandomConnectionId();
@@ -3743,6 +3822,62 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
3743
3822
  }
3744
3823
  }
3745
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
+ }
3746
3881
  setupDataChannel(dataChannel) {
3747
3882
  this.dataChannel = dataChannel;
3748
3883
  this.dataChannel.binaryType = 'arraybuffer';
@@ -3761,7 +3896,10 @@ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
3761
3896
  dataChannel.onmessage = (msg) => {
3762
3897
  logger$u.trace('dc.onmessage (worker)');
3763
3898
  logGapDiagnosticSampled('dht.dc.onmessage');
3899
+ this.recordRecv(msg.data instanceof ArrayBuffer ? msg.data.byteLength : 0);
3900
+ const t0 = performance.now();
3764
3901
  this.emit('data', new Uint8Array(msg.data));
3902
+ recordDcProcessing(performance.now() - t0);
3765
3903
  };
3766
3904
  dataChannel.onbufferedamountlow = () => {
3767
3905
  logger$u.trace('dc.onBufferedAmountLow (worker)');