Files
skills/docs/networking/drive/drive-webrtc/src/peer-manager/peer-bandwidth-tracker/peer-bandwidth-tracker.md
2026-05-29 08:20:56 +01:00

4.1 KiB
Raw Blame History

PeerBandwidthTracker

(no submodules)

Purpose

PeerBandwidthTracker keeps a rolling-window record of per-peer transfer activity for the WebRTC mesh: bytes in and out, request counts in both directions, and an EWMA-smoothed round-trip time. It exposes a composite score in [0, 1] that @suaveplan/drive-webrtc's PeerManager uses to rank candidate sources, and a canServe / assertCanServe pair the chunk-source layer calls before serving a remote request, so noisy or abusive peers can be detected and throttled without coupling every caller to per-peer accounting.

Features

  • Rolling window of windowMs (default 60s) with explicit rollWindow hook callable from a periodic timer.
  • Per-peer byte / request counters in both directions.
  • EWMA RTT (α = 0.2) preserved across window rolls.
  • Composite score: 0.5 * (1 − bytesInLoad) + 0.3 * (1 − requestsInLoad) + 0.2 * rttFactor.
  • New peers with no samples start at score = 1.0 so they are not unfairly demoted on first sight.
  • assertCanServe throws DriveWebRtcError(PEER_BANDWIDTH_EXCEEDED).
  • Injectable clock for deterministic tests.

Usage: Basic Example

import { PeerBandwidthTracker } from "@suaveplan/drive-webrtc";

const tracker = new PeerBandwidthTracker();
tracker.recordBytesIn("peer-1", 16_000);
tracker.recordRtt("peer-1", 42);
const metrics = tracker.get("peer-1");
console.log(metrics.score, metrics.rttMs);

Usage: Advanced Example

import { PeerBandwidthTracker, DriveWebRtcError } from "@suaveplan/drive-webrtc";
import { FakeClock } from "@suaveplan/testing";

const clock = new FakeClock();
const tracker = new PeerBandwidthTracker({
    windowMs: 1_000,
    maxBytesPerWindow: 100_000,
    maxRequestsPerWindow: 50,
    clock: { now: () => clock.now() },
});

for (let i = 0; i < 50; i++) tracker.recordRequestIn("peer-1");
try {
    tracker.assertCanServe("peer-1");
} catch (err) {
    if (err instanceof DriveWebRtcError) {
        // err.code === "DRIVE_WEBRTC.PEER_BANDWIDTH_EXCEEDED"
    }
}

clock.advance(1_001);
tracker.rollWindow();
tracker.assertCanServe("peer-1"); // recovered

API Reference

new PeerBandwidthTracker(config?)

Construct a tracker. config accepts windowMs, maxBytesPerWindow, maxRequestsPerWindow, clock.

recordBytesIn(peerId, bytes) / recordBytesOut(peerId, bytes)

Add bytes to the inbound or outbound counter, lazily rolling the window if the peer's window has expired.

recordRequestIn(peerId) / recordRequestOut(peerId)

Increment the inbound / outbound request counter by one.

recordRtt(peerId, rttMs)

Update the EWMA RTT estimate (α = 0.2). The first sample seeds the estimate verbatim.

canServe(peerId): boolean

Returns false when the peer's total bytes or total requests in the current window meet or exceed the configured thresholds.

assertCanServe(peerId): void

Throws DriveWebRtcError(PEER_BANDWIDTH_EXCEEDED) when canServe returns false.

get(peerId): PeerMetrics

Snapshot of the peer's current window. Returns a zero-valued metrics object with score = 1 if the peer has never been touched.

snapshot(): readonly PeerMetrics[]

Array of metrics for every tracked peer.

forget(peerId)

Drop all state for the peer — call on disconnect.

rollWindow(now?)

Force-roll every peer's window. Counters past windowMs are zeroed; RTT estimates persist.

Implementation Notes

  • The window rolls lazily on every read / record call so callers do not need to schedule rollWindow for correctness — it is provided for callers that want predictable timing.
  • Scores compose three normalised loads with hand-picked weights (50% inbound bytes, 30% inbound requests, 20% RTT). The weights match the upstream design intent of biasing against peers that are saturating us with traffic versus peers that are slow.
  • When maxBytesPerWindow / maxRequestsPerWindow are Infinity (default), the load components collapse to zero and the score is driven entirely by RTT.
  • The class never throws on record* — recording is always safe; only assertCanServe raises.