stats_go_test.go 38 KB

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