You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConnectionStatsTable.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /* @flow */
  2. import React, { Component } from 'react';
  3. import { translate } from '../../base/i18n';
  4. /**
  5. * The type of the React {@code Component} props of
  6. * {@link ConnectionStatsTable}.
  7. */
  8. type Props = {
  9. /**
  10. * Statistics related to bandwidth.
  11. * {{
  12. * download: Number,
  13. * upload: Number
  14. * }}
  15. */
  16. bandwidth: Object,
  17. /**
  18. * Statistics related to bitrate.
  19. * {{
  20. * download: Number,
  21. * upload: Number
  22. * }}
  23. */
  24. bitrate: Object,
  25. /**
  26. * The number of bridges (aka media servers) currently used in the
  27. * conference.
  28. */
  29. bridgeCount: number,
  30. /**
  31. * A message describing the connection quality.
  32. */
  33. connectionSummary: string,
  34. /**
  35. * The end-to-end round-trip-time.
  36. */
  37. e2eRtt: number,
  38. /**
  39. * Statistics related to frame rates for each ssrc.
  40. * {{
  41. * [ ssrc ]: Number
  42. * }}
  43. */
  44. framerate: Object,
  45. /**
  46. * Whether or not the statistics are for local video.
  47. */
  48. isLocalVideo: boolean,
  49. /**
  50. * The send-side max enabled resolution (aka the highest layer that is not
  51. * suspended on the send-side).
  52. */
  53. maxEnabledResolution: number,
  54. /**
  55. * Callback to invoke when the show additional stats link is clicked.
  56. */
  57. onShowMore: Function,
  58. /**
  59. * Statistics related to packet loss.
  60. * {{
  61. * download: Number,
  62. * upload: Number
  63. * }}
  64. */
  65. packetLoss: Object,
  66. /**
  67. * The region that we think the client is in.
  68. */
  69. region: string,
  70. /**
  71. * Statistics related to display resolutions for each ssrc.
  72. * {{
  73. * [ ssrc ]: {
  74. * height: Number,
  75. * width: Number
  76. * }
  77. * }}
  78. */
  79. resolution: Object,
  80. /**
  81. * The region of the media server that we are connected to.
  82. */
  83. serverRegion: string,
  84. /**
  85. * Whether or not additional stats about bandwidth and transport should be
  86. * displayed. Will not display even if true for remote participants.
  87. */
  88. shouldShowMore: boolean,
  89. /**
  90. * Invoked to obtain translated strings.
  91. */
  92. t: Function,
  93. /**
  94. * Statistics related to transports.
  95. */
  96. transport: Array<Object>
  97. };
  98. /**
  99. * React {@code Component} for displaying connection statistics.
  100. *
  101. * @extends Component
  102. */
  103. class ConnectionStatsTable extends Component<Props> {
  104. /**
  105. * Implements React's {@link Component#render()}.
  106. *
  107. * @inheritdoc
  108. * @returns {ReactElement}
  109. */
  110. render() {
  111. const { isLocalVideo } = this.props;
  112. return (
  113. <div className = 'connection-info'>
  114. { this._renderStatistics() }
  115. { isLocalVideo ? this._renderShowMoreLink() : null }
  116. { isLocalVideo && this.props.shouldShowMore
  117. ? this._renderAdditionalStats() : null }
  118. </div>
  119. );
  120. }
  121. /**
  122. * Creates a table as ReactElement that will display additional statistics
  123. * related to bandwidth and transport for the local user.
  124. *
  125. * @private
  126. * @returns {ReactElement}
  127. */
  128. _renderAdditionalStats() {
  129. return (
  130. <table className = 'connection-info__container'>
  131. <tbody>
  132. { this._renderBandwidth() }
  133. { this._renderTransport() }
  134. { this._renderRegion() }
  135. </tbody>
  136. </table>
  137. );
  138. }
  139. /**
  140. * Creates a table row as a ReactElement for displaying bandwidth related
  141. * statistics.
  142. *
  143. * @private
  144. * @returns {ReactElement}
  145. */
  146. _renderBandwidth() {
  147. const { download, upload } = this.props.bandwidth || {};
  148. return (
  149. <tr>
  150. <td>
  151. { this.props.t('connectionindicator.bandwidth') }
  152. </td>
  153. <td>
  154. <span className = 'connection-info__download'>
  155. &darr;
  156. </span>
  157. { download ? `${download} Kbps` : 'N/A' }
  158. <span className = 'connection-info__upload'>
  159. &uarr;
  160. </span>
  161. { upload ? `${upload} Kbps` : 'N/A' }
  162. </td>
  163. </tr>
  164. );
  165. }
  166. /**
  167. * Creates a a table row as a ReactElement for displaying bitrate related
  168. * statistics.
  169. *
  170. * @private
  171. * @returns {ReactElement}
  172. */
  173. _renderBitrate() {
  174. const { download, upload } = this.props.bitrate || {};
  175. return (
  176. <tr>
  177. <td>
  178. <span>
  179. { this.props.t('connectionindicator.bitrate') }
  180. </span>
  181. </td>
  182. <td>
  183. <span className = 'connection-info__download'>
  184. &darr;
  185. </span>
  186. { download ? `${download} Kbps` : 'N/A' }
  187. <span className = 'connection-info__upload'>
  188. &uarr;
  189. </span>
  190. { upload ? `${upload} Kbps` : 'N/A' }
  191. </td>
  192. </tr>
  193. );
  194. }
  195. /**
  196. * Creates a table row as a ReactElement for displaying a summary message
  197. * about the current connection status.
  198. *
  199. * @private
  200. * @returns {ReactElement}
  201. */
  202. _renderConnectionSummary() {
  203. return (
  204. <tr className = 'connection-info__status'>
  205. <td>
  206. <span>{ this.props.t('connectionindicator.status') }</span>
  207. </td>
  208. <td>{ this.props.connectionSummary }</td>
  209. </tr>
  210. );
  211. }
  212. /**
  213. * Creates a table row as a ReactElement for displaying end-to-end RTT and
  214. * the region.
  215. *
  216. * @returns {ReactElement}
  217. * @private
  218. */
  219. _renderE2eRtt() {
  220. const { e2eRtt, t } = this.props;
  221. const str = e2eRtt ? `${e2eRtt.toFixed(0)}ms` : 'N/A';
  222. return (
  223. <tr>
  224. <td>
  225. <span>{ t('connectionindicator.e2e_rtt') }</span>
  226. </td>
  227. <td>{ str }</td>
  228. </tr>
  229. );
  230. }
  231. /**
  232. * Creates a table row as a ReactElement for displaying the "connected to"
  233. * information.
  234. *
  235. * @returns {ReactElement}
  236. * @private
  237. */
  238. _renderRegion() {
  239. const { region, serverRegion, t } = this.props;
  240. let str = serverRegion;
  241. if (!serverRegion) {
  242. return;
  243. }
  244. if (region && serverRegion && region !== serverRegion) {
  245. str += ` from ${region}`;
  246. }
  247. return (
  248. <tr>
  249. <td>
  250. <span>{ t('connectionindicator.connectedTo') }</span>
  251. </td>
  252. <td>{ str }</td>
  253. </tr>
  254. );
  255. }
  256. /**
  257. * Creates a table row as a ReactElement for displaying the "bridge count"
  258. * information.
  259. *
  260. * @returns {*}
  261. * @private
  262. */
  263. _renderBridgeCount() {
  264. const { bridgeCount, t } = this.props;
  265. // 0 is valid, but undefined/null/NaN aren't.
  266. if (!bridgeCount && bridgeCount !== 0) {
  267. return;
  268. }
  269. return (
  270. <tr>
  271. <td>
  272. <span>{ t('connectionindicator.bridgeCount') }</span>
  273. </td>
  274. <td>{ bridgeCount }</td>
  275. </tr>
  276. );
  277. }
  278. /**
  279. * Creates a table row as a ReactElement for displaying frame rate related
  280. * statistics.
  281. *
  282. * @private
  283. * @returns {ReactElement}
  284. */
  285. _renderFrameRate() {
  286. const { framerate, t } = this.props;
  287. const frameRateString = Object.keys(framerate || {})
  288. .map(ssrc => framerate[ssrc])
  289. .join(', ') || 'N/A';
  290. return (
  291. <tr>
  292. <td>
  293. <span>{ t('connectionindicator.framerate') }</span>
  294. </td>
  295. <td>{ frameRateString }</td>
  296. </tr>
  297. );
  298. }
  299. /**
  300. * Creates a tables row as a ReactElement for displaying packet loss related
  301. * statistics.
  302. *
  303. * @private
  304. * @returns {ReactElement}
  305. */
  306. _renderPacketLoss() {
  307. const { packetLoss, t } = this.props;
  308. let packetLossTableData;
  309. if (packetLoss) {
  310. const { download, upload } = packetLoss;
  311. packetLossTableData = (
  312. <td>
  313. <span className = 'connection-info__download'>
  314. &darr;
  315. </span>
  316. { download === null ? 'N/A' : `${download}%` }
  317. <span className = 'connection-info__upload'>
  318. &uarr;
  319. </span>
  320. { upload === null ? 'N/A' : `${upload}%` }
  321. </td>
  322. );
  323. } else {
  324. packetLossTableData = <td>N/A</td>;
  325. }
  326. return (
  327. <tr>
  328. <td>
  329. <span>
  330. { t('connectionindicator.packetloss') }
  331. </span>
  332. </td>
  333. { packetLossTableData }
  334. </tr>
  335. );
  336. }
  337. /**
  338. * Creates a table row as a ReactElement for displaying resolution related
  339. * statistics.
  340. *
  341. * @private
  342. * @returns {ReactElement}
  343. */
  344. _renderResolution() {
  345. const { resolution, maxEnabledResolution, t } = this.props;
  346. let resolutionString = Object.keys(resolution || {})
  347. .map(ssrc => {
  348. const { width, height } = resolution[ssrc];
  349. return `${width}x${height}`;
  350. })
  351. .join(', ') || 'N/A';
  352. if (maxEnabledResolution && maxEnabledResolution < 720) {
  353. const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');
  354. resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`;
  355. }
  356. return (
  357. <tr>
  358. <td>
  359. <span>{ t('connectionindicator.resolution') }</span>
  360. </td>
  361. <td>{ resolutionString }</td>
  362. </tr>
  363. );
  364. }
  365. /**
  366. * Creates a ReactElement for display a link to toggle showing additional
  367. * statistics.
  368. *
  369. * @private
  370. * @returns {ReactElement}
  371. */
  372. _renderShowMoreLink() {
  373. const translationKey
  374. = this.props.shouldShowMore
  375. ? 'connectionindicator.less'
  376. : 'connectionindicator.more';
  377. return (
  378. <a
  379. className = 'showmore link'
  380. onClick = { this.props.onShowMore } >
  381. { this.props.t(translationKey) }
  382. </a>
  383. );
  384. }
  385. /**
  386. * Creates a table as a ReactElement for displaying connection statistics.
  387. *
  388. * @private
  389. * @returns {ReactElement}
  390. */
  391. _renderStatistics() {
  392. const isRemoteVideo = !this.props.isLocalVideo;
  393. return (
  394. <table className = 'connection-info__container'>
  395. <tbody>
  396. { this._renderConnectionSummary() }
  397. { this._renderBitrate() }
  398. { this._renderPacketLoss() }
  399. { isRemoteVideo ? this._renderE2eRtt() : null }
  400. { isRemoteVideo ? this._renderRegion() : null }
  401. { this._renderResolution() }
  402. { this._renderFrameRate() }
  403. { isRemoteVideo ? null : this._renderBridgeCount() }
  404. </tbody>
  405. </table>
  406. );
  407. }
  408. /**
  409. * Creates table rows as ReactElements for displaying transport related
  410. * statistics.
  411. *
  412. * @private
  413. * @returns {ReactElement[]}
  414. */
  415. _renderTransport() {
  416. const { t, transport } = this.props;
  417. if (!transport || transport.length === 0) {
  418. const NA = (
  419. <tr key = 'address'>
  420. <td>
  421. <span>{ t('connectionindicator.address') }</span>
  422. </td>
  423. <td>
  424. N/A
  425. </td>
  426. </tr>
  427. );
  428. return [ NA ];
  429. }
  430. const data = {
  431. localIP: [],
  432. localPort: [],
  433. remoteIP: [],
  434. remotePort: [],
  435. transportType: []
  436. };
  437. for (let i = 0; i < transport.length; i++) {
  438. const ip = getIP(transport[i].ip);
  439. const localIP = getIP(transport[i].localip);
  440. const localPort = getPort(transport[i].localip);
  441. const port = getPort(transport[i].ip);
  442. if (!data.remoteIP.includes(ip)) {
  443. data.remoteIP.push(ip);
  444. }
  445. if (!data.localIP.includes(localIP)) {
  446. data.localIP.push(localIP);
  447. }
  448. if (!data.localPort.includes(localPort)) {
  449. data.localPort.push(localPort);
  450. }
  451. if (!data.remotePort.includes(port)) {
  452. data.remotePort.push(port);
  453. }
  454. if (!data.transportType.includes(transport[i].type)) {
  455. data.transportType.push(transport[i].type);
  456. }
  457. }
  458. // All of the transports should be either P2P or JVB
  459. let isP2P = false, isTURN = false;
  460. if (transport.length) {
  461. isP2P = transport[0].p2p;
  462. isTURN = transport[0].localCandidateType === 'relay'
  463. || transport[0].remoteCandidateType === 'relay';
  464. }
  465. const additionalData = [];
  466. if (isP2P) {
  467. additionalData.push(
  468. <span> (p2p)</span>);
  469. }
  470. if (isTURN) {
  471. additionalData.push(<span> (turn)</span>);
  472. }
  473. // First show remote statistics, then local, and then transport type.
  474. const tableRowConfigurations = [
  475. {
  476. additionalData,
  477. data: data.remoteIP,
  478. key: 'remoteaddress',
  479. label: t('connectionindicator.remoteaddress',
  480. { count: data.remoteIP.length })
  481. },
  482. {
  483. data: data.remotePort,
  484. key: 'remoteport',
  485. label: t('connectionindicator.remoteport',
  486. { count: transport.length })
  487. },
  488. {
  489. data: data.localIP,
  490. key: 'localaddress',
  491. label: t('connectionindicator.localaddress',
  492. { count: data.localIP.length })
  493. },
  494. {
  495. data: data.localPort,
  496. key: 'localport',
  497. label: t('connectionindicator.localport',
  498. { count: transport.length })
  499. },
  500. {
  501. data: data.transportType,
  502. key: 'transport',
  503. label: t('connectionindicator.transport',
  504. { count: data.transportType.length })
  505. }
  506. ];
  507. return tableRowConfigurations.map(this._renderTransportTableRow);
  508. }
  509. /**
  510. * Creates a table row as a ReactElement for displaying a transport related
  511. * statistic.
  512. *
  513. * @param {Object} config - Describes the contents of the row.
  514. * @param {ReactElement} config.additionalData - Extra data to display next
  515. * to the passed in config.data.
  516. * @param {Array} config.data - The transport statistics to display.
  517. * @param {string} config.key - The ReactElement's key. Must be unique for
  518. * iterating over multiple child rows.
  519. * @param {string} config.label - The text to display describing the data.
  520. * @private
  521. * @returns {ReactElement}
  522. */
  523. _renderTransportTableRow(config: Object) {
  524. const { additionalData, data, key, label } = config;
  525. return (
  526. <tr key = { key }>
  527. <td>
  528. <span>
  529. { label }
  530. </span>
  531. </td>
  532. <td>
  533. { getStringFromArray(data) }
  534. { additionalData || null }
  535. </td>
  536. </tr>
  537. );
  538. }
  539. }
  540. /**
  541. * Utility for getting the IP from a transport statistics object's
  542. * representation of an IP.
  543. *
  544. * @param {string} value - The transport's IP to parse.
  545. * @private
  546. * @returns {string}
  547. */
  548. function getIP(value) {
  549. if (!value) {
  550. return '';
  551. }
  552. return value.substring(0, value.lastIndexOf(':'));
  553. }
  554. /**
  555. * Utility for getting the port from a transport statistics object's
  556. * representation of an IP.
  557. *
  558. * @param {string} value - The transport's IP to parse.
  559. * @private
  560. * @returns {string}
  561. */
  562. function getPort(value) {
  563. if (!value) {
  564. return '';
  565. }
  566. return value.substring(value.lastIndexOf(':') + 1, value.length);
  567. }
  568. /**
  569. * Utility for concatenating values in an array into a comma separated string.
  570. *
  571. * @param {Array} array - Transport statistics to concatenate.
  572. * @private
  573. * @returns {string}
  574. */
  575. function getStringFromArray(array) {
  576. let res = '';
  577. for (let i = 0; i < array.length; i++) {
  578. res += (i === 0 ? '' : ', ') + array[i];
  579. }
  580. return res;
  581. }
  582. export default translate(ConnectionStatsTable);