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

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