Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/moqtail/moqtail/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The NetworkTelemetry class provides real-time monitoring of network performance metrics using a sliding time window. It tracks latency and throughput over time, enabling adaptive streaming decisions and quality-of-service monitoring.

Throughput

Measure data transfer rates in bytes per second

Latency

Track round-trip times and network delays

Installation

import { NetworkTelemetry } from 'moqtail'

Basic Usage

Initialization

Create a telemetry instance with a sliding window duration:
// Create telemetry with 1-second sliding window (default)
const telemetry = new NetworkTelemetry(1000)

// Create with custom 5-second window
const longWindowTelemetry = new NetworkTelemetry(5000)

// Default window is 1000ms
const defaultTelemetry = new NetworkTelemetry()

Recording Events

Push network events as they occur:
// Record a network event
telemetry.push({
  latency: 50,  // Round-trip time in milliseconds
  size: 1024,   // Bytes transferred
})

// Record multiple events
for (const packet of receivedPackets) {
  telemetry.push({
    latency: packet.rtt,
    size: packet.payload.length,
  })
}

Reading Metrics

// Get current throughput (bytes per second)
const throughput = telemetry.throughput
console.log(`Current throughput: ${throughput} bytes/sec`)
console.log(`Current throughput: ${(throughput / 1024).toFixed(2)} KB/sec`)

// Get average latency (milliseconds)
const latency = telemetry.latency
console.log(`Average latency: ${latency.toFixed(2)} ms`)

Implementation Details

Sliding Window

The telemetry system uses a sliding time window to calculate metrics:
export class NetworkTelemetry {
  private events: { timestamp: number; latency: number; size: number }[] = []
  private windowMs: number

  constructor(windowMs = 1000) {
    this.windowMs = windowMs
  }

  push({ latency, size }: { latency: number; size: number }) {
    const now = Date.now()
    this.events.push({ timestamp: now, latency, size })
  }

  private clean() {
    const cutoff = Date.now() - this.windowMs
    while (this.events.length && this.events[0] && this.events[0].timestamp < cutoff) {
      this.events.shift()
    }
  }

  get throughput() {
    this.clean()
    const totalBytes = this.events.reduce((sum, e) => sum + e.size, 0)
    return totalBytes / (this.windowMs / 1000) // bytes per second
  }

  get latency() {
    this.clean()
    if (!this.events.length) return 0
    return this.events.reduce((sum, e) => sum + e.latency, 0) / this.events.length
  }
}

Automatic Cleanup

Events older than the window duration are automatically removed when accessing metrics:
  • On throughput access: Old events are cleaned before calculation
  • On latency access: Old events are cleaned before calculation
  • Efficiency: Only events within the time window contribute to metrics
The clean() method runs automatically when accessing metrics, so you never need to call it manually.

Adaptive Bitrate Streaming

Use telemetry to implement adaptive bitrate (ABR) algorithms:
import { NetworkTelemetry } from 'moqtail'

class AdaptiveBitrateController {
  private telemetry: NetworkTelemetry
  private availableBitrates = [500_000, 1_000_000, 2_500_000, 5_000_000] // bps
  private currentBitrate = 1_000_000

  constructor() {
    // Use 3-second window for stable measurements
    this.telemetry = new NetworkTelemetry(3000)
  }

  recordTransfer(latency: number, size: number) {
    this.telemetry.push({ latency, size })
  }

  selectBitrate(): number {
    const throughputBps = this.telemetry.throughput * 8 // bytes/sec to bits/sec
    const latency = this.telemetry.latency

    // Use 80% of available bandwidth to leave headroom
    const targetBps = throughputBps * 0.8

    // Penalize high latency
    const latencyFactor = latency > 100 ? 0.7 : 1.0
    const adjustedTarget = targetBps * latencyFactor

    // Find highest bitrate that fits
    let selectedBitrate = this.availableBitrates[0]
    for (const bitrate of this.availableBitrates) {
      if (bitrate <= adjustedTarget) {
        selectedBitrate = bitrate
      } else {
        break
      }
    }

    // Smooth transitions - don't jump too quickly
    if (selectedBitrate > this.currentBitrate) {
      // Step up one level at a time
      const currentIndex = this.availableBitrates.indexOf(this.currentBitrate)
      if (currentIndex < this.availableBitrates.length - 1) {
        selectedBitrate = this.availableBitrates[currentIndex + 1]
      }
    }

    this.currentBitrate = selectedBitrate
    return selectedBitrate
  }

  logStats() {
    const throughputMbps = (this.telemetry.throughput * 8 / 1_000_000).toFixed(2)
    const latency = this.telemetry.latency.toFixed(2)
    const bitrateKbps = (this.currentBitrate / 1000).toFixed(0)

    console.log(`Throughput: ${throughputMbps} Mbps | Latency: ${latency} ms | Selected: ${bitrateKbps} Kbps`)
  }
}

// Usage
const abrController = new AdaptiveBitrateController()

// Record each received object
for await (const object of objectStream) {
  abrController.recordTransfer(
    object.metadata.rtt,
    object.payload.length
  )

  // Periodically adjust quality
  const selectedBitrate = abrController.selectBitrate()
  abrController.logStats()
}

Network Condition Monitoring

Monitor network conditions and trigger alerts:
class NetworkMonitor {
  private telemetry: NetworkTelemetry
  private readonly HIGH_LATENCY_THRESHOLD = 200 // ms
  private readonly LOW_THROUGHPUT_THRESHOLD = 100_000 // bytes/sec

  constructor() {
    this.telemetry = new NetworkTelemetry(2000)
  }

  async monitorConnection(objectStream: ReadableStream<MoqtObject>) {
    const reader = objectStream.getReader()
    const startTime = Date.now()

    try {
      while (true) {
        const receiveStart = Date.now()
        const { done, value: object } = await reader.read()
        if (done) break

        const receiveEnd = Date.now()
        const latency = receiveEnd - receiveStart

        this.telemetry.push({
          latency,
          size: object.payload?.length || 0,
        })

        // Check for network issues
        this.checkNetworkHealth()
      }
    } finally {
      reader.releaseLock()
    }
  }

  private checkNetworkHealth() {
    const latency = this.telemetry.latency
    const throughput = this.telemetry.throughput

    if (latency > this.HIGH_LATENCY_THRESHOLD) {
      console.warn(`High latency detected: ${latency.toFixed(2)} ms`)
      this.onHighLatency(latency)
    }

    if (throughput < this.LOW_THROUGHPUT_THRESHOLD) {
      console.warn(`Low throughput detected: ${(throughput / 1024).toFixed(2)} KB/sec`)
      this.onLowThroughput(throughput)
    }
  }

  private onHighLatency(latency: number) {
    // Take corrective action
    // - Reduce GOP size for faster recovery
    // - Increase buffer size
    // - Lower bitrate
  }

  private onLowThroughput(throughput: number) {
    // Take corrective action
    // - Switch to lower quality
    // - Reduce frame rate
    // - Enable compression
  }

  getStats() {
    return {
      throughputBps: this.telemetry.throughput * 8,
      throughputMbps: (this.telemetry.throughput * 8 / 1_000_000).toFixed(2),
      latencyMs: this.telemetry.latency.toFixed(2),
      status: this.getNetworkStatus(),
    }
  }

  private getNetworkStatus(): 'excellent' | 'good' | 'fair' | 'poor' {
    const latency = this.telemetry.latency
    const throughputMbps = this.telemetry.throughput * 8 / 1_000_000

    if (latency < 50 && throughputMbps > 5) return 'excellent'
    if (latency < 100 && throughputMbps > 2) return 'good'
    if (latency < 200 && throughputMbps > 1) return 'fair'
    return 'poor'
  }
}

Real-Time Dashboard

Visualize network metrics in real-time:
class TelemetryDashboard {
  private telemetry: NetworkTelemetry
  private updateInterval: number

  constructor(updateIntervalMs = 1000) {
    this.telemetry = new NetworkTelemetry(5000) // 5-second window
    this.updateInterval = updateIntervalMs
  }

  start() {
    setInterval(() => {
      this.updateDisplay()
    }, this.updateInterval)
  }

  recordEvent(latency: number, size: number) {
    this.telemetry.push({ latency, size })
  }

  private updateDisplay() {
    const throughputMbps = (this.telemetry.throughput * 8 / 1_000_000).toFixed(2)
    const latency = this.telemetry.latency.toFixed(0)

    // Update UI elements
    document.getElementById('throughput').textContent = `${throughputMbps} Mbps`
    document.getElementById('latency').textContent = `${latency} ms`

    // Update quality indicator
    const quality = this.getQualityIndicator()
    document.getElementById('quality').textContent = quality
    document.getElementById('quality').className = `quality-${quality}`
  }

  private getQualityIndicator(): string {
    const latency = this.telemetry.latency
    
    if (latency === 0) return 'unknown'
    if (latency < 50) return 'excellent'
    if (latency < 100) return 'good'
    if (latency < 200) return 'fair'
    return 'poor'
  }
}

// Usage
const dashboard = new TelemetryDashboard(500) // Update every 500ms
dashboard.start()

// Record events as they happen
for await (const object of objectStream) {
  dashboard.recordEvent(
    object.metadata.rtt,
    object.payload.length
  )
}

Performance Debugging

Use telemetry for performance analysis:
class PerformanceAnalyzer {
  private telemetry: NetworkTelemetry
  private samples: number[] = []

  constructor() {
    this.telemetry = new NetworkTelemetry(10000) // 10-second window
  }

  async analyzeStream(objectStream: ReadableStream<MoqtObject>) {
    const reader = objectStream.getReader()
    let objectCount = 0
    let totalBytes = 0
    const startTime = Date.now()

    try {
      while (true) {
        const receiveStart = performance.now()
        const { done, value: object } = await reader.read()
        if (done) break
        const receiveEnd = performance.now()

        const latency = receiveEnd - receiveStart
        const size = object.payload?.length || 0

        this.telemetry.push({ latency, size })
        this.samples.push(latency)

        objectCount++
        totalBytes += size

        // Log every 100 objects
        if (objectCount % 100 === 0) {
          this.logAnalytics(objectCount, totalBytes, startTime)
        }
      }
    } finally {
      reader.releaseLock()
      this.printSummary(objectCount, totalBytes, startTime)
    }
  }

  private logAnalytics(objectCount: number, totalBytes: number, startTime: number) {
    const elapsedSec = (Date.now() - startTime) / 1000
    const avgThroughput = (totalBytes / elapsedSec / 1024).toFixed(2)
    const currentThroughput = (this.telemetry.throughput / 1024).toFixed(2)
    const avgLatency = this.telemetry.latency.toFixed(2)

    console.log(
      `Objects: ${objectCount} | ` +
      `Avg throughput: ${avgThroughput} KB/s | ` +
      `Current throughput: ${currentThroughput} KB/s | ` +
      `Avg latency: ${avgLatency} ms`
    )
  }

  private printSummary(objectCount: number, totalBytes: number, startTime: number) {
    const elapsedSec = (Date.now() - startTime) / 1000
    
    // Calculate percentiles
    this.samples.sort((a, b) => a - b)
    const p50 = this.samples[Math.floor(this.samples.length * 0.5)]
    const p95 = this.samples[Math.floor(this.samples.length * 0.95)]
    const p99 = this.samples[Math.floor(this.samples.length * 0.99)]

    console.log('\n=== Performance Summary ===')
    console.log(`Total objects: ${objectCount}`)
    console.log(`Total data: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)
    console.log(`Duration: ${elapsedSec.toFixed(2)}s`)
    console.log(`Average throughput: ${(totalBytes / elapsedSec / 1024).toFixed(2)} KB/s`)
    console.log(`Latency p50: ${p50.toFixed(2)} ms`)
    console.log(`Latency p95: ${p95.toFixed(2)} ms`)
    console.log(`Latency p99: ${p99.toFixed(2)} ms`)
  }
}

API Reference

Constructor

windowMs
number
default:"1000"
Duration of the sliding window in milliseconds. Events older than this duration are automatically excluded from metric calculations.

Methods

push
(event: NetworkEvent) => void
Record a network event with latency and size.Parameters:
  • event.latency (number): Round-trip time in milliseconds
  • event.size (number): Bytes transferred

Properties

throughput
number
Current throughput in bytes per second, calculated over the sliding window.Returns 0 if no events are in the window.
latency
number
Average latency in milliseconds, calculated over the sliding window.Returns 0 if no events are in the window.

Best Practices

  • Short windows (1-2s): Fast reaction to network changes, more volatile
  • Long windows (5-10s): Smoother metrics, slower to react
  • Adaptive streaming: Use 2-5 second windows for balance
Push every received object to get accurate metrics. Missing events will skew calculations.
Use telemetry to drive ABR decisions, but combine with buffer status and other factors for best results.
Handle zero values gracefully - empty windows return 0 for both metrics.

Playout Buffer

Consumer-driven buffering with latency management

Clock Sync

Network time synchronization utilities