or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mddata-channels.mdindex.mdmedia-streaming.mdnetwork-transport.mdpeer-connection.mdrtp-transport.mdstatistics.md

statistics.mddocs/

0

# Statistics

1

2

Connection statistics, media quality metrics, and transport performance monitoring for debugging and quality assurance.

3

4

## Capabilities

5

6

### RTCStatsReport Class

7

8

Container for WebRTC statistics providing dictionary-like access to stats objects.

9

10

```python { .api }

11

class RTCStatsReport:

12

"""Statistics report container."""

13

14

def __init__(self):

15

"""Create empty statistics report."""

16

17

def add(self, stats) -> None:

18

"""

19

Add statistics object to report.

20

21

Parameters:

22

- stats: Statistics object (RTCInboundRtpStreamStats, RTCOutboundRtpStreamStats, etc.)

23

"""

24

25

def __getitem__(self, key: str):

26

"""Get statistics object by ID."""

27

28

def __iter__(self):

29

"""Iterate over statistics IDs."""

30

31

def keys(self):

32

"""Get all statistics IDs."""

33

34

def values(self):

35

"""Get all statistics objects."""

36

37

def items(self):

38

"""Get (ID, stats) pairs."""

39

```

40

41

### Base Statistics Class

42

43

Base class for all statistics objects with common properties.

44

45

```python { .api }

46

class RTCStats:

47

"""Base class for statistics objects."""

48

49

@property

50

def id(self) -> str:

51

"""Unique statistics identifier"""

52

53

@property

54

def timestamp(self) -> float:

55

"""Timestamp when statistics were gathered (seconds since epoch)"""

56

57

@property

58

def type(self) -> str:

59

"""Statistics type identifier"""

60

```

61

62

### RTP Stream Statistics

63

64

Statistics for incoming and outgoing RTP media streams.

65

66

```python { .api }

67

class RTCInboundRtpStreamStats(RTCStats):

68

"""Statistics for inbound RTP streams."""

69

70

@property

71

def type(self) -> str:

72

"""Always "inbound-rtp" """

73

74

@property

75

def ssrc(self) -> int:

76

"""Synchronization source identifier"""

77

78

@property

79

def kind(self) -> str:

80

"""Media kind: "audio" or "video" """

81

82

@property

83

def trackId(self) -> str:

84

"""Associated track identifier"""

85

86

@property

87

def transportId(self) -> str:

88

"""Associated transport identifier"""

89

90

@property

91

def codecId(self) -> str:

92

"""Associated codec identifier"""

93

94

# Packet statistics

95

@property

96

def packetsReceived(self) -> int:

97

"""Total packets received"""

98

99

@property

100

def packetsLost(self) -> int:

101

"""Total packets lost"""

102

103

@property

104

def packetsDiscarded(self) -> int:

105

"""Total packets discarded"""

106

107

@property

108

def packetsRepaired(self) -> int:

109

"""Total packets repaired"""

110

111

# Byte statistics

112

@property

113

def bytesReceived(self) -> int:

114

"""Total bytes received"""

115

116

@property

117

def headerBytesReceived(self) -> int:

118

"""Total header bytes received"""

119

120

# Timing statistics

121

@property

122

def jitter(self) -> float:

123

"""Packet jitter in seconds"""

124

125

@property

126

def fractionLost(self) -> float:

127

"""Fraction of packets lost (0.0 to 1.0)"""

128

129

class RTCOutboundRtpStreamStats(RTCStats):

130

"""Statistics for outbound RTP streams."""

131

132

@property

133

def type(self) -> str:

134

"""Always "outbound-rtp" """

135

136

@property

137

def ssrc(self) -> int:

138

"""Synchronization source identifier"""

139

140

@property

141

def kind(self) -> str:

142

"""Media kind: "audio" or "video" """

143

144

@property

145

def trackId(self) -> str:

146

"""Associated track identifier"""

147

148

@property

149

def transportId(self) -> str:

150

"""Associated transport identifier"""

151

152

@property

153

def codecId(self) -> str:

154

"""Associated codec identifier"""

155

156

# Packet statistics

157

@property

158

def packetsSent(self) -> int:

159

"""Total packets sent"""

160

161

@property

162

def packetsLost(self) -> int:

163

"""Total packets lost (from RTCP feedback)"""

164

165

@property

166

def retransmittedPacketsSent(self) -> int:

167

"""Total retransmitted packets sent"""

168

169

# Byte statistics

170

@property

171

def bytesSent(self) -> int:

172

"""Total bytes sent"""

173

174

@property

175

def headerBytesSent(self) -> int:

176

"""Total header bytes sent"""

177

178

@property

179

def retransmittedBytesSent(self) -> int:

180

"""Total retransmitted bytes sent"""

181

182

# Quality statistics

183

@property

184

def targetBitrate(self) -> float:

185

"""Target bitrate in bits per second"""

186

187

@property

188

def framesEncoded(self) -> int:

189

"""Total frames encoded (video only)"""

190

191

@property

192

def keyFramesEncoded(self) -> int:

193

"""Total key frames encoded (video only)"""

194

195

class RTCRemoteInboundRtpStreamStats(RTCStats):

196

"""Statistics for remote inbound RTP streams (from RTCP feedback)."""

197

198

@property

199

def type(self) -> str:

200

"""Always "remote-inbound-rtp" """

201

202

@property

203

def ssrc(self) -> int:

204

"""Synchronization source identifier"""

205

206

@property

207

def kind(self) -> str:

208

"""Media kind: "audio" or "video" """

209

210

@property

211

def packetsLost(self) -> int:

212

"""Total packets lost at remote"""

213

214

@property

215

def fractionLost(self) -> float:

216

"""Fraction lost at remote (0.0 to 1.0)"""

217

218

@property

219

def roundTripTime(self) -> float:

220

"""Round trip time in seconds"""

221

222

@property

223

def jitter(self) -> float:

224

"""Jitter at remote in seconds"""

225

226

class RTCRemoteOutboundRtpStreamStats(RTCStats):

227

"""Statistics for remote outbound RTP streams (from RTCP feedback)."""

228

229

@property

230

def type(self) -> str:

231

"""Always "remote-outbound-rtp" """

232

233

@property

234

def ssrc(self) -> int:

235

"""Synchronization source identifier"""

236

237

@property

238

def kind(self) -> str:

239

"""Media kind: "audio" or "video" """

240

241

@property

242

def packetsSent(self) -> int:

243

"""Total packets sent by remote"""

244

245

@property

246

def bytesSent(self) -> int:

247

"""Total bytes sent by remote"""

248

249

@property

250

def remoteTimestamp(self) -> float:

251

"""Remote timestamp"""

252

```

253

254

### Transport Statistics

255

256

Statistics for transport layer performance.

257

258

```python { .api }

259

class RTCTransportStats(RTCStats):

260

"""Statistics for transport layer."""

261

262

@property

263

def type(self) -> str:

264

"""Always "transport" """

265

266

@property

267

def bytesSent(self) -> int:

268

"""Total bytes sent over transport"""

269

270

@property

271

def bytesReceived(self) -> int:

272

"""Total bytes received over transport"""

273

274

@property

275

def dtlsState(self) -> str:

276

"""DTLS connection state"""

277

278

@property

279

def iceRole(self) -> str:

280

"""ICE role: "controlling" or "controlled" """

281

282

@property

283

def iceState(self) -> str:

284

"""ICE connection state"""

285

286

@property

287

def localCandidateId(self) -> str:

288

"""Selected local candidate ID"""

289

290

@property

291

def remoteCandidateId(self) -> str:

292

"""Selected remote candidate ID"""

293

294

@property

295

def tlsVersion(self) -> str:

296

"""TLS/DTLS version used"""

297

298

@property

299

def dtlsCipher(self) -> str:

300

"""DTLS cipher suite"""

301

302

@property

303

def srtpCipher(self) -> str:

304

"""SRTP cipher suite"""

305

```

306

307

## Usage Examples

308

309

### Basic Statistics Collection

310

311

```python

312

import aiortc

313

import asyncio

314

315

async def collect_basic_stats():

316

pc = aiortc.RTCPeerConnection()

317

318

# Add media tracks

319

audio_track = aiortc.AudioStreamTrack()

320

video_track = aiortc.VideoStreamTrack()

321

322

audio_sender = pc.addTrack(audio_track)

323

video_sender = pc.addTrack(video_track)

324

325

# Simulate connection setup (simplified)

326

offer = await pc.createOffer()

327

await pc.setLocalDescription(offer)

328

329

# Get connection statistics

330

stats_report = await pc.getStats()

331

332

print(f"Total statistics objects: {len(list(stats_report.keys()))}")

333

334

# Iterate through all statistics

335

for stats_id, stats in stats_report.items():

336

print(f"Stats ID: {stats_id}")

337

print(f" Type: {stats.type}")

338

print(f" Timestamp: {stats.timestamp}")

339

340

# Print type-specific information

341

if hasattr(stats, 'ssrc'):

342

print(f" SSRC: {stats.ssrc}")

343

if hasattr(stats, 'kind'):

344

print(f" Kind: {stats.kind}")

345

```

346

347

### RTP Stream Statistics Monitoring

348

349

```python

350

async def monitor_rtp_stats():

351

pc = aiortc.RTCPeerConnection()

352

353

# Add tracks

354

audio_sender = pc.addTrack(aiortc.AudioStreamTrack())

355

video_sender = pc.addTrack(aiortc.VideoStreamTrack())

356

357

async def print_rtp_stats():

358

while True:

359

try:

360

# Get sender statistics

361

audio_stats = await audio_sender.getStats()

362

video_stats = await video_sender.getStats()

363

364

print("=== RTP Statistics ===")

365

366

# Process audio sender stats

367

for stats in audio_stats.values():

368

if isinstance(stats, aiortc.RTCOutboundRtpStreamStats):

369

print(f"Audio Outbound:")

370

print(f" Packets sent: {stats.packetsSent}")

371

print(f" Bytes sent: {stats.bytesSent}")

372

print(f" Packets lost: {stats.packetsLost}")

373

374

# Process video sender stats

375

for stats in video_stats.values():

376

if isinstance(stats, aiortc.RTCOutboundRtpStreamStats):

377

print(f"Video Outbound:")

378

print(f" Packets sent: {stats.packetsSent}")

379

print(f" Bytes sent: {stats.bytesSent}")

380

print(f" Frames encoded: {stats.framesEncoded}")

381

print(f" Key frames: {stats.keyFramesEncoded}")

382

383

await asyncio.sleep(5) # Update every 5 seconds

384

385

except Exception as e:

386

print(f"Error getting stats: {e}")

387

break

388

389

# Start monitoring

390

monitor_task = asyncio.create_task(print_rtp_stats())

391

392

# Let it run for a while

393

await asyncio.sleep(30)

394

monitor_task.cancel()

395

```

396

397

### Receiver Statistics Analysis

398

399

```python

400

async def analyze_receiver_stats():

401

pc = aiortc.RTCPeerConnection()

402

403

@pc.on("track")

404

def on_track(track):

405

print(f"Received {track.kind} track")

406

407

# Find the receiver for this track

408

receiver = None

409

for transceiver in pc.getTransceivers():

410

if transceiver.receiver.track == track:

411

receiver = transceiver.receiver

412

break

413

414

if receiver:

415

async def monitor_receiver():

416

while True:

417

try:

418

stats_report = await receiver.getStats()

419

420

for stats in stats_report.values():

421

if isinstance(stats, aiortc.RTCInboundRtpStreamStats):

422

print(f"{track.kind.capitalize()} Inbound:")

423

print(f" Packets received: {stats.packetsReceived}")

424

print(f" Packets lost: {stats.packetsLost}")

425

print(f" Bytes received: {stats.bytesReceived}")

426

print(f" Jitter: {stats.jitter:.4f}s")

427

print(f" Fraction lost: {stats.fractionLost:.2%}")

428

429

await asyncio.sleep(3)

430

431

except Exception as e:

432

print(f"Error monitoring receiver: {e}")

433

break

434

435

asyncio.create_task(monitor_receiver())

436

```

437

438

### Transport Layer Statistics

439

440

```python

441

async def monitor_transport_stats():

442

pc = aiortc.RTCPeerConnection()

443

444

async def print_transport_stats():

445

stats_report = await pc.getStats()

446

447

for stats in stats_report.values():

448

if isinstance(stats, aiortc.RTCTransportStats):

449

print("Transport Statistics:")

450

print(f" Bytes sent: {stats.bytesSent}")

451

print(f" Bytes received: {stats.bytesReceived}")

452

print(f" DTLS state: {stats.dtlsState}")

453

print(f" ICE state: {stats.iceState}")

454

print(f" ICE role: {stats.iceRole}")

455

print(f" TLS version: {stats.tlsVersion}")

456

print(f" DTLS cipher: {stats.dtlsCipher}")

457

print(f" SRTP cipher: {stats.srtpCipher}")

458

print(f" Local candidate: {stats.localCandidateId}")

459

print(f" Remote candidate: {stats.remoteCandidateId}")

460

461

# Monitor transport stats

462

await print_transport_stats()

463

```

464

465

### Quality Metrics Dashboard

466

467

```python

468

async def quality_metrics_dashboard():

469

"""Create a comprehensive quality metrics dashboard."""

470

471

pc = aiortc.RTCPeerConnection()

472

473

# Add tracks

474

audio_sender = pc.addTrack(aiortc.AudioStreamTrack())

475

video_sender = pc.addTrack(aiortc.VideoStreamTrack())

476

477

class QualityMetrics:

478

def __init__(self):

479

self.reset()

480

481

def reset(self):

482

self.total_packets_sent = 0

483

self.total_packets_lost = 0

484

self.total_bytes_sent = 0

485

self.average_jitter = 0.0

486

self.packet_loss_rate = 0.0

487

self.bitrate = 0.0

488

self.last_timestamp = None

489

self.last_bytes = 0

490

491

audio_metrics = QualityMetrics()

492

video_metrics = QualityMetrics()

493

494

async def update_quality_metrics():

495

nonlocal audio_metrics, video_metrics

496

497

try:

498

# Get all connection statistics

499

stats_report = await pc.getStats()

500

current_time = asyncio.get_event_loop().time()

501

502

for stats in stats_report.values():

503

if isinstance(stats, aiortc.RTCOutboundRtpStreamStats):

504

metrics = audio_metrics if stats.kind == "audio" else video_metrics

505

506

# Update packet statistics

507

metrics.total_packets_sent = stats.packetsSent

508

metrics.total_packets_lost = stats.packetsLost

509

metrics.total_bytes_sent = stats.bytesSent

510

511

# Calculate packet loss rate

512

total_packets = metrics.total_packets_sent + metrics.total_packets_lost

513

if total_packets > 0:

514

metrics.packet_loss_rate = metrics.total_packets_lost / total_packets

515

516

# Calculate bitrate

517

if metrics.last_timestamp:

518

time_diff = current_time - metrics.last_timestamp

519

byte_diff = stats.bytesSent - metrics.last_bytes

520

if time_diff > 0:

521

metrics.bitrate = (byte_diff * 8) / time_diff # bits per second

522

523

metrics.last_timestamp = current_time

524

metrics.last_bytes = stats.bytesSent

525

526

elif isinstance(stats, aiortc.RTCInboundRtpStreamStats):

527

metrics = audio_metrics if stats.kind == "audio" else video_metrics

528

metrics.average_jitter = stats.jitter

529

530

except Exception as e:

531

print(f"Error updating metrics: {e}")

532

533

def print_dashboard():

534

print("\n" + "="*50)

535

print("QUALITY METRICS DASHBOARD")

536

print("="*50)

537

538

print(f"AUDIO:")

539

print(f" Packets sent: {audio_metrics.total_packets_sent}")

540

print(f" Packets lost: {audio_metrics.total_packets_lost}")

541

print(f" Loss rate: {audio_metrics.packet_loss_rate:.2%}")

542

print(f" Bitrate: {audio_metrics.bitrate/1000:.1f} kbps")

543

print(f" Jitter: {audio_metrics.average_jitter*1000:.1f} ms")

544

545

print(f"VIDEO:")

546

print(f" Packets sent: {video_metrics.total_packets_sent}")

547

print(f" Packets lost: {video_metrics.total_packets_lost}")

548

print(f" Loss rate: {video_metrics.packet_loss_rate:.2%}")

549

print(f" Bitrate: {video_metrics.bitrate/1000:.1f} kbps")

550

print(f" Jitter: {video_metrics.average_jitter*1000:.1f} ms")

551

552

# Run dashboard updates

553

for i in range(10): # Run for 10 iterations

554

await update_quality_metrics()

555

print_dashboard()

556

await asyncio.sleep(2)

557

```

558

559

### Statistics Export

560

561

```python

562

async def export_statistics():

563

"""Export statistics to different formats."""

564

565

pc = aiortc.RTCPeerConnection()

566

567

# Add some tracks

568

pc.addTrack(aiortc.AudioStreamTrack())

569

pc.addTrack(aiortc.VideoStreamTrack())

570

571

# Get statistics

572

stats_report = await pc.getStats()

573

574

# Export to JSON

575

import json

576

577

stats_data = {}

578

for stats_id, stats in stats_report.items():

579

stats_dict = {

580

"id": stats.id,

581

"type": stats.type,

582

"timestamp": stats.timestamp

583

}

584

585

# Add type-specific fields

586

if hasattr(stats, 'ssrc'):

587

stats_dict['ssrc'] = stats.ssrc

588

if hasattr(stats, 'kind'):

589

stats_dict['kind'] = stats.kind

590

if hasattr(stats, 'packetsSent'):

591

stats_dict['packetsSent'] = stats.packetsSent

592

if hasattr(stats, 'bytesSent'):

593

stats_dict['bytesSent'] = stats.bytesSent

594

# Add more fields as needed

595

596

stats_data[stats_id] = stats_dict

597

598

# Save to file

599

with open("webrtc_stats.json", "w") as f:

600

json.dump(stats_data, f, indent=2)

601

602

print("Statistics exported to webrtc_stats.json")

603

604

# Export to CSV

605

import csv

606

607

with open("webrtc_stats.csv", "w", newline="") as f:

608

writer = csv.writer(f)

609

610

# Write header

611

writer.writerow(["ID", "Type", "Timestamp", "SSRC", "Kind", "PacketsSent", "BytesSent"])

612

613

# Write data

614

for stats_id, stats in stats_report.items():

615

row = [

616

stats.id,

617

stats.type,

618

stats.timestamp,

619

getattr(stats, 'ssrc', ''),

620

getattr(stats, 'kind', ''),

621

getattr(stats, 'packetsSent', ''),

622

getattr(stats, 'bytesSent', '')

623

]

624

writer.writerow(row)

625

626

print("Statistics exported to webrtc_stats.csv")

627

```

628

629

### Real-time Statistics Alerts

630

631

```python

632

async def statistics_alerts():

633

"""Monitor statistics and trigger alerts for issues."""

634

635

pc = aiortc.RTCPeerConnection()

636

audio_sender = pc.addTrack(aiortc.AudioStreamTrack())

637

video_sender = pc.addTrack(aiortc.VideoStreamTrack())

638

639

# Alert thresholds

640

PACKET_LOSS_THRESHOLD = 0.05 # 5%

641

HIGH_JITTER_THRESHOLD = 0.050 # 50ms

642

LOW_BITRATE_THRESHOLD = 50000 # 50 kbps

643

644

async def check_alerts():

645

while True:

646

try:

647

stats_report = await pc.getStats()

648

649

for stats in stats_report.values():

650

if isinstance(stats, aiortc.RTCOutboundRtpStreamStats):

651

# Check packet loss

652

total_packets = stats.packetsSent + stats.packetsLost

653

if total_packets > 0:

654

loss_rate = stats.packetsLost / total_packets

655

if loss_rate > PACKET_LOSS_THRESHOLD:

656

print(f"🚨 HIGH PACKET LOSS: {loss_rate:.2%} on {stats.kind}")

657

658

# Check bitrate (simplified calculation)

659

if hasattr(stats, 'targetBitrate') and stats.targetBitrate < LOW_BITRATE_THRESHOLD:

660

print(f"⚠️ LOW BITRATE: {stats.targetBitrate/1000:.1f} kbps on {stats.kind}")

661

662

elif isinstance(stats, aiortc.RTCInboundRtpStreamStats):

663

# Check jitter

664

if stats.jitter > HIGH_JITTER_THRESHOLD:

665

print(f"⚠️ HIGH JITTER: {stats.jitter*1000:.1f} ms on {stats.kind}")

666

667

# Check fraction lost

668

if stats.fractionLost > PACKET_LOSS_THRESHOLD:

669

print(f"🚨 HIGH LOSS FRACTION: {stats.fractionLost:.2%} on {stats.kind}")

670

671

await asyncio.sleep(5) # Check every 5 seconds

672

673

except Exception as e:

674

print(f"Error checking alerts: {e}")

675

break

676

677

# Start alert monitoring

678

await check_alerts()

679

```