@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.
@@ -240,15 +240,18 @@ class SendFailed extends Err {
240
240
  constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
241
241
  }
242
242
 
243
- let enabled = false;
243
+ let enabled = true;
244
244
  function setGapDiagnosticsEnabled(val) {
245
245
  enabled = val;
246
246
  globalThis.__dhtGapDiagEnabled = val;
247
247
  }
248
+ function isGapDiagnosticsEnabled() {
249
+ return enabled;
250
+ }
248
251
  const SUMMARY_INTERVAL_MS = 2000;
249
252
  const accumulators = new Map();
250
253
  function logGapDiagnosticSampled(layer, opts = {}) {
251
- if (!enabled)
254
+ if (!enabled && !globalThis.__dhtGapDiagEnabled)
252
255
  return;
253
256
  const now = performance.now();
254
257
  const threshold = opts.outlierThresholdMs ?? 30;
@@ -2863,7 +2866,7 @@ const parseVersion = (version) => {
2863
2866
  }
2864
2867
  };
2865
2868
 
2866
- var version = "103.7.0-rc.2";
2869
+ var version = "103.8.0-rc.3";
2867
2870
 
2868
2871
  const logger$y = new Logger('Handshaker');
2869
2872
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
@@ -3565,6 +3568,69 @@ function installWebrtcBridge(worker) {
3565
3568
  * onbufferedamountlow) fire in the worker's event loop. The main thread
3566
3569
  * is never involved in the data path.
3567
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
+ }
3568
3634
  // ── Module-level bridge client (initialized once per worker) ────────
3569
3635
  let resolveBridgeProxy;
3570
3636
  const bridgeProxyPromise = new Promise((resolve) => {
@@ -3607,6 +3673,19 @@ class WorkerWebrtcConnection extends EventEmitter {
3607
3673
  earlyTimeout;
3608
3674
  messageQueue = [];
3609
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;
3610
3689
  constructor(params) {
3611
3690
  super();
3612
3691
  this.connectionId = createRandomConnectionId();
@@ -3722,6 +3801,62 @@ class WorkerWebrtcConnection extends EventEmitter {
3722
3801
  }
3723
3802
  }
3724
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
+ }
3725
3860
  setupDataChannel(dataChannel) {
3726
3861
  this.dataChannel = dataChannel;
3727
3862
  this.dataChannel.binaryType = 'arraybuffer';
@@ -3740,7 +3875,10 @@ class WorkerWebrtcConnection extends EventEmitter {
3740
3875
  dataChannel.onmessage = (msg) => {
3741
3876
  logger$u.trace('dc.onmessage (worker)');
3742
3877
  logGapDiagnosticSampled('dht.dc.onmessage');
3878
+ this.recordRecv(msg.data instanceof ArrayBuffer ? msg.data.byteLength : 0);
3879
+ const t0 = performance.now();
3743
3880
  this.emit('data', new Uint8Array(msg.data));
3881
+ recordDcProcessing(performance.now() - t0);
3744
3882
  };
3745
3883
  dataChannel.onbufferedamountlow = () => {
3746
3884
  logger$u.trace('dc.onBufferedAmountLow (worker)');