mirror of
https://github.com/pion/webrtc.git
synced 2025-09-26 19:21:12 +08:00
1348 lines
38 KiB
Go
1348 lines
38 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build !js
|
|
// +build !js
|
|
|
|
package webrtc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/ice/v3"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var errReceiveOfferTimeout = fmt.Errorf("timed out waiting to receive offer")
|
|
|
|
func TestStatsTimestampTime(t *testing.T) {
|
|
for _, test := range []struct {
|
|
Timestamp StatsTimestamp
|
|
WantTime time.Time
|
|
}{
|
|
{
|
|
Timestamp: 0,
|
|
WantTime: time.Unix(0, 0),
|
|
},
|
|
{
|
|
Timestamp: 1,
|
|
WantTime: time.Unix(0, 1e6),
|
|
},
|
|
{
|
|
Timestamp: 0.001,
|
|
WantTime: time.Unix(0, 1e3),
|
|
},
|
|
} {
|
|
if got, want := test.Timestamp.Time(), test.WantTime.UTC(); got != want {
|
|
t.Fatalf("StatsTimestamp(%v).Time() = %v, want %v", test.Timestamp, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type statSample struct {
|
|
name string
|
|
stats Stats
|
|
json string
|
|
}
|
|
|
|
func getStatsSamples() []statSample {
|
|
codecStats := CodecStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeCodec,
|
|
ID: "COT01_111_minptime=10;useinbandfec=1",
|
|
PayloadType: 111,
|
|
CodecType: CodecTypeEncode,
|
|
TransportID: "T01",
|
|
MimeType: "audio/opus",
|
|
ClockRate: 48000,
|
|
Channels: 2,
|
|
SDPFmtpLine: "minptime=10;useinbandfec=1",
|
|
Implementation: "libvpx",
|
|
}
|
|
codecStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "codec",
|
|
"id": "COT01_111_minptime=10;useinbandfec=1",
|
|
"payloadType": 111,
|
|
"codecType": "encode",
|
|
"transportId": "T01",
|
|
"mimeType": "audio/opus",
|
|
"clockRate": 48000,
|
|
"channels": 2,
|
|
"sdpFmtpLine": "minptime=10;useinbandfec=1",
|
|
"implementation": "libvpx"
|
|
}
|
|
`
|
|
inboundRTPStreamStats := InboundRTPStreamStats{
|
|
Timestamp: 1688978831527.718,
|
|
ID: "IT01A2184088143",
|
|
Type: StatsTypeInboundRTP,
|
|
SSRC: 2184088143,
|
|
Kind: "audio",
|
|
TransportID: "T01",
|
|
CodecID: "CIT01_111_minptime=10;useinbandfec=1",
|
|
FIRCount: 1,
|
|
PLICount: 2,
|
|
NACKCount: 3,
|
|
SLICount: 4,
|
|
QPSum: 5,
|
|
PacketsReceived: 6,
|
|
PacketsLost: 7,
|
|
Jitter: 8,
|
|
PacketsDiscarded: 9,
|
|
PacketsRepaired: 10,
|
|
BurstPacketsLost: 11,
|
|
BurstPacketsDiscarded: 12,
|
|
BurstLossCount: 13,
|
|
BurstDiscardCount: 14,
|
|
BurstLossRate: 15,
|
|
BurstDiscardRate: 16,
|
|
GapLossRate: 17,
|
|
GapDiscardRate: 18,
|
|
TrackID: "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
ReceiverID: "R01",
|
|
RemoteID: "ROA2184088143",
|
|
FramesDecoded: 17,
|
|
LastPacketReceivedTimestamp: 1689668364374.181,
|
|
AverageRTCPInterval: 18,
|
|
FECPacketsReceived: 19,
|
|
BytesReceived: 20,
|
|
PacketsFailedDecryption: 21,
|
|
PacketsDuplicated: 22,
|
|
PerDSCPPacketsReceived: map[string]uint32{
|
|
"123": 23,
|
|
},
|
|
}
|
|
inboundRTPStreamStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"id": "IT01A2184088143",
|
|
"type": "inbound-rtp",
|
|
"ssrc": 2184088143,
|
|
"kind": "audio",
|
|
"transportId": "T01",
|
|
"codecId": "CIT01_111_minptime=10;useinbandfec=1",
|
|
"firCount": 1,
|
|
"pliCount": 2,
|
|
"nackCount": 3,
|
|
"sliCount": 4,
|
|
"qpSum": 5,
|
|
"packetsReceived": 6,
|
|
"packetsLost": 7,
|
|
"jitter": 8,
|
|
"packetsDiscarded": 9,
|
|
"packetsRepaired": 10,
|
|
"burstPacketsLost": 11,
|
|
"burstPacketsDiscarded": 12,
|
|
"burstLossCount": 13,
|
|
"burstDiscardCount": 14,
|
|
"burstLossRate": 15,
|
|
"burstDiscardRate": 16,
|
|
"gapLossRate": 17,
|
|
"gapDiscardRate": 18,
|
|
"trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
"receiverId": "R01",
|
|
"remoteId": "ROA2184088143",
|
|
"framesDecoded": 17,
|
|
"lastPacketReceivedTimestamp": 1689668364374.181,
|
|
"averageRtcpInterval": 18,
|
|
"fecPacketsReceived": 19,
|
|
"bytesReceived": 20,
|
|
"packetsFailedDecryption": 21,
|
|
"packetsDuplicated": 22,
|
|
"perDscpPacketsReceived": {
|
|
"123": 23
|
|
}
|
|
}
|
|
`
|
|
outboundRTPStreamStats := OutboundRTPStreamStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeOutboundRTP,
|
|
ID: "OT01A2184088143",
|
|
SSRC: 2184088143,
|
|
Kind: "audio",
|
|
TransportID: "T01",
|
|
CodecID: "COT01_111_minptime=10;useinbandfec=1",
|
|
FIRCount: 1,
|
|
PLICount: 2,
|
|
NACKCount: 3,
|
|
SLICount: 4,
|
|
QPSum: 5,
|
|
PacketsSent: 6,
|
|
PacketsDiscardedOnSend: 7,
|
|
FECPacketsSent: 8,
|
|
BytesSent: 9,
|
|
BytesDiscardedOnSend: 10,
|
|
TrackID: "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
SenderID: "S01",
|
|
RemoteID: "ROA2184088143",
|
|
LastPacketSentTimestamp: 11,
|
|
TargetBitrate: 12,
|
|
FramesEncoded: 13,
|
|
TotalEncodeTime: 14,
|
|
AverageRTCPInterval: 15,
|
|
QualityLimitationReason: "cpu",
|
|
QualityLimitationDurations: map[string]float64{
|
|
"none": 16,
|
|
"cpu": 17,
|
|
"bandwidth": 18,
|
|
"other": 19,
|
|
},
|
|
PerDSCPPacketsSent: map[string]uint32{
|
|
"123": 23,
|
|
},
|
|
}
|
|
outboundRTPStreamStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "outbound-rtp",
|
|
"id": "OT01A2184088143",
|
|
"ssrc": 2184088143,
|
|
"kind": "audio",
|
|
"transportId": "T01",
|
|
"codecId": "COT01_111_minptime=10;useinbandfec=1",
|
|
"firCount": 1,
|
|
"pliCount": 2,
|
|
"nackCount": 3,
|
|
"sliCount": 4,
|
|
"qpSum": 5,
|
|
"packetsSent": 6,
|
|
"packetsDiscardedOnSend": 7,
|
|
"fecPacketsSent": 8,
|
|
"bytesSent": 9,
|
|
"bytesDiscardedOnSend": 10,
|
|
"trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
"senderId": "S01",
|
|
"remoteId": "ROA2184088143",
|
|
"lastPacketSentTimestamp": 11,
|
|
"targetBitrate": 12,
|
|
"framesEncoded": 13,
|
|
"totalEncodeTime": 14,
|
|
"averageRtcpInterval": 15,
|
|
"qualityLimitationReason": "cpu",
|
|
"qualityLimitationDurations": {
|
|
"none": 16,
|
|
"cpu": 17,
|
|
"bandwidth": 18,
|
|
"other": 19
|
|
},
|
|
"perDscpPacketsSent": {
|
|
"123": 23
|
|
}
|
|
}
|
|
`
|
|
remoteInboundRTPStreamStats := RemoteInboundRTPStreamStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeRemoteInboundRTP,
|
|
ID: "RIA2184088143",
|
|
SSRC: 2184088143,
|
|
Kind: "audio",
|
|
TransportID: "T01",
|
|
CodecID: "COT01_111_minptime=10;useinbandfec=1",
|
|
FIRCount: 1,
|
|
PLICount: 2,
|
|
NACKCount: 3,
|
|
SLICount: 4,
|
|
QPSum: 5,
|
|
PacketsReceived: 6,
|
|
PacketsLost: 7,
|
|
Jitter: 8,
|
|
PacketsDiscarded: 9,
|
|
PacketsRepaired: 10,
|
|
BurstPacketsLost: 11,
|
|
BurstPacketsDiscarded: 12,
|
|
BurstLossCount: 13,
|
|
BurstDiscardCount: 14,
|
|
BurstLossRate: 15,
|
|
BurstDiscardRate: 16,
|
|
GapLossRate: 17,
|
|
GapDiscardRate: 18,
|
|
LocalID: "RIA2184088143",
|
|
RoundTripTime: 19,
|
|
FractionLost: 20,
|
|
}
|
|
remoteInboundRTPStreamStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "remote-inbound-rtp",
|
|
"id": "RIA2184088143",
|
|
"ssrc": 2184088143,
|
|
"kind": "audio",
|
|
"transportId": "T01",
|
|
"codecId": "COT01_111_minptime=10;useinbandfec=1",
|
|
"firCount": 1,
|
|
"pliCount": 2,
|
|
"nackCount": 3,
|
|
"sliCount": 4,
|
|
"qpSum": 5,
|
|
"packetsReceived": 6,
|
|
"packetsLost": 7,
|
|
"jitter": 8,
|
|
"packetsDiscarded": 9,
|
|
"packetsRepaired": 10,
|
|
"burstPacketsLost": 11,
|
|
"burstPacketsDiscarded": 12,
|
|
"burstLossCount": 13,
|
|
"burstDiscardCount": 14,
|
|
"burstLossRate": 15,
|
|
"burstDiscardRate": 16,
|
|
"gapLossRate": 17,
|
|
"gapDiscardRate": 18,
|
|
"localId": "RIA2184088143",
|
|
"roundTripTime": 19,
|
|
"fractionLost": 20
|
|
}
|
|
`
|
|
remoteOutboundRTPStreamStats := RemoteOutboundRTPStreamStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeRemoteOutboundRTP,
|
|
ID: "ROA2184088143",
|
|
SSRC: 2184088143,
|
|
Kind: "audio",
|
|
TransportID: "T01",
|
|
CodecID: "CIT01_111_minptime=10;useinbandfec=1",
|
|
FIRCount: 1,
|
|
PLICount: 2,
|
|
NACKCount: 3,
|
|
SLICount: 4,
|
|
QPSum: 5,
|
|
PacketsSent: 1259,
|
|
PacketsDiscardedOnSend: 6,
|
|
FECPacketsSent: 7,
|
|
BytesSent: 92654,
|
|
BytesDiscardedOnSend: 8,
|
|
LocalID: "IT01A2184088143",
|
|
RemoteTimestamp: 1689668361298,
|
|
}
|
|
remoteOutboundRTPStreamStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "remote-outbound-rtp",
|
|
"id": "ROA2184088143",
|
|
"ssrc": 2184088143,
|
|
"kind": "audio",
|
|
"transportId": "T01",
|
|
"codecId": "CIT01_111_minptime=10;useinbandfec=1",
|
|
"firCount": 1,
|
|
"pliCount": 2,
|
|
"nackCount": 3,
|
|
"sliCount": 4,
|
|
"qpSum": 5,
|
|
"packetsSent": 1259,
|
|
"packetsDiscardedOnSend": 6,
|
|
"fecPacketsSent": 7,
|
|
"bytesSent": 92654,
|
|
"bytesDiscardedOnSend": 8,
|
|
"localId": "IT01A2184088143",
|
|
"remoteTimestamp": 1689668361298
|
|
}
|
|
`
|
|
csrcStats := RTPContributingSourceStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeCSRC,
|
|
ID: "ROA2184088143",
|
|
ContributorSSRC: 2184088143,
|
|
InboundRTPStreamID: "IT01A2184088143",
|
|
PacketsContributedTo: 5,
|
|
AudioLevel: 0.3,
|
|
}
|
|
csrcStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "csrc",
|
|
"id": "ROA2184088143",
|
|
"contributorSsrc": 2184088143,
|
|
"inboundRtpStreamId": "IT01A2184088143",
|
|
"packetsContributedTo": 5,
|
|
"audioLevel": 0.3
|
|
}
|
|
`
|
|
audioSourceStats := AudioSourceStats{
|
|
Timestamp: 1689668364374.479,
|
|
Type: StatsTypeMediaSource,
|
|
ID: "SA5",
|
|
TrackIdentifier: "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
Kind: "audio",
|
|
AudioLevel: 0.0030518509475997192,
|
|
TotalAudioEnergy: 0.0024927631236904358,
|
|
TotalSamplesDuration: 28.360000000001634,
|
|
EchoReturnLoss: -30,
|
|
EchoReturnLossEnhancement: 0.17551203072071075,
|
|
DroppedSamplesDuration: 0.1,
|
|
DroppedSamplesEvents: 2,
|
|
TotalCaptureDelay: 0.3,
|
|
TotalSamplesCaptured: 4,
|
|
}
|
|
audioSourceStatsJSON := `
|
|
{
|
|
"timestamp": 1689668364374.479,
|
|
"type": "media-source",
|
|
"id": "SA5",
|
|
"trackIdentifier": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
|
|
"kind": "audio",
|
|
"audioLevel": 0.0030518509475997192,
|
|
"totalAudioEnergy": 0.0024927631236904358,
|
|
"totalSamplesDuration": 28.360000000001634,
|
|
"echoReturnLoss": -30,
|
|
"echoReturnLossEnhancement": 0.17551203072071075,
|
|
"droppedSamplesDuration": 0.1,
|
|
"droppedSamplesEvents": 2,
|
|
"totalCaptureDelay": 0.3,
|
|
"totalSamplesCaptured": 4
|
|
}
|
|
`
|
|
videoSourceStats := VideoSourceStats{
|
|
Timestamp: 1689668364374.479,
|
|
Type: StatsTypeMediaSource,
|
|
ID: "SV6",
|
|
TrackIdentifier: "d7f11739-d395-42e9-af87-5dfa1cc10ee0",
|
|
Kind: "video",
|
|
Width: 640,
|
|
Height: 480,
|
|
Frames: 850,
|
|
FramesPerSecond: 30,
|
|
}
|
|
videoSourceStatsJSON := `
|
|
{
|
|
"timestamp": 1689668364374.479,
|
|
"type": "media-source",
|
|
"id": "SV6",
|
|
"trackIdentifier": "d7f11739-d395-42e9-af87-5dfa1cc10ee0",
|
|
"kind": "video",
|
|
"width": 640,
|
|
"height": 480,
|
|
"frames": 850,
|
|
"framesPerSecond": 30
|
|
}
|
|
`
|
|
audioPlayoutStats := AudioPlayoutStats{
|
|
Timestamp: 1689668364374.181,
|
|
Type: StatsTypeMediaPlayout,
|
|
ID: "AP",
|
|
Kind: "audio",
|
|
SynthesizedSamplesDuration: 1,
|
|
SynthesizedSamplesEvents: 2,
|
|
TotalSamplesDuration: 593.5,
|
|
TotalPlayoutDelay: 1062194.11536,
|
|
TotalSamplesCount: 28488000,
|
|
}
|
|
audioPlayoutStatsJSON := `
|
|
{
|
|
"timestamp": 1689668364374.181,
|
|
"type": "media-playout",
|
|
"id": "AP",
|
|
"kind": "audio",
|
|
"synthesizedSamplesDuration": 1,
|
|
"synthesizedSamplesEvents": 2,
|
|
"totalSamplesDuration": 593.5,
|
|
"totalPlayoutDelay": 1062194.11536,
|
|
"totalSamplesCount": 28488000
|
|
}
|
|
`
|
|
peerConnectionStats := PeerConnectionStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypePeerConnection,
|
|
ID: "P",
|
|
DataChannelsOpened: 1,
|
|
DataChannelsClosed: 2,
|
|
DataChannelsRequested: 3,
|
|
DataChannelsAccepted: 4,
|
|
}
|
|
peerConnectionStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "peer-connection",
|
|
"id": "P",
|
|
"dataChannelsOpened": 1,
|
|
"dataChannelsClosed": 2,
|
|
"dataChannelsRequested": 3,
|
|
"dataChannelsAccepted": 4
|
|
}
|
|
`
|
|
dataChannelStats := DataChannelStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeDataChannel,
|
|
ID: "D1",
|
|
Label: "display",
|
|
Protocol: "protocol",
|
|
DataChannelIdentifier: 1,
|
|
TransportID: "T1",
|
|
State: DataChannelStateOpen,
|
|
MessagesSent: 1,
|
|
BytesSent: 16,
|
|
MessagesReceived: 2,
|
|
BytesReceived: 20,
|
|
}
|
|
dataChannelStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "data-channel",
|
|
"id": "D1",
|
|
"label": "display",
|
|
"protocol": "protocol",
|
|
"dataChannelIdentifier": 1,
|
|
"transportId": "T1",
|
|
"state": "open",
|
|
"messagesSent": 1,
|
|
"bytesSent": 16,
|
|
"messagesReceived": 2,
|
|
"bytesReceived": 20
|
|
}
|
|
`
|
|
streamStats := MediaStreamStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeStream,
|
|
ID: "ROA2184088143",
|
|
StreamIdentifier: "S1",
|
|
TrackIDs: []string{"d57dbc4b-484b-4b40-9088-d3150e3a2010"},
|
|
}
|
|
streamStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "stream",
|
|
"id": "ROA2184088143",
|
|
"streamIdentifier": "S1",
|
|
"trackIds": [
|
|
"d57dbc4b-484b-4b40-9088-d3150e3a2010"
|
|
]
|
|
}
|
|
`
|
|
senderVideoTrackAttachmentStats := SenderVideoTrackAttachmentStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeTrack,
|
|
ID: "S2",
|
|
Kind: "video",
|
|
FramesCaptured: 1,
|
|
FramesSent: 2,
|
|
HugeFramesSent: 3,
|
|
KeyFramesSent: 4,
|
|
}
|
|
senderVideoTrackAttachmentStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "track",
|
|
"id": "S2",
|
|
"kind": "video",
|
|
"framesCaptured": 1,
|
|
"framesSent": 2,
|
|
"hugeFramesSent": 3,
|
|
"keyFramesSent": 4
|
|
}
|
|
`
|
|
senderAudioTrackAttachmentStats := SenderAudioTrackAttachmentStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeTrack,
|
|
ID: "S1",
|
|
TrackIdentifier: "audio",
|
|
RemoteSource: true,
|
|
Ended: true,
|
|
Kind: "audio",
|
|
AudioLevel: 0.1,
|
|
TotalAudioEnergy: 0.2,
|
|
VoiceActivityFlag: true,
|
|
TotalSamplesDuration: 0.3,
|
|
EchoReturnLoss: 0.4,
|
|
EchoReturnLossEnhancement: 0.5,
|
|
TotalSamplesSent: 200,
|
|
}
|
|
senderAudioTrackAttachmentStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "track",
|
|
"id": "S1",
|
|
"trackIdentifier": "audio",
|
|
"remoteSource": true,
|
|
"ended": true,
|
|
"kind": "audio",
|
|
"audioLevel": 0.1,
|
|
"totalAudioEnergy": 0.2,
|
|
"voiceActivityFlag": true,
|
|
"totalSamplesDuration": 0.3,
|
|
"echoReturnLoss": 0.4,
|
|
"echoReturnLossEnhancement": 0.5,
|
|
"totalSamplesSent": 200
|
|
}
|
|
`
|
|
videoSenderStats := VideoSenderStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeSender,
|
|
ID: "S2",
|
|
Kind: "video",
|
|
FramesCaptured: 1,
|
|
FramesSent: 2,
|
|
HugeFramesSent: 3,
|
|
KeyFramesSent: 4,
|
|
}
|
|
videoSenderStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "sender",
|
|
"id": "S2",
|
|
"kind": "video",
|
|
"framesCaptured": 1,
|
|
"framesSent": 2,
|
|
"hugeFramesSent": 3,
|
|
"keyFramesSent": 4
|
|
}
|
|
`
|
|
audioSenderStats := AudioSenderStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeSender,
|
|
ID: "S1",
|
|
TrackIdentifier: "audio",
|
|
RemoteSource: true,
|
|
Ended: true,
|
|
Kind: "audio",
|
|
AudioLevel: 0.1,
|
|
TotalAudioEnergy: 0.2,
|
|
VoiceActivityFlag: true,
|
|
TotalSamplesDuration: 0.3,
|
|
EchoReturnLoss: 0.4,
|
|
EchoReturnLossEnhancement: 0.5,
|
|
TotalSamplesSent: 200,
|
|
}
|
|
audioSenderStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "sender",
|
|
"id": "S1",
|
|
"trackIdentifier": "audio",
|
|
"remoteSource": true,
|
|
"ended": true,
|
|
"kind": "audio",
|
|
"audioLevel": 0.1,
|
|
"totalAudioEnergy": 0.2,
|
|
"voiceActivityFlag": true,
|
|
"totalSamplesDuration": 0.3,
|
|
"echoReturnLoss": 0.4,
|
|
"echoReturnLossEnhancement": 0.5,
|
|
"totalSamplesSent": 200
|
|
}
|
|
`
|
|
videoReceiverStats := VideoReceiverStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeReceiver,
|
|
ID: "ROA2184088143",
|
|
Kind: "video",
|
|
FrameWidth: 720,
|
|
FrameHeight: 480,
|
|
FramesPerSecond: 30.0,
|
|
EstimatedPlayoutTimestamp: 1688978831527.718,
|
|
JitterBufferDelay: 0.1,
|
|
JitterBufferEmittedCount: 1,
|
|
FramesReceived: 79,
|
|
KeyFramesReceived: 10,
|
|
FramesDecoded: 10,
|
|
FramesDropped: 10,
|
|
PartialFramesLost: 5,
|
|
FullFramesLost: 5,
|
|
}
|
|
videoReceiverStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "receiver",
|
|
"id": "ROA2184088143",
|
|
"kind": "video",
|
|
"frameWidth": 720,
|
|
"frameHeight": 480,
|
|
"framesPerSecond": 30.0,
|
|
"estimatedPlayoutTimestamp": 1688978831527.718,
|
|
"jitterBufferDelay": 0.1,
|
|
"jitterBufferEmittedCount": 1,
|
|
"framesReceived": 79,
|
|
"keyFramesReceived": 10,
|
|
"framesDecoded": 10,
|
|
"framesDropped": 10,
|
|
"partialFramesLost": 5,
|
|
"fullFramesLost": 5
|
|
}
|
|
`
|
|
audioReceiverStats := AudioReceiverStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeReceiver,
|
|
ID: "R1",
|
|
Kind: "audio",
|
|
AudioLevel: 0.1,
|
|
TotalAudioEnergy: 0.2,
|
|
VoiceActivityFlag: true,
|
|
TotalSamplesDuration: 0.3,
|
|
EstimatedPlayoutTimestamp: 1688978831527.718,
|
|
JitterBufferDelay: 0.5,
|
|
JitterBufferEmittedCount: 6,
|
|
TotalSamplesReceived: 7,
|
|
ConcealedSamples: 8,
|
|
ConcealmentEvents: 9,
|
|
}
|
|
audioReceiverStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "receiver",
|
|
"id": "R1",
|
|
"kind": "audio",
|
|
"audioLevel": 0.1,
|
|
"totalAudioEnergy": 0.2,
|
|
"voiceActivityFlag": true,
|
|
"totalSamplesDuration": 0.3,
|
|
"estimatedPlayoutTimestamp": 1688978831527.718,
|
|
"jitterBufferDelay": 0.5,
|
|
"jitterBufferEmittedCount": 6,
|
|
"totalSamplesReceived": 7,
|
|
"concealedSamples": 8,
|
|
"concealmentEvents": 9
|
|
}
|
|
`
|
|
transportStats := TransportStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeTransport,
|
|
ID: "T01",
|
|
PacketsSent: 60,
|
|
PacketsReceived: 8,
|
|
BytesSent: 6517,
|
|
BytesReceived: 1159,
|
|
RTCPTransportStatsID: "T01",
|
|
ICERole: ICERoleControlling,
|
|
DTLSState: DTLSTransportStateConnected,
|
|
SelectedCandidatePairID: "CPxIhBDNnT_sPDhy1TB",
|
|
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",
|
|
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",
|
|
DTLSCipher: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
SRTPCipher: "AES_CM_128_HMAC_SHA1_80",
|
|
}
|
|
transportStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "transport",
|
|
"id": "T01",
|
|
"packetsSent": 60,
|
|
"packetsReceived": 8,
|
|
"bytesSent": 6517,
|
|
"bytesReceived": 1159,
|
|
"rtcpTransportStatsId": "T01",
|
|
"iceRole": "controlling",
|
|
"dtlsState": "connected",
|
|
"selectedCandidatePairId": "CPxIhBDNnT_sPDhy1TB",
|
|
"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",
|
|
"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",
|
|
"dtlsCipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
"srtpCipher": "AES_CM_128_HMAC_SHA1_80"
|
|
}
|
|
`
|
|
iceCandidatePairStats := ICECandidatePairStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeCandidatePair,
|
|
ID: "CPxIhBDNnT_LlMJOnBv",
|
|
TransportID: "T01",
|
|
LocalCandidateID: "IxIhBDNnT",
|
|
RemoteCandidateID: "ILlMJOnBv",
|
|
State: "waiting",
|
|
Nominated: true,
|
|
PacketsSent: 1,
|
|
PacketsReceived: 2,
|
|
BytesSent: 3,
|
|
BytesReceived: 4,
|
|
LastPacketSentTimestamp: 5,
|
|
LastPacketReceivedTimestamp: 6,
|
|
FirstRequestTimestamp: 7,
|
|
LastRequestTimestamp: 8,
|
|
LastResponseTimestamp: 9,
|
|
TotalRoundTripTime: 10,
|
|
CurrentRoundTripTime: 11,
|
|
AvailableOutgoingBitrate: 12,
|
|
AvailableIncomingBitrate: 13,
|
|
CircuitBreakerTriggerCount: 14,
|
|
RequestsReceived: 15,
|
|
RequestsSent: 16,
|
|
ResponsesReceived: 17,
|
|
ResponsesSent: 18,
|
|
RetransmissionsReceived: 19,
|
|
RetransmissionsSent: 20,
|
|
ConsentRequestsSent: 21,
|
|
ConsentExpiredTimestamp: 22,
|
|
}
|
|
iceCandidatePairStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "candidate-pair",
|
|
"id": "CPxIhBDNnT_LlMJOnBv",
|
|
"transportId": "T01",
|
|
"localCandidateId": "IxIhBDNnT",
|
|
"remoteCandidateId": "ILlMJOnBv",
|
|
"state": "waiting",
|
|
"nominated": true,
|
|
"packetsSent": 1,
|
|
"packetsReceived": 2,
|
|
"bytesSent": 3,
|
|
"bytesReceived": 4,
|
|
"lastPacketSentTimestamp": 5,
|
|
"lastPacketReceivedTimestamp": 6,
|
|
"firstRequestTimestamp": 7,
|
|
"lastRequestTimestamp": 8,
|
|
"lastResponseTimestamp": 9,
|
|
"totalRoundTripTime": 10,
|
|
"currentRoundTripTime": 11,
|
|
"availableOutgoingBitrate": 12,
|
|
"availableIncomingBitrate": 13,
|
|
"circuitBreakerTriggerCount": 14,
|
|
"requestsReceived": 15,
|
|
"requestsSent": 16,
|
|
"responsesReceived": 17,
|
|
"responsesSent": 18,
|
|
"retransmissionsReceived": 19,
|
|
"retransmissionsSent": 20,
|
|
"consentRequestsSent": 21,
|
|
"consentExpiredTimestamp": 22
|
|
}
|
|
`
|
|
localIceCandidateStats := ICECandidateStats{
|
|
Timestamp: 1688978831527.718,
|
|
Type: StatsTypeLocalCandidate,
|
|
ID: "ILO8S8KYr",
|
|
TransportID: "T01",
|
|
NetworkType: "wifi",
|
|
IP: "192.168.0.36",
|
|
Port: 65400,
|
|
Protocol: "udp",
|
|
CandidateType: ICECandidateTypeHost,
|
|
Priority: 2122260223,
|
|
URL: "example.com",
|
|
RelayProtocol: "tcp",
|
|
Deleted: true,
|
|
}
|
|
localIceCandidateStatsJSON := `
|
|
{
|
|
"timestamp": 1688978831527.718,
|
|
"type": "local-candidate",
|
|
"id": "ILO8S8KYr",
|
|
"transportId": "T01",
|
|
"networkType": "wifi",
|
|
"ip": "192.168.0.36",
|
|
"port": 65400,
|
|
"protocol": "udp",
|
|
"candidateType": "host",
|
|
"priority": 2122260223,
|
|
"url": "example.com",
|
|
"relayProtocol": "tcp",
|
|
"deleted": true
|
|
}
|
|
`
|
|
remoteIceCandidateStats := ICECandidateStats{
|
|
Timestamp: 1689668364374.181,
|
|
Type: StatsTypeRemoteCandidate,
|
|
ID: "IGPGeswsH",
|
|
TransportID: "T01",
|
|
IP: "10.213.237.226",
|
|
Port: 50618,
|
|
Protocol: "udp",
|
|
CandidateType: ICECandidateTypeHost,
|
|
Priority: 2122194687,
|
|
URL: "example.com",
|
|
RelayProtocol: "tcp",
|
|
Deleted: true,
|
|
}
|
|
remoteIceCandidateStatsJSON := `
|
|
{
|
|
"timestamp": 1689668364374.181,
|
|
"type": "remote-candidate",
|
|
"id": "IGPGeswsH",
|
|
"transportId": "T01",
|
|
"ip": "10.213.237.226",
|
|
"port": 50618,
|
|
"protocol": "udp",
|
|
"candidateType": "host",
|
|
"priority": 2122194687,
|
|
"url": "example.com",
|
|
"relayProtocol": "tcp",
|
|
"deleted": true
|
|
}
|
|
`
|
|
certificateStats := CertificateStats{
|
|
Timestamp: 1689668364374.479,
|
|
Type: StatsTypeCertificate,
|
|
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",
|
|
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",
|
|
FingerprintAlgorithm: "sha-256",
|
|
Base64Certificate: "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+",
|
|
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",
|
|
}
|
|
certificateStatsJSON := `
|
|
{
|
|
"timestamp": 1689668364374.479,
|
|
"type": "certificate",
|
|
"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",
|
|
"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",
|
|
"fingerprintAlgorithm": "sha-256",
|
|
"base64Certificate": "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+",
|
|
"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"
|
|
}
|
|
`
|
|
|
|
return []statSample{
|
|
{
|
|
name: "codec_stats",
|
|
stats: codecStats,
|
|
json: codecStatsJSON,
|
|
},
|
|
{
|
|
name: "inbound_rtp_stream_stats",
|
|
stats: inboundRTPStreamStats,
|
|
json: inboundRTPStreamStatsJSON,
|
|
},
|
|
{
|
|
name: "outbound_rtp_stream_stats",
|
|
stats: outboundRTPStreamStats,
|
|
json: outboundRTPStreamStatsJSON,
|
|
},
|
|
{
|
|
name: "remote_inbound_rtp_stream_stats",
|
|
stats: remoteInboundRTPStreamStats,
|
|
json: remoteInboundRTPStreamStatsJSON,
|
|
},
|
|
{
|
|
name: "remote_outbound_rtp_stream_stats",
|
|
stats: remoteOutboundRTPStreamStats,
|
|
json: remoteOutboundRTPStreamStatsJSON,
|
|
},
|
|
{
|
|
name: "rtp_contributing_source_stats",
|
|
stats: csrcStats,
|
|
json: csrcStatsJSON,
|
|
},
|
|
{
|
|
name: "audio_source_stats",
|
|
stats: audioSourceStats,
|
|
json: audioSourceStatsJSON,
|
|
},
|
|
{
|
|
name: "video_source_stats",
|
|
stats: videoSourceStats,
|
|
json: videoSourceStatsJSON,
|
|
},
|
|
{
|
|
name: "audio_playout_stats",
|
|
stats: audioPlayoutStats,
|
|
json: audioPlayoutStatsJSON,
|
|
},
|
|
{
|
|
name: "peer_connection_stats",
|
|
stats: peerConnectionStats,
|
|
json: peerConnectionStatsJSON,
|
|
},
|
|
{
|
|
name: "data_channel_stats",
|
|
stats: dataChannelStats,
|
|
json: dataChannelStatsJSON,
|
|
},
|
|
{
|
|
name: "media_stream_stats",
|
|
stats: streamStats,
|
|
json: streamStatsJSON,
|
|
},
|
|
{
|
|
name: "sender_video_track_stats",
|
|
stats: senderVideoTrackAttachmentStats,
|
|
json: senderVideoTrackAttachmentStatsJSON,
|
|
},
|
|
{
|
|
name: "sender_audio_track_stats",
|
|
stats: senderAudioTrackAttachmentStats,
|
|
json: senderAudioTrackAttachmentStatsJSON,
|
|
},
|
|
{
|
|
name: "receiver_video_track_stats",
|
|
stats: videoSenderStats,
|
|
json: videoSenderStatsJSON,
|
|
},
|
|
{
|
|
name: "receiver_audio_track_stats",
|
|
stats: audioSenderStats,
|
|
json: audioSenderStatsJSON,
|
|
},
|
|
{
|
|
name: "receiver_video_track_stats",
|
|
stats: videoReceiverStats,
|
|
json: videoReceiverStatsJSON,
|
|
},
|
|
{
|
|
name: "receiver_audio_track_stats",
|
|
stats: audioReceiverStats,
|
|
json: audioReceiverStatsJSON,
|
|
},
|
|
{
|
|
name: "transport_stats",
|
|
stats: transportStats,
|
|
json: transportStatsJSON,
|
|
},
|
|
{
|
|
name: "ice_candidate_pair_stats",
|
|
stats: iceCandidatePairStats,
|
|
json: iceCandidatePairStatsJSON,
|
|
},
|
|
{
|
|
name: "local_ice_candidate_stats",
|
|
stats: localIceCandidateStats,
|
|
json: localIceCandidateStatsJSON,
|
|
},
|
|
{
|
|
name: "remote_ice_candidate_stats",
|
|
stats: remoteIceCandidateStats,
|
|
json: remoteIceCandidateStatsJSON,
|
|
},
|
|
{
|
|
name: "certificate_stats",
|
|
stats: certificateStats,
|
|
json: certificateStatsJSON,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestStatsMarshal(t *testing.T) {
|
|
for _, test := range getStatsSamples() {
|
|
t.Run(test.name+"_marshal", func(t *testing.T) {
|
|
actualJSON, err := json.Marshal(test.stats)
|
|
require.NoError(t, err)
|
|
|
|
assert.JSONEq(t, test.json, string(actualJSON))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStatsUnmarshal(t *testing.T) {
|
|
for _, test := range getStatsSamples() {
|
|
t.Run(test.name+"_unmarshal", func(t *testing.T) {
|
|
actualStats, err := UnmarshalStatsJSON([]byte(test.json))
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, test.stats, actualStats)
|
|
})
|
|
}
|
|
}
|
|
|
|
func waitWithTimeout(t *testing.T, wg *sync.WaitGroup) {
|
|
// Wait for all of the event handlers to be triggered.
|
|
done := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
done <- struct{}{}
|
|
}()
|
|
timeout := time.After(5 * time.Second)
|
|
select {
|
|
case <-done:
|
|
break
|
|
case <-timeout:
|
|
t.Fatal("timed out waiting for waitgroup")
|
|
}
|
|
}
|
|
|
|
func getConnectionStats(t *testing.T, report StatsReport, pc *PeerConnection) PeerConnectionStats {
|
|
stats, ok := report.GetConnectionStats(pc)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, stats.Type, StatsTypePeerConnection)
|
|
return stats
|
|
}
|
|
|
|
func getDataChannelStats(t *testing.T, report StatsReport, dc *DataChannel) DataChannelStats {
|
|
stats, ok := report.GetDataChannelStats(dc)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, stats.Type, StatsTypeDataChannel)
|
|
return stats
|
|
}
|
|
|
|
func getCodecStats(t *testing.T, report StatsReport, c *RTPCodecParameters) CodecStats {
|
|
stats, ok := report.GetCodecStats(c)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, stats.Type, StatsTypeCodec)
|
|
return stats
|
|
}
|
|
|
|
func getTransportStats(t *testing.T, report StatsReport, statsID string) TransportStats {
|
|
stats, ok := report[statsID]
|
|
assert.True(t, ok)
|
|
transportStats, ok := stats.(TransportStats)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, transportStats.Type, StatsTypeTransport)
|
|
return transportStats
|
|
}
|
|
|
|
func getSctpTransportStats(t *testing.T, report StatsReport) SCTPTransportStats {
|
|
stats, ok := report["sctpTransport"]
|
|
assert.True(t, ok)
|
|
transportStats, ok := stats.(SCTPTransportStats)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, transportStats.Type, StatsTypeSCTPTransport)
|
|
return transportStats
|
|
}
|
|
|
|
func getCertificateStats(t *testing.T, report StatsReport, certificate *Certificate) CertificateStats {
|
|
certificateStats, ok := report.GetCertificateStats(certificate)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, certificateStats.Type, StatsTypeCertificate)
|
|
return certificateStats
|
|
}
|
|
|
|
func findLocalCandidateStats(report StatsReport) []ICECandidateStats {
|
|
result := []ICECandidateStats{}
|
|
for _, s := range report {
|
|
stats, ok := s.(ICECandidateStats)
|
|
if ok && stats.Type == StatsTypeLocalCandidate {
|
|
result = append(result, stats)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func findRemoteCandidateStats(report StatsReport) []ICECandidateStats {
|
|
result := []ICECandidateStats{}
|
|
for _, s := range report {
|
|
stats, ok := s.(ICECandidateStats)
|
|
if ok && stats.Type == StatsTypeRemoteCandidate {
|
|
result = append(result, stats)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func findCandidatePairStats(t *testing.T, report StatsReport) []ICECandidatePairStats {
|
|
result := []ICECandidatePairStats{}
|
|
for _, s := range report {
|
|
stats, ok := s.(ICECandidatePairStats)
|
|
if ok {
|
|
assert.Equal(t, StatsTypeCandidatePair, stats.Type)
|
|
result = append(result, stats)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func signalPairForStats(pcOffer *PeerConnection, pcAnswer *PeerConnection) error {
|
|
offerChan := make(chan SessionDescription)
|
|
pcOffer.OnICECandidate(func(candidate *ICECandidate) {
|
|
if candidate == nil {
|
|
offerChan <- *pcOffer.PendingLocalDescription()
|
|
}
|
|
})
|
|
|
|
offer, err := pcOffer.CreateOffer(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := pcOffer.SetLocalDescription(offer); err != nil {
|
|
return err
|
|
}
|
|
|
|
timeout := time.After(3 * time.Second)
|
|
select {
|
|
case <-timeout:
|
|
return errReceiveOfferTimeout
|
|
case offer := <-offerChan:
|
|
if err := pcAnswer.SetRemoteDescription(offer); err != nil {
|
|
return err
|
|
}
|
|
|
|
answer, err := pcAnswer.CreateAnswer(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = pcAnswer.SetLocalDescription(answer); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = pcOffer.SetRemoteDescription(answer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func TestStatsConvertState(t *testing.T) {
|
|
testCases := []struct {
|
|
ice ice.CandidatePairState
|
|
stats StatsICECandidatePairState
|
|
}{
|
|
{
|
|
ice.CandidatePairStateWaiting,
|
|
StatsICECandidatePairStateWaiting,
|
|
},
|
|
{
|
|
ice.CandidatePairStateInProgress,
|
|
StatsICECandidatePairStateInProgress,
|
|
},
|
|
{
|
|
ice.CandidatePairStateFailed,
|
|
StatsICECandidatePairStateFailed,
|
|
},
|
|
{
|
|
ice.CandidatePairStateSucceeded,
|
|
StatsICECandidatePairStateSucceeded,
|
|
},
|
|
}
|
|
|
|
s, err := toStatsICECandidatePairState(ice.CandidatePairState(42))
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t,
|
|
StatsICECandidatePairState("Unknown"),
|
|
s)
|
|
for i, testCase := range testCases {
|
|
s, err := toStatsICECandidatePairState(testCase.ice)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t,
|
|
testCase.stats,
|
|
s,
|
|
"testCase: %d %v", i, testCase,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestPeerConnection_GetStats(t *testing.T) {
|
|
offerPC, answerPC, err := newPair()
|
|
assert.NoError(t, err)
|
|
|
|
track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1")
|
|
require.NoError(t, err)
|
|
|
|
_, err = offerPC.AddTrack(track1)
|
|
require.NoError(t, err)
|
|
|
|
baseLineReportPCOffer := offerPC.GetStats()
|
|
baseLineReportPCAnswer := answerPC.GetStats()
|
|
|
|
connStatsOffer := getConnectionStats(t, baseLineReportPCOffer, offerPC)
|
|
connStatsAnswer := getConnectionStats(t, baseLineReportPCAnswer, answerPC)
|
|
|
|
for _, connStats := range []PeerConnectionStats{connStatsOffer, connStatsAnswer} {
|
|
assert.Equal(t, uint32(0), connStats.DataChannelsOpened)
|
|
assert.Equal(t, uint32(0), connStats.DataChannelsClosed)
|
|
assert.Equal(t, uint32(0), connStats.DataChannelsRequested)
|
|
assert.Equal(t, uint32(0), connStats.DataChannelsAccepted)
|
|
}
|
|
|
|
// Create a DC, open it and send a message
|
|
offerDC, err := offerPC.CreateDataChannel("offerDC", nil)
|
|
assert.NoError(t, err)
|
|
|
|
msg := []byte("a classic test message")
|
|
offerDC.OnOpen(func() {
|
|
assert.NoError(t, offerDC.Send(msg))
|
|
})
|
|
|
|
dcWait := sync.WaitGroup{}
|
|
dcWait.Add(1)
|
|
|
|
answerDCChan := make(chan *DataChannel)
|
|
answerPC.OnDataChannel(func(d *DataChannel) {
|
|
d.OnOpen(func() {
|
|
answerDCChan <- d
|
|
})
|
|
d.OnMessage(func(DataChannelMessage) {
|
|
dcWait.Done()
|
|
})
|
|
})
|
|
|
|
assert.NoError(t, signalPairForStats(offerPC, answerPC))
|
|
waitWithTimeout(t, &dcWait)
|
|
|
|
answerDC := <-answerDCChan
|
|
|
|
reportPCOffer := offerPC.GetStats()
|
|
reportPCAnswer := answerPC.GetStats()
|
|
|
|
connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC)
|
|
assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened)
|
|
assert.Equal(t, uint32(0), connStatsOffer.DataChannelsClosed)
|
|
assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested)
|
|
assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted)
|
|
dcStatsOffer := getDataChannelStats(t, reportPCOffer, offerDC)
|
|
assert.Equal(t, DataChannelStateOpen, dcStatsOffer.State)
|
|
assert.Equal(t, uint32(1), dcStatsOffer.MessagesSent)
|
|
assert.Equal(t, uint64(len(msg)), dcStatsOffer.BytesSent)
|
|
assert.NotEmpty(t, findLocalCandidateStats(reportPCOffer))
|
|
assert.NotEmpty(t, findRemoteCandidateStats(reportPCOffer))
|
|
assert.NotEmpty(t, findCandidatePairStats(t, reportPCOffer))
|
|
|
|
connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC)
|
|
assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened)
|
|
assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsClosed)
|
|
assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested)
|
|
assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted)
|
|
dcStatsAnswer := getDataChannelStats(t, reportPCAnswer, answerDC)
|
|
assert.Equal(t, DataChannelStateOpen, dcStatsAnswer.State)
|
|
assert.Equal(t, uint32(1), dcStatsAnswer.MessagesReceived)
|
|
assert.Equal(t, uint64(len(msg)), dcStatsAnswer.BytesReceived)
|
|
assert.NotEmpty(t, findLocalCandidateStats(reportPCAnswer))
|
|
assert.NotEmpty(t, findRemoteCandidateStats(reportPCAnswer))
|
|
assert.NotEmpty(t, findCandidatePairStats(t, reportPCAnswer))
|
|
assert.NoError(t, err)
|
|
for i := range offerPC.api.mediaEngine.videoCodecs {
|
|
codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.videoCodecs[i]))
|
|
assert.NotEmpty(t, codecStat)
|
|
}
|
|
for i := range offerPC.api.mediaEngine.audioCodecs {
|
|
codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.audioCodecs[i]))
|
|
assert.NotEmpty(t, codecStat)
|
|
}
|
|
|
|
// Close answer DC now
|
|
dcWait = sync.WaitGroup{}
|
|
dcWait.Add(1)
|
|
offerDC.OnClose(func() {
|
|
dcWait.Done()
|
|
})
|
|
assert.NoError(t, answerDC.Close())
|
|
waitWithTimeout(t, &dcWait)
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
reportPCOffer = offerPC.GetStats()
|
|
reportPCAnswer = answerPC.GetStats()
|
|
|
|
connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC)
|
|
assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened)
|
|
assert.Equal(t, uint32(1), connStatsOffer.DataChannelsClosed)
|
|
assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested)
|
|
assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted)
|
|
dcStatsOffer = getDataChannelStats(t, reportPCOffer, offerDC)
|
|
assert.Equal(t, DataChannelStateClosed, dcStatsOffer.State)
|
|
|
|
connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC)
|
|
assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened)
|
|
assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsClosed)
|
|
assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested)
|
|
assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted)
|
|
dcStatsAnswer = getDataChannelStats(t, reportPCAnswer, answerDC)
|
|
assert.Equal(t, DataChannelStateClosed, dcStatsAnswer.State)
|
|
|
|
answerICETransportStats := getTransportStats(t, reportPCAnswer, "iceTransport")
|
|
offerICETransportStats := getTransportStats(t, reportPCOffer, "iceTransport")
|
|
assert.GreaterOrEqual(t, offerICETransportStats.BytesSent, answerICETransportStats.BytesReceived)
|
|
assert.GreaterOrEqual(t, answerICETransportStats.BytesSent, offerICETransportStats.BytesReceived)
|
|
|
|
answerSCTPTransportStats := getSctpTransportStats(t, reportPCAnswer)
|
|
offerSCTPTransportStats := getSctpTransportStats(t, reportPCOffer)
|
|
assert.GreaterOrEqual(t, offerSCTPTransportStats.BytesSent, answerSCTPTransportStats.BytesReceived)
|
|
assert.GreaterOrEqual(t, answerSCTPTransportStats.BytesSent, offerSCTPTransportStats.BytesReceived)
|
|
|
|
certificates := offerPC.configuration.Certificates
|
|
|
|
for i := range certificates {
|
|
assert.NotEmpty(t, getCertificateStats(t, reportPCOffer, &certificates[i]))
|
|
}
|
|
|
|
closePairNow(t, offerPC, answerPC)
|
|
}
|
|
|
|
func TestPeerConnection_GetStats_Closed(t *testing.T) {
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, pc.Close())
|
|
|
|
pc.GetStats()
|
|
}
|