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