github.com/pion/webrtc/v4@v4.0.1/stats_go_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/pion/ice/v4" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 var errReceiveOfferTimeout = fmt.Errorf("timed out waiting to receive offer") 22 23 func TestStatsTimestampTime(t *testing.T) { 24 for _, test := range []struct { 25 Timestamp StatsTimestamp 26 WantTime time.Time 27 }{ 28 { 29 Timestamp: 0, 30 WantTime: time.Unix(0, 0), 31 }, 32 { 33 Timestamp: 1, 34 WantTime: time.Unix(0, 1e6), 35 }, 36 { 37 Timestamp: 0.001, 38 WantTime: time.Unix(0, 1e3), 39 }, 40 } { 41 if got, want := test.Timestamp.Time(), test.WantTime.UTC(); got != want { 42 t.Fatalf("StatsTimestamp(%v).Time() = %v, want %v", test.Timestamp, got, want) 43 } 44 } 45 } 46 47 type statSample struct { 48 name string 49 stats Stats 50 json string 51 } 52 53 func getStatsSamples() []statSample { 54 codecStats := CodecStats{ 55 Timestamp: 1688978831527.718, 56 Type: StatsTypeCodec, 57 ID: "COT01_111_minptime=10;useinbandfec=1", 58 PayloadType: 111, 59 CodecType: CodecTypeEncode, 60 TransportID: "T01", 61 MimeType: "audio/opus", 62 ClockRate: 48000, 63 Channels: 2, 64 SDPFmtpLine: "minptime=10;useinbandfec=1", 65 Implementation: "libvpx", 66 } 67 codecStatsJSON := ` 68 { 69 "timestamp": 1688978831527.718, 70 "type": "codec", 71 "id": "COT01_111_minptime=10;useinbandfec=1", 72 "payloadType": 111, 73 "codecType": "encode", 74 "transportId": "T01", 75 "mimeType": "audio/opus", 76 "clockRate": 48000, 77 "channels": 2, 78 "sdpFmtpLine": "minptime=10;useinbandfec=1", 79 "implementation": "libvpx" 80 } 81 ` 82 inboundRTPStreamStats := InboundRTPStreamStats{ 83 Mid: "1", 84 Timestamp: 1688978831527.718, 85 ID: "IT01A2184088143", 86 Type: StatsTypeInboundRTP, 87 SSRC: 2184088143, 88 Kind: "audio", 89 TransportID: "T01", 90 CodecID: "CIT01_111_minptime=10;useinbandfec=1", 91 FIRCount: 1, 92 PLICount: 2, 93 TotalProcessingDelay: 23, 94 NACKCount: 3, 95 JitterBufferDelay: 24, 96 JitterBufferTargetDelay: 25, 97 JitterBufferEmittedCount: 26, 98 JitterBufferMinimumDelay: 27, 99 TotalSamplesReceived: 28, 100 ConcealedSamples: 29, 101 SilentConcealedSamples: 30, 102 ConcealmentEvents: 31, 103 InsertedSamplesForDeceleration: 32, 104 RemovedSamplesForAcceleration: 33, 105 AudioLevel: 34, 106 TotalAudioEnergy: 35, 107 TotalSamplesDuration: 36, 108 SLICount: 4, 109 QPSum: 5, 110 TotalDecodeTime: 37, 111 TotalInterFrameDelay: 38, 112 TotalSquaredInterFrameDelay: 39, 113 PacketsReceived: 6, 114 PacketsLost: 7, 115 Jitter: 8, 116 PacketsDiscarded: 9, 117 PacketsRepaired: 10, 118 BurstPacketsLost: 11, 119 BurstPacketsDiscarded: 12, 120 BurstLossCount: 13, 121 BurstDiscardCount: 14, 122 BurstLossRate: 15, 123 BurstDiscardRate: 16, 124 GapLossRate: 17, 125 GapDiscardRate: 18, 126 TrackID: "d57dbc4b-484b-4b40-9088-d3150e3a2010", 127 ReceiverID: "R01", 128 RemoteID: "ROA2184088143", 129 FramesDecoded: 17, 130 KeyFramesDecoded: 40, 131 FramesRendered: 41, 132 FramesDropped: 42, 133 FrameWidth: 43, 134 FrameHeight: 44, 135 LastPacketReceivedTimestamp: 1689668364374.181, 136 HeaderBytesReceived: 45, 137 AverageRTCPInterval: 18, 138 FECPacketsReceived: 19, 139 FECPacketsDiscarded: 46, 140 BytesReceived: 20, 141 FramesReceived: 47, 142 PacketsFailedDecryption: 21, 143 PacketsDuplicated: 22, 144 PerDSCPPacketsReceived: map[string]uint32{ 145 "123": 23, 146 }, 147 } 148 inboundRTPStreamStatsJSON := ` 149 { 150 "mid": "1", 151 "timestamp": 1688978831527.718, 152 "id": "IT01A2184088143", 153 "type": "inbound-rtp", 154 "ssrc": 2184088143, 155 "kind": "audio", 156 "transportId": "T01", 157 "codecId": "CIT01_111_minptime=10;useinbandfec=1", 158 "firCount": 1, 159 "pliCount": 2, 160 "totalProcessingDelay": 23, 161 "nackCount": 3, 162 "jitterBufferDelay": 24, 163 "jitterBufferTargetDelay": 25, 164 "jitterBufferEmittedCount": 26, 165 "jitterBufferMinimumDelay": 27, 166 "totalSamplesReceived": 28, 167 "concealedSamples": 29, 168 "silentConcealedSamples": 30, 169 "concealmentEvents": 31, 170 "insertedSamplesForDeceleration": 32, 171 "removedSamplesForAcceleration": 33, 172 "audioLevel": 34, 173 "totalAudioEnergy": 35, 174 "totalSamplesDuration": 36, 175 "sliCount": 4, 176 "qpSum": 5, 177 "totalDecodeTime": 37, 178 "totalInterFrameDelay": 38, 179 "totalSquaredInterFrameDelay": 39, 180 "packetsReceived": 6, 181 "packetsLost": 7, 182 "jitter": 8, 183 "packetsDiscarded": 9, 184 "packetsRepaired": 10, 185 "burstPacketsLost": 11, 186 "burstPacketsDiscarded": 12, 187 "burstLossCount": 13, 188 "burstDiscardCount": 14, 189 "burstLossRate": 15, 190 "burstDiscardRate": 16, 191 "gapLossRate": 17, 192 "gapDiscardRate": 18, 193 "trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010", 194 "receiverId": "R01", 195 "remoteId": "ROA2184088143", 196 "framesDecoded": 17, 197 "keyFramesDecoded": 40, 198 "framesRendered": 41, 199 "framesDropped": 42, 200 "frameWidth": 43, 201 "frameHeight": 44, 202 "lastPacketReceivedTimestamp": 1689668364374.181, 203 "headerBytesReceived": 45, 204 "averageRtcpInterval": 18, 205 "fecPacketsReceived": 19, 206 "fecPacketsDiscarded": 46, 207 "bytesReceived": 20, 208 "framesReceived": 47, 209 "packetsFailedDecryption": 21, 210 "packetsDuplicated": 22, 211 "perDscpPacketsReceived": { 212 "123": 23 213 } 214 } 215 ` 216 outboundRTPStreamStats := OutboundRTPStreamStats{ 217 Mid: "1", 218 Rid: "hi", 219 MediaSourceID: "SA5", 220 Timestamp: 1688978831527.718, 221 Type: StatsTypeOutboundRTP, 222 ID: "OT01A2184088143", 223 SSRC: 2184088143, 224 Kind: "audio", 225 TransportID: "T01", 226 CodecID: "COT01_111_minptime=10;useinbandfec=1", 227 HeaderBytesSent: 24, 228 RetransmittedPacketsSent: 25, 229 RetransmittedBytesSent: 26, 230 FIRCount: 1, 231 PLICount: 2, 232 NACKCount: 3, 233 SLICount: 4, 234 QPSum: 5, 235 PacketsSent: 6, 236 PacketsDiscardedOnSend: 7, 237 FECPacketsSent: 8, 238 BytesSent: 9, 239 BytesDiscardedOnSend: 10, 240 TrackID: "d57dbc4b-484b-4b40-9088-d3150e3a2010", 241 SenderID: "S01", 242 RemoteID: "ROA2184088143", 243 LastPacketSentTimestamp: 11, 244 TargetBitrate: 12, 245 TotalEncodedBytesTarget: 27, 246 FrameWidth: 28, 247 FrameHeight: 29, 248 FramesPerSecond: 30, 249 FramesSent: 31, 250 HugeFramesSent: 32, 251 FramesEncoded: 13, 252 KeyFramesEncoded: 33, 253 TotalEncodeTime: 14, 254 TotalPacketSendDelay: 34, 255 AverageRTCPInterval: 15, 256 QualityLimitationReason: "cpu", 257 QualityLimitationDurations: map[string]float64{ 258 "none": 16, 259 "cpu": 17, 260 "bandwidth": 18, 261 "other": 19, 262 }, 263 QualityLimitationResolutionChanges: 35, 264 PerDSCPPacketsSent: map[string]uint32{ 265 "123": 23, 266 }, 267 Active: true, 268 } 269 outboundRTPStreamStatsJSON := ` 270 { 271 "mid": "1", 272 "rid": "hi", 273 "mediaSourceId": "SA5", 274 "timestamp": 1688978831527.718, 275 "type": "outbound-rtp", 276 "id": "OT01A2184088143", 277 "ssrc": 2184088143, 278 "kind": "audio", 279 "transportId": "T01", 280 "codecId": "COT01_111_minptime=10;useinbandfec=1", 281 "headerBytesSent": 24, 282 "retransmittedPacketsSent": 25, 283 "retransmittedBytesSent": 26, 284 "firCount": 1, 285 "pliCount": 2, 286 "nackCount": 3, 287 "sliCount": 4, 288 "qpSum": 5, 289 "packetsSent": 6, 290 "packetsDiscardedOnSend": 7, 291 "fecPacketsSent": 8, 292 "bytesSent": 9, 293 "bytesDiscardedOnSend": 10, 294 "trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010", 295 "senderId": "S01", 296 "remoteId": "ROA2184088143", 297 "lastPacketSentTimestamp": 11, 298 "targetBitrate": 12, 299 "totalEncodedBytesTarget": 27, 300 "frameWidth": 28, 301 "frameHeight": 29, 302 "framesPerSecond": 30, 303 "framesSent": 31, 304 "hugeFramesSent": 32, 305 "framesEncoded": 13, 306 "keyFramesEncoded": 33, 307 "totalEncodeTime": 14, 308 "totalPacketSendDelay": 34, 309 "averageRtcpInterval": 15, 310 "qualityLimitationReason": "cpu", 311 "qualityLimitationDurations": { 312 "none": 16, 313 "cpu": 17, 314 "bandwidth": 18, 315 "other": 19 316 }, 317 "qualityLimitationResolutionChanges": 35, 318 "perDscpPacketsSent": { 319 "123": 23 320 }, 321 "active": true 322 } 323 ` 324 remoteInboundRTPStreamStats := RemoteInboundRTPStreamStats{ 325 Timestamp: 1688978831527.718, 326 Type: StatsTypeRemoteInboundRTP, 327 ID: "RIA2184088143", 328 SSRC: 2184088143, 329 Kind: "audio", 330 TransportID: "T01", 331 CodecID: "COT01_111_minptime=10;useinbandfec=1", 332 FIRCount: 1, 333 PLICount: 2, 334 NACKCount: 3, 335 SLICount: 4, 336 QPSum: 5, 337 PacketsReceived: 6, 338 PacketsLost: 7, 339 Jitter: 8, 340 PacketsDiscarded: 9, 341 PacketsRepaired: 10, 342 BurstPacketsLost: 11, 343 BurstPacketsDiscarded: 12, 344 BurstLossCount: 13, 345 BurstDiscardCount: 14, 346 BurstLossRate: 15, 347 BurstDiscardRate: 16, 348 GapLossRate: 17, 349 GapDiscardRate: 18, 350 LocalID: "RIA2184088143", 351 RoundTripTime: 19, 352 TotalRoundTripTime: 21, 353 FractionLost: 20, 354 RoundTripTimeMeasurements: 22, 355 } 356 remoteInboundRTPStreamStatsJSON := ` 357 { 358 "timestamp": 1688978831527.718, 359 "type": "remote-inbound-rtp", 360 "id": "RIA2184088143", 361 "ssrc": 2184088143, 362 "kind": "audio", 363 "transportId": "T01", 364 "codecId": "COT01_111_minptime=10;useinbandfec=1", 365 "firCount": 1, 366 "pliCount": 2, 367 "nackCount": 3, 368 "sliCount": 4, 369 "qpSum": 5, 370 "packetsReceived": 6, 371 "packetsLost": 7, 372 "jitter": 8, 373 "packetsDiscarded": 9, 374 "packetsRepaired": 10, 375 "burstPacketsLost": 11, 376 "burstPacketsDiscarded": 12, 377 "burstLossCount": 13, 378 "burstDiscardCount": 14, 379 "burstLossRate": 15, 380 "burstDiscardRate": 16, 381 "gapLossRate": 17, 382 "gapDiscardRate": 18, 383 "localId": "RIA2184088143", 384 "roundTripTime": 19, 385 "totalRoundTripTime": 21, 386 "fractionLost": 20, 387 "roundTripTimeMeasurements": 22 388 } 389 ` 390 remoteOutboundRTPStreamStats := RemoteOutboundRTPStreamStats{ 391 Timestamp: 1688978831527.718, 392 Type: StatsTypeRemoteOutboundRTP, 393 ID: "ROA2184088143", 394 SSRC: 2184088143, 395 Kind: "audio", 396 TransportID: "T01", 397 CodecID: "CIT01_111_minptime=10;useinbandfec=1", 398 FIRCount: 1, 399 PLICount: 2, 400 NACKCount: 3, 401 SLICount: 4, 402 QPSum: 5, 403 PacketsSent: 1259, 404 PacketsDiscardedOnSend: 6, 405 FECPacketsSent: 7, 406 BytesSent: 92654, 407 BytesDiscardedOnSend: 8, 408 LocalID: "IT01A2184088143", 409 RemoteTimestamp: 1689668361298, 410 ReportsSent: 9, 411 RoundTripTime: 10, 412 TotalRoundTripTime: 11, 413 RoundTripTimeMeasurements: 12, 414 } 415 remoteOutboundRTPStreamStatsJSON := ` 416 { 417 "timestamp": 1688978831527.718, 418 "type": "remote-outbound-rtp", 419 "id": "ROA2184088143", 420 "ssrc": 2184088143, 421 "kind": "audio", 422 "transportId": "T01", 423 "codecId": "CIT01_111_minptime=10;useinbandfec=1", 424 "firCount": 1, 425 "pliCount": 2, 426 "nackCount": 3, 427 "sliCount": 4, 428 "qpSum": 5, 429 "packetsSent": 1259, 430 "packetsDiscardedOnSend": 6, 431 "fecPacketsSent": 7, 432 "bytesSent": 92654, 433 "bytesDiscardedOnSend": 8, 434 "localId": "IT01A2184088143", 435 "remoteTimestamp": 1689668361298, 436 "reportsSent": 9, 437 "roundTripTime": 10, 438 "totalRoundTripTime": 11, 439 "roundTripTimeMeasurements": 12 440 } 441 ` 442 csrcStats := RTPContributingSourceStats{ 443 Timestamp: 1688978831527.718, 444 Type: StatsTypeCSRC, 445 ID: "ROA2184088143", 446 ContributorSSRC: 2184088143, 447 InboundRTPStreamID: "IT01A2184088143", 448 PacketsContributedTo: 5, 449 AudioLevel: 0.3, 450 } 451 csrcStatsJSON := ` 452 { 453 "timestamp": 1688978831527.718, 454 "type": "csrc", 455 "id": "ROA2184088143", 456 "contributorSsrc": 2184088143, 457 "inboundRtpStreamId": "IT01A2184088143", 458 "packetsContributedTo": 5, 459 "audioLevel": 0.3 460 } 461 ` 462 audioSourceStats := AudioSourceStats{ 463 Timestamp: 1689668364374.479, 464 Type: StatsTypeMediaSource, 465 ID: "SA5", 466 TrackIdentifier: "d57dbc4b-484b-4b40-9088-d3150e3a2010", 467 Kind: "audio", 468 AudioLevel: 0.0030518509475997192, 469 TotalAudioEnergy: 0.0024927631236904358, 470 TotalSamplesDuration: 28.360000000001634, 471 EchoReturnLoss: -30, 472 EchoReturnLossEnhancement: 0.17551203072071075, 473 DroppedSamplesDuration: 0.1, 474 DroppedSamplesEvents: 2, 475 TotalCaptureDelay: 0.3, 476 TotalSamplesCaptured: 4, 477 } 478 audioSourceStatsJSON := ` 479 { 480 "timestamp": 1689668364374.479, 481 "type": "media-source", 482 "id": "SA5", 483 "trackIdentifier": "d57dbc4b-484b-4b40-9088-d3150e3a2010", 484 "kind": "audio", 485 "audioLevel": 0.0030518509475997192, 486 "totalAudioEnergy": 0.0024927631236904358, 487 "totalSamplesDuration": 28.360000000001634, 488 "echoReturnLoss": -30, 489 "echoReturnLossEnhancement": 0.17551203072071075, 490 "droppedSamplesDuration": 0.1, 491 "droppedSamplesEvents": 2, 492 "totalCaptureDelay": 0.3, 493 "totalSamplesCaptured": 4 494 } 495 ` 496 videoSourceStats := VideoSourceStats{ 497 Timestamp: 1689668364374.479, 498 Type: StatsTypeMediaSource, 499 ID: "SV6", 500 TrackIdentifier: "d7f11739-d395-42e9-af87-5dfa1cc10ee0", 501 Kind: "video", 502 Width: 640, 503 Height: 480, 504 Frames: 850, 505 FramesPerSecond: 30, 506 } 507 videoSourceStatsJSON := ` 508 { 509 "timestamp": 1689668364374.479, 510 "type": "media-source", 511 "id": "SV6", 512 "trackIdentifier": "d7f11739-d395-42e9-af87-5dfa1cc10ee0", 513 "kind": "video", 514 "width": 640, 515 "height": 480, 516 "frames": 850, 517 "framesPerSecond": 30 518 } 519 ` 520 audioPlayoutStats := AudioPlayoutStats{ 521 Timestamp: 1689668364374.181, 522 Type: StatsTypeMediaPlayout, 523 ID: "AP", 524 Kind: "audio", 525 SynthesizedSamplesDuration: 1, 526 SynthesizedSamplesEvents: 2, 527 TotalSamplesDuration: 593.5, 528 TotalPlayoutDelay: 1062194.11536, 529 TotalSamplesCount: 28488000, 530 } 531 audioPlayoutStatsJSON := ` 532 { 533 "timestamp": 1689668364374.181, 534 "type": "media-playout", 535 "id": "AP", 536 "kind": "audio", 537 "synthesizedSamplesDuration": 1, 538 "synthesizedSamplesEvents": 2, 539 "totalSamplesDuration": 593.5, 540 "totalPlayoutDelay": 1062194.11536, 541 "totalSamplesCount": 28488000 542 } 543 ` 544 peerConnectionStats := PeerConnectionStats{ 545 Timestamp: 1688978831527.718, 546 Type: StatsTypePeerConnection, 547 ID: "P", 548 DataChannelsOpened: 1, 549 DataChannelsClosed: 2, 550 DataChannelsRequested: 3, 551 DataChannelsAccepted: 4, 552 } 553 peerConnectionStatsJSON := ` 554 { 555 "timestamp": 1688978831527.718, 556 "type": "peer-connection", 557 "id": "P", 558 "dataChannelsOpened": 1, 559 "dataChannelsClosed": 2, 560 "dataChannelsRequested": 3, 561 "dataChannelsAccepted": 4 562 } 563 ` 564 dataChannelStats := DataChannelStats{ 565 Timestamp: 1688978831527.718, 566 Type: StatsTypeDataChannel, 567 ID: "D1", 568 Label: "display", 569 Protocol: "protocol", 570 DataChannelIdentifier: 1, 571 TransportID: "T1", 572 State: DataChannelStateOpen, 573 MessagesSent: 1, 574 BytesSent: 16, 575 MessagesReceived: 2, 576 BytesReceived: 20, 577 } 578 dataChannelStatsJSON := ` 579 { 580 "timestamp": 1688978831527.718, 581 "type": "data-channel", 582 "id": "D1", 583 "label": "display", 584 "protocol": "protocol", 585 "dataChannelIdentifier": 1, 586 "transportId": "T1", 587 "state": "open", 588 "messagesSent": 1, 589 "bytesSent": 16, 590 "messagesReceived": 2, 591 "bytesReceived": 20 592 } 593 ` 594 streamStats := MediaStreamStats{ 595 Timestamp: 1688978831527.718, 596 Type: StatsTypeStream, 597 ID: "ROA2184088143", 598 StreamIdentifier: "S1", 599 TrackIDs: []string{"d57dbc4b-484b-4b40-9088-d3150e3a2010"}, 600 } 601 streamStatsJSON := ` 602 { 603 "timestamp": 1688978831527.718, 604 "type": "stream", 605 "id": "ROA2184088143", 606 "streamIdentifier": "S1", 607 "trackIds": [ 608 "d57dbc4b-484b-4b40-9088-d3150e3a2010" 609 ] 610 } 611 ` 612 senderVideoTrackAttachmentStats := SenderVideoTrackAttachmentStats{ 613 Timestamp: 1688978831527.718, 614 Type: StatsTypeTrack, 615 ID: "S2", 616 Kind: "video", 617 FramesCaptured: 1, 618 FramesSent: 2, 619 HugeFramesSent: 3, 620 KeyFramesSent: 4, 621 } 622 senderVideoTrackAttachmentStatsJSON := ` 623 { 624 "timestamp": 1688978831527.718, 625 "type": "track", 626 "id": "S2", 627 "kind": "video", 628 "framesCaptured": 1, 629 "framesSent": 2, 630 "hugeFramesSent": 3, 631 "keyFramesSent": 4 632 } 633 ` 634 senderAudioTrackAttachmentStats := SenderAudioTrackAttachmentStats{ 635 Timestamp: 1688978831527.718, 636 Type: StatsTypeTrack, 637 ID: "S1", 638 TrackIdentifier: "audio", 639 RemoteSource: true, 640 Ended: true, 641 Kind: "audio", 642 AudioLevel: 0.1, 643 TotalAudioEnergy: 0.2, 644 VoiceActivityFlag: true, 645 TotalSamplesDuration: 0.3, 646 EchoReturnLoss: 0.4, 647 EchoReturnLossEnhancement: 0.5, 648 TotalSamplesSent: 200, 649 } 650 senderAudioTrackAttachmentStatsJSON := ` 651 { 652 "timestamp": 1688978831527.718, 653 "type": "track", 654 "id": "S1", 655 "trackIdentifier": "audio", 656 "remoteSource": true, 657 "ended": true, 658 "kind": "audio", 659 "audioLevel": 0.1, 660 "totalAudioEnergy": 0.2, 661 "voiceActivityFlag": true, 662 "totalSamplesDuration": 0.3, 663 "echoReturnLoss": 0.4, 664 "echoReturnLossEnhancement": 0.5, 665 "totalSamplesSent": 200 666 } 667 ` 668 videoSenderStats := VideoSenderStats{ 669 Timestamp: 1688978831527.718, 670 Type: StatsTypeSender, 671 ID: "S2", 672 Kind: "video", 673 FramesCaptured: 1, 674 FramesSent: 2, 675 HugeFramesSent: 3, 676 KeyFramesSent: 4, 677 } 678 videoSenderStatsJSON := ` 679 { 680 "timestamp": 1688978831527.718, 681 "type": "sender", 682 "id": "S2", 683 "kind": "video", 684 "framesCaptured": 1, 685 "framesSent": 2, 686 "hugeFramesSent": 3, 687 "keyFramesSent": 4 688 } 689 ` 690 audioSenderStats := AudioSenderStats{ 691 Timestamp: 1688978831527.718, 692 Type: StatsTypeSender, 693 ID: "S1", 694 TrackIdentifier: "audio", 695 RemoteSource: true, 696 Ended: true, 697 Kind: "audio", 698 AudioLevel: 0.1, 699 TotalAudioEnergy: 0.2, 700 VoiceActivityFlag: true, 701 TotalSamplesDuration: 0.3, 702 EchoReturnLoss: 0.4, 703 EchoReturnLossEnhancement: 0.5, 704 TotalSamplesSent: 200, 705 } 706 audioSenderStatsJSON := ` 707 { 708 "timestamp": 1688978831527.718, 709 "type": "sender", 710 "id": "S1", 711 "trackIdentifier": "audio", 712 "remoteSource": true, 713 "ended": true, 714 "kind": "audio", 715 "audioLevel": 0.1, 716 "totalAudioEnergy": 0.2, 717 "voiceActivityFlag": true, 718 "totalSamplesDuration": 0.3, 719 "echoReturnLoss": 0.4, 720 "echoReturnLossEnhancement": 0.5, 721 "totalSamplesSent": 200 722 } 723 ` 724 videoReceiverStats := VideoReceiverStats{ 725 Timestamp: 1688978831527.718, 726 Type: StatsTypeReceiver, 727 ID: "ROA2184088143", 728 Kind: "video", 729 FrameWidth: 720, 730 FrameHeight: 480, 731 FramesPerSecond: 30.0, 732 EstimatedPlayoutTimestamp: 1688978831527.718, 733 JitterBufferDelay: 0.1, 734 JitterBufferEmittedCount: 1, 735 FramesReceived: 79, 736 KeyFramesReceived: 10, 737 FramesDecoded: 10, 738 FramesDropped: 10, 739 PartialFramesLost: 5, 740 FullFramesLost: 5, 741 } 742 videoReceiverStatsJSON := ` 743 { 744 "timestamp": 1688978831527.718, 745 "type": "receiver", 746 "id": "ROA2184088143", 747 "kind": "video", 748 "frameWidth": 720, 749 "frameHeight": 480, 750 "framesPerSecond": 30.0, 751 "estimatedPlayoutTimestamp": 1688978831527.718, 752 "jitterBufferDelay": 0.1, 753 "jitterBufferEmittedCount": 1, 754 "framesReceived": 79, 755 "keyFramesReceived": 10, 756 "framesDecoded": 10, 757 "framesDropped": 10, 758 "partialFramesLost": 5, 759 "fullFramesLost": 5 760 } 761 ` 762 audioReceiverStats := AudioReceiverStats{ 763 Timestamp: 1688978831527.718, 764 Type: StatsTypeReceiver, 765 ID: "R1", 766 Kind: "audio", 767 AudioLevel: 0.1, 768 TotalAudioEnergy: 0.2, 769 VoiceActivityFlag: true, 770 TotalSamplesDuration: 0.3, 771 EstimatedPlayoutTimestamp: 1688978831527.718, 772 JitterBufferDelay: 0.5, 773 JitterBufferEmittedCount: 6, 774 TotalSamplesReceived: 7, 775 ConcealedSamples: 8, 776 ConcealmentEvents: 9, 777 } 778 audioReceiverStatsJSON := ` 779 { 780 "timestamp": 1688978831527.718, 781 "type": "receiver", 782 "id": "R1", 783 "kind": "audio", 784 "audioLevel": 0.1, 785 "totalAudioEnergy": 0.2, 786 "voiceActivityFlag": true, 787 "totalSamplesDuration": 0.3, 788 "estimatedPlayoutTimestamp": 1688978831527.718, 789 "jitterBufferDelay": 0.5, 790 "jitterBufferEmittedCount": 6, 791 "totalSamplesReceived": 7, 792 "concealedSamples": 8, 793 "concealmentEvents": 9 794 } 795 ` 796 transportStats := TransportStats{ 797 Timestamp: 1688978831527.718, 798 Type: StatsTypeTransport, 799 ID: "T01", 800 PacketsSent: 60, 801 PacketsReceived: 8, 802 BytesSent: 6517, 803 BytesReceived: 1159, 804 RTCPTransportStatsID: "T01", 805 ICERole: ICERoleControlling, 806 DTLSState: DTLSTransportStateConnected, 807 ICEState: ICETransportStateConnected, 808 SelectedCandidatePairID: "CPxIhBDNnT_sPDhy1TB", 809 LocalCertificateID: "CFF4:4F:C4:C7:F3:31:6C:B9:D5:AD:19:64:05:9F:2F:E9:00:70:56:1E:BA:92:29:3A:08:CE:1B:27:CF:2D:AB:24", 810 RemoteCertificateID: "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49", 811 DTLSCipher: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 812 SRTPCipher: "AES_CM_128_HMAC_SHA1_80", 813 } 814 transportStatsJSON := ` 815 { 816 "timestamp": 1688978831527.718, 817 "type": "transport", 818 "id": "T01", 819 "packetsSent": 60, 820 "packetsReceived": 8, 821 "bytesSent": 6517, 822 "bytesReceived": 1159, 823 "rtcpTransportStatsId": "T01", 824 "iceRole": "controlling", 825 "dtlsState": "connected", 826 "iceState": "connected", 827 "selectedCandidatePairId": "CPxIhBDNnT_sPDhy1TB", 828 "localCertificateId": "CFF4:4F:C4:C7:F3:31:6C:B9:D5:AD:19:64:05:9F:2F:E9:00:70:56:1E:BA:92:29:3A:08:CE:1B:27:CF:2D:AB:24", 829 "remoteCertificateId": "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49", 830 "dtlsCipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 831 "srtpCipher": "AES_CM_128_HMAC_SHA1_80" 832 } 833 ` 834 iceCandidatePairStats := ICECandidatePairStats{ 835 Timestamp: 1688978831527.718, 836 Type: StatsTypeCandidatePair, 837 ID: "CPxIhBDNnT_LlMJOnBv", 838 TransportID: "T01", 839 LocalCandidateID: "IxIhBDNnT", 840 RemoteCandidateID: "ILlMJOnBv", 841 State: "waiting", 842 Nominated: true, 843 PacketsSent: 1, 844 PacketsReceived: 2, 845 BytesSent: 3, 846 BytesReceived: 4, 847 LastPacketSentTimestamp: 5, 848 LastPacketReceivedTimestamp: 6, 849 FirstRequestTimestamp: 7, 850 LastRequestTimestamp: 8, 851 LastResponseTimestamp: 9, 852 TotalRoundTripTime: 10, 853 CurrentRoundTripTime: 11, 854 AvailableOutgoingBitrate: 12, 855 AvailableIncomingBitrate: 13, 856 CircuitBreakerTriggerCount: 14, 857 RequestsReceived: 15, 858 RequestsSent: 16, 859 ResponsesReceived: 17, 860 ResponsesSent: 18, 861 RetransmissionsReceived: 19, 862 RetransmissionsSent: 20, 863 ConsentRequestsSent: 21, 864 ConsentExpiredTimestamp: 22, 865 PacketsDiscardedOnSend: 23, 866 BytesDiscardedOnSend: 24, 867 } 868 iceCandidatePairStatsJSON := ` 869 { 870 "timestamp": 1688978831527.718, 871 "type": "candidate-pair", 872 "id": "CPxIhBDNnT_LlMJOnBv", 873 "transportId": "T01", 874 "localCandidateId": "IxIhBDNnT", 875 "remoteCandidateId": "ILlMJOnBv", 876 "state": "waiting", 877 "nominated": true, 878 "packetsSent": 1, 879 "packetsReceived": 2, 880 "bytesSent": 3, 881 "bytesReceived": 4, 882 "lastPacketSentTimestamp": 5, 883 "lastPacketReceivedTimestamp": 6, 884 "firstRequestTimestamp": 7, 885 "lastRequestTimestamp": 8, 886 "lastResponseTimestamp": 9, 887 "totalRoundTripTime": 10, 888 "currentRoundTripTime": 11, 889 "availableOutgoingBitrate": 12, 890 "availableIncomingBitrate": 13, 891 "circuitBreakerTriggerCount": 14, 892 "requestsReceived": 15, 893 "requestsSent": 16, 894 "responsesReceived": 17, 895 "responsesSent": 18, 896 "retransmissionsReceived": 19, 897 "retransmissionsSent": 20, 898 "consentRequestsSent": 21, 899 "consentExpiredTimestamp": 22, 900 "packetsDiscardedOnSend": 23, 901 "bytesDiscardedOnSend": 24 902 } 903 ` 904 localIceCandidateStats := ICECandidateStats{ 905 Timestamp: 1688978831527.718, 906 Type: StatsTypeLocalCandidate, 907 ID: "ILO8S8KYr", 908 TransportID: "T01", 909 NetworkType: "wifi", 910 IP: "192.168.0.36", 911 Port: 65400, 912 Protocol: "udp", 913 CandidateType: ICECandidateTypeHost, 914 Priority: 2122260223, 915 URL: "example.com", 916 RelayProtocol: "tcp", 917 Deleted: true, 918 } 919 localIceCandidateStatsJSON := ` 920 { 921 "timestamp": 1688978831527.718, 922 "type": "local-candidate", 923 "id": "ILO8S8KYr", 924 "transportId": "T01", 925 "networkType": "wifi", 926 "ip": "192.168.0.36", 927 "port": 65400, 928 "protocol": "udp", 929 "candidateType": "host", 930 "priority": 2122260223, 931 "url": "example.com", 932 "relayProtocol": "tcp", 933 "deleted": true 934 } 935 ` 936 remoteIceCandidateStats := ICECandidateStats{ 937 Timestamp: 1689668364374.181, 938 Type: StatsTypeRemoteCandidate, 939 ID: "IGPGeswsH", 940 TransportID: "T01", 941 IP: "10.213.237.226", 942 Port: 50618, 943 Protocol: "udp", 944 CandidateType: ICECandidateTypeHost, 945 Priority: 2122194687, 946 URL: "example.com", 947 RelayProtocol: "tcp", 948 Deleted: true, 949 } 950 remoteIceCandidateStatsJSON := ` 951 { 952 "timestamp": 1689668364374.181, 953 "type": "remote-candidate", 954 "id": "IGPGeswsH", 955 "transportId": "T01", 956 "ip": "10.213.237.226", 957 "port": 50618, 958 "protocol": "udp", 959 "candidateType": "host", 960 "priority": 2122194687, 961 "url": "example.com", 962 "relayProtocol": "tcp", 963 "deleted": true 964 } 965 ` 966 certificateStats := CertificateStats{ 967 Timestamp: 1689668364374.479, 968 Type: StatsTypeCertificate, 969 ID: "CF23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20", 970 Fingerprint: "23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20", 971 FingerprintAlgorithm: "sha-256", 972 Base64Certificate: "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+", 973 IssuerCertificateID: "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49", 974 } 975 certificateStatsJSON := ` 976 { 977 "timestamp": 1689668364374.479, 978 "type": "certificate", 979 "id": "CF23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20", 980 "fingerprint": "23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20", 981 "fingerprintAlgorithm": "sha-256", 982 "base64Certificate": "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+", 983 "issuerCertificateId": "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49" 984 } 985 ` 986 987 return []statSample{ 988 { 989 name: "codec_stats", 990 stats: codecStats, 991 json: codecStatsJSON, 992 }, 993 { 994 name: "inbound_rtp_stream_stats", 995 stats: inboundRTPStreamStats, 996 json: inboundRTPStreamStatsJSON, 997 }, 998 { 999 name: "outbound_rtp_stream_stats", 1000 stats: outboundRTPStreamStats, 1001 json: outboundRTPStreamStatsJSON, 1002 }, 1003 { 1004 name: "remote_inbound_rtp_stream_stats", 1005 stats: remoteInboundRTPStreamStats, 1006 json: remoteInboundRTPStreamStatsJSON, 1007 }, 1008 { 1009 name: "remote_outbound_rtp_stream_stats", 1010 stats: remoteOutboundRTPStreamStats, 1011 json: remoteOutboundRTPStreamStatsJSON, 1012 }, 1013 { 1014 name: "rtp_contributing_source_stats", 1015 stats: csrcStats, 1016 json: csrcStatsJSON, 1017 }, 1018 { 1019 name: "audio_source_stats", 1020 stats: audioSourceStats, 1021 json: audioSourceStatsJSON, 1022 }, 1023 { 1024 name: "video_source_stats", 1025 stats: videoSourceStats, 1026 json: videoSourceStatsJSON, 1027 }, 1028 { 1029 name: "audio_playout_stats", 1030 stats: audioPlayoutStats, 1031 json: audioPlayoutStatsJSON, 1032 }, 1033 { 1034 name: "peer_connection_stats", 1035 stats: peerConnectionStats, 1036 json: peerConnectionStatsJSON, 1037 }, 1038 { 1039 name: "data_channel_stats", 1040 stats: dataChannelStats, 1041 json: dataChannelStatsJSON, 1042 }, 1043 { 1044 name: "media_stream_stats", 1045 stats: streamStats, 1046 json: streamStatsJSON, 1047 }, 1048 { 1049 name: "sender_video_track_stats", 1050 stats: senderVideoTrackAttachmentStats, 1051 json: senderVideoTrackAttachmentStatsJSON, 1052 }, 1053 { 1054 name: "sender_audio_track_stats", 1055 stats: senderAudioTrackAttachmentStats, 1056 json: senderAudioTrackAttachmentStatsJSON, 1057 }, 1058 { 1059 name: "receiver_video_track_stats", 1060 stats: videoSenderStats, 1061 json: videoSenderStatsJSON, 1062 }, 1063 { 1064 name: "receiver_audio_track_stats", 1065 stats: audioSenderStats, 1066 json: audioSenderStatsJSON, 1067 }, 1068 { 1069 name: "receiver_video_track_stats", 1070 stats: videoReceiverStats, 1071 json: videoReceiverStatsJSON, 1072 }, 1073 { 1074 name: "receiver_audio_track_stats", 1075 stats: audioReceiverStats, 1076 json: audioReceiverStatsJSON, 1077 }, 1078 { 1079 name: "transport_stats", 1080 stats: transportStats, 1081 json: transportStatsJSON, 1082 }, 1083 { 1084 name: "ice_candidate_pair_stats", 1085 stats: iceCandidatePairStats, 1086 json: iceCandidatePairStatsJSON, 1087 }, 1088 { 1089 name: "local_ice_candidate_stats", 1090 stats: localIceCandidateStats, 1091 json: localIceCandidateStatsJSON, 1092 }, 1093 { 1094 name: "remote_ice_candidate_stats", 1095 stats: remoteIceCandidateStats, 1096 json: remoteIceCandidateStatsJSON, 1097 }, 1098 { 1099 name: "certificate_stats", 1100 stats: certificateStats, 1101 json: certificateStatsJSON, 1102 }, 1103 } 1104 } 1105 1106 func TestStatsMarshal(t *testing.T) { 1107 for _, test := range getStatsSamples() { 1108 t.Run(test.name+"_marshal", func(t *testing.T) { 1109 actualJSON, err := json.Marshal(test.stats) 1110 require.NoError(t, err) 1111 1112 assert.JSONEq(t, test.json, string(actualJSON)) 1113 }) 1114 } 1115 } 1116 1117 func TestStatsUnmarshal(t *testing.T) { 1118 for _, test := range getStatsSamples() { 1119 t.Run(test.name+"_unmarshal", func(t *testing.T) { 1120 actualStats, err := UnmarshalStatsJSON([]byte(test.json)) 1121 require.NoError(t, err) 1122 1123 assert.Equal(t, test.stats, actualStats) 1124 }) 1125 } 1126 } 1127 1128 func waitWithTimeout(t *testing.T, wg *sync.WaitGroup) { 1129 // Wait for all of the event handlers to be triggered. 1130 done := make(chan struct{}) 1131 go func() { 1132 wg.Wait() 1133 done <- struct{}{} 1134 }() 1135 timeout := time.After(5 * time.Second) 1136 select { 1137 case <-done: 1138 break 1139 case <-timeout: 1140 t.Fatal("timed out waiting for waitgroup") 1141 } 1142 } 1143 1144 func getConnectionStats(t *testing.T, report StatsReport, pc *PeerConnection) PeerConnectionStats { 1145 stats, ok := report.GetConnectionStats(pc) 1146 assert.True(t, ok) 1147 assert.Equal(t, stats.Type, StatsTypePeerConnection) 1148 return stats 1149 } 1150 1151 func getDataChannelStats(t *testing.T, report StatsReport, dc *DataChannel) DataChannelStats { 1152 stats, ok := report.GetDataChannelStats(dc) 1153 assert.True(t, ok) 1154 assert.Equal(t, stats.Type, StatsTypeDataChannel) 1155 return stats 1156 } 1157 1158 func getCodecStats(t *testing.T, report StatsReport, c *RTPCodecParameters) CodecStats { 1159 stats, ok := report.GetCodecStats(c) 1160 assert.True(t, ok) 1161 assert.Equal(t, stats.Type, StatsTypeCodec) 1162 return stats 1163 } 1164 1165 func getTransportStats(t *testing.T, report StatsReport, statsID string) TransportStats { 1166 stats, ok := report[statsID] 1167 assert.True(t, ok) 1168 transportStats, ok := stats.(TransportStats) 1169 assert.True(t, ok) 1170 assert.Equal(t, transportStats.Type, StatsTypeTransport) 1171 return transportStats 1172 } 1173 1174 func getSctpTransportStats(t *testing.T, report StatsReport) SCTPTransportStats { 1175 stats, ok := report["sctpTransport"] 1176 assert.True(t, ok) 1177 transportStats, ok := stats.(SCTPTransportStats) 1178 assert.True(t, ok) 1179 assert.Equal(t, transportStats.Type, StatsTypeSCTPTransport) 1180 return transportStats 1181 } 1182 1183 func getCertificateStats(t *testing.T, report StatsReport, certificate *Certificate) CertificateStats { 1184 certificateStats, ok := report.GetCertificateStats(certificate) 1185 assert.True(t, ok) 1186 assert.Equal(t, certificateStats.Type, StatsTypeCertificate) 1187 return certificateStats 1188 } 1189 1190 func findLocalCandidateStats(report StatsReport) []ICECandidateStats { 1191 result := []ICECandidateStats{} 1192 for _, s := range report { 1193 stats, ok := s.(ICECandidateStats) 1194 if ok && stats.Type == StatsTypeLocalCandidate { 1195 result = append(result, stats) 1196 } 1197 } 1198 return result 1199 } 1200 1201 func findRemoteCandidateStats(report StatsReport) []ICECandidateStats { 1202 result := []ICECandidateStats{} 1203 for _, s := range report { 1204 stats, ok := s.(ICECandidateStats) 1205 if ok && stats.Type == StatsTypeRemoteCandidate { 1206 result = append(result, stats) 1207 } 1208 } 1209 return result 1210 } 1211 1212 func findCandidatePairStats(t *testing.T, report StatsReport) []ICECandidatePairStats { 1213 result := []ICECandidatePairStats{} 1214 for _, s := range report { 1215 stats, ok := s.(ICECandidatePairStats) 1216 if ok { 1217 assert.Equal(t, StatsTypeCandidatePair, stats.Type) 1218 result = append(result, stats) 1219 } 1220 } 1221 return result 1222 } 1223 1224 func signalPairForStats(pcOffer *PeerConnection, pcAnswer *PeerConnection) error { 1225 offerChan := make(chan SessionDescription) 1226 pcOffer.OnICECandidate(func(candidate *ICECandidate) { 1227 if candidate == nil { 1228 offerChan <- *pcOffer.PendingLocalDescription() 1229 } 1230 }) 1231 1232 offer, err := pcOffer.CreateOffer(nil) 1233 if err != nil { 1234 return err 1235 } 1236 if err := pcOffer.SetLocalDescription(offer); err != nil { 1237 return err 1238 } 1239 1240 timeout := time.After(3 * time.Second) 1241 select { 1242 case <-timeout: 1243 return errReceiveOfferTimeout 1244 case offer := <-offerChan: 1245 if err := pcAnswer.SetRemoteDescription(offer); err != nil { 1246 return err 1247 } 1248 1249 answer, err := pcAnswer.CreateAnswer(nil) 1250 if err != nil { 1251 return err 1252 } 1253 1254 if err = pcAnswer.SetLocalDescription(answer); err != nil { 1255 return err 1256 } 1257 1258 err = pcOffer.SetRemoteDescription(answer) 1259 if err != nil { 1260 return err 1261 } 1262 return nil 1263 } 1264 } 1265 1266 func TestStatsConvertState(t *testing.T) { 1267 testCases := []struct { 1268 ice ice.CandidatePairState 1269 stats StatsICECandidatePairState 1270 }{ 1271 { 1272 ice.CandidatePairStateWaiting, 1273 StatsICECandidatePairStateWaiting, 1274 }, 1275 { 1276 ice.CandidatePairStateInProgress, 1277 StatsICECandidatePairStateInProgress, 1278 }, 1279 { 1280 ice.CandidatePairStateFailed, 1281 StatsICECandidatePairStateFailed, 1282 }, 1283 { 1284 ice.CandidatePairStateSucceeded, 1285 StatsICECandidatePairStateSucceeded, 1286 }, 1287 } 1288 1289 s, err := toStatsICECandidatePairState(ice.CandidatePairState(42)) 1290 1291 assert.Error(t, err) 1292 assert.Equal(t, 1293 StatsICECandidatePairState("Unknown"), 1294 s) 1295 for i, testCase := range testCases { 1296 s, err := toStatsICECandidatePairState(testCase.ice) 1297 assert.NoError(t, err) 1298 assert.Equal(t, 1299 testCase.stats, 1300 s, 1301 "testCase: %d %v", i, testCase, 1302 ) 1303 } 1304 } 1305 1306 func TestPeerConnection_GetStats(t *testing.T) { 1307 offerPC, answerPC, err := newPair() 1308 assert.NoError(t, err) 1309 1310 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 1311 require.NoError(t, err) 1312 1313 _, err = offerPC.AddTrack(track1) 1314 require.NoError(t, err) 1315 1316 baseLineReportPCOffer := offerPC.GetStats() 1317 baseLineReportPCAnswer := answerPC.GetStats() 1318 1319 connStatsOffer := getConnectionStats(t, baseLineReportPCOffer, offerPC) 1320 connStatsAnswer := getConnectionStats(t, baseLineReportPCAnswer, answerPC) 1321 1322 for _, connStats := range []PeerConnectionStats{connStatsOffer, connStatsAnswer} { 1323 assert.Equal(t, uint32(0), connStats.DataChannelsOpened) 1324 assert.Equal(t, uint32(0), connStats.DataChannelsClosed) 1325 assert.Equal(t, uint32(0), connStats.DataChannelsRequested) 1326 assert.Equal(t, uint32(0), connStats.DataChannelsAccepted) 1327 } 1328 1329 // Create a DC, open it and send a message 1330 offerDC, err := offerPC.CreateDataChannel("offerDC", nil) 1331 assert.NoError(t, err) 1332 1333 msg := []byte("a classic test message") 1334 offerDC.OnOpen(func() { 1335 assert.NoError(t, offerDC.Send(msg)) 1336 }) 1337 1338 dcWait := sync.WaitGroup{} 1339 dcWait.Add(1) 1340 1341 answerDCChan := make(chan *DataChannel) 1342 answerPC.OnDataChannel(func(d *DataChannel) { 1343 d.OnOpen(func() { 1344 answerDCChan <- d 1345 }) 1346 d.OnMessage(func(DataChannelMessage) { 1347 dcWait.Done() 1348 }) 1349 }) 1350 1351 assert.NoError(t, signalPairForStats(offerPC, answerPC)) 1352 waitWithTimeout(t, &dcWait) 1353 1354 answerDC := <-answerDCChan 1355 1356 reportPCOffer := offerPC.GetStats() 1357 reportPCAnswer := answerPC.GetStats() 1358 1359 connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC) 1360 assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened) 1361 assert.Equal(t, uint32(0), connStatsOffer.DataChannelsClosed) 1362 assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested) 1363 assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted) 1364 dcStatsOffer := getDataChannelStats(t, reportPCOffer, offerDC) 1365 assert.Equal(t, DataChannelStateOpen, dcStatsOffer.State) 1366 assert.Equal(t, uint32(1), dcStatsOffer.MessagesSent) 1367 assert.Equal(t, uint64(len(msg)), dcStatsOffer.BytesSent) 1368 assert.NotEmpty(t, findLocalCandidateStats(reportPCOffer)) 1369 assert.NotEmpty(t, findRemoteCandidateStats(reportPCOffer)) 1370 assert.NotEmpty(t, findCandidatePairStats(t, reportPCOffer)) 1371 1372 connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC) 1373 assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened) 1374 assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsClosed) 1375 assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested) 1376 assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted) 1377 dcStatsAnswer := getDataChannelStats(t, reportPCAnswer, answerDC) 1378 assert.Equal(t, DataChannelStateOpen, dcStatsAnswer.State) 1379 assert.Equal(t, uint32(1), dcStatsAnswer.MessagesReceived) 1380 assert.Equal(t, uint64(len(msg)), dcStatsAnswer.BytesReceived) 1381 assert.NotEmpty(t, findLocalCandidateStats(reportPCAnswer)) 1382 assert.NotEmpty(t, findRemoteCandidateStats(reportPCAnswer)) 1383 assert.NotEmpty(t, findCandidatePairStats(t, reportPCAnswer)) 1384 assert.NoError(t, err) 1385 for i := range offerPC.api.mediaEngine.videoCodecs { 1386 codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.videoCodecs[i])) 1387 assert.NotEmpty(t, codecStat) 1388 } 1389 for i := range offerPC.api.mediaEngine.audioCodecs { 1390 codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.audioCodecs[i])) 1391 assert.NotEmpty(t, codecStat) 1392 } 1393 1394 // Close answer DC now 1395 dcWait = sync.WaitGroup{} 1396 dcWait.Add(1) 1397 offerDC.OnClose(func() { 1398 dcWait.Done() 1399 }) 1400 assert.NoError(t, answerDC.Close()) 1401 waitWithTimeout(t, &dcWait) 1402 time.Sleep(10 * time.Millisecond) 1403 1404 reportPCOffer = offerPC.GetStats() 1405 reportPCAnswer = answerPC.GetStats() 1406 1407 connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC) 1408 assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened) 1409 assert.Equal(t, uint32(1), connStatsOffer.DataChannelsClosed) 1410 assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested) 1411 assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted) 1412 dcStatsOffer = getDataChannelStats(t, reportPCOffer, offerDC) 1413 assert.Equal(t, DataChannelStateClosed, dcStatsOffer.State) 1414 1415 connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC) 1416 assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened) 1417 assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsClosed) 1418 assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested) 1419 assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted) 1420 dcStatsAnswer = getDataChannelStats(t, reportPCAnswer, answerDC) 1421 assert.Equal(t, DataChannelStateClosed, dcStatsAnswer.State) 1422 1423 answerICETransportStats := getTransportStats(t, reportPCAnswer, "iceTransport") 1424 offerICETransportStats := getTransportStats(t, reportPCOffer, "iceTransport") 1425 assert.GreaterOrEqual(t, offerICETransportStats.BytesSent, answerICETransportStats.BytesReceived) 1426 assert.GreaterOrEqual(t, answerICETransportStats.BytesSent, offerICETransportStats.BytesReceived) 1427 1428 answerSCTPTransportStats := getSctpTransportStats(t, reportPCAnswer) 1429 offerSCTPTransportStats := getSctpTransportStats(t, reportPCOffer) 1430 assert.GreaterOrEqual(t, offerSCTPTransportStats.BytesSent, answerSCTPTransportStats.BytesReceived) 1431 assert.GreaterOrEqual(t, answerSCTPTransportStats.BytesSent, offerSCTPTransportStats.BytesReceived) 1432 1433 certificates := offerPC.configuration.Certificates 1434 1435 for i := range certificates { 1436 assert.NotEmpty(t, getCertificateStats(t, reportPCOffer, &certificates[i])) 1437 } 1438 1439 closePairNow(t, offerPC, answerPC) 1440 } 1441 1442 func TestPeerConnection_GetStats_Closed(t *testing.T) { 1443 pc, err := NewPeerConnection(Configuration{}) 1444 assert.NoError(t, err) 1445 1446 assert.NoError(t, pc.Close()) 1447 1448 pc.GetStats() 1449 }