Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
dpapad | 303c9281 | 2023-10-31 02:08:35 | [diff] [blame] | 5 | import {$} from 'chrome://resources/js/util.js'; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 6 | |
Henrik Boström | bbf023e | 2023-01-24 13:04:59 | [diff] [blame] | 7 | import {generateStatsLabel} from './stats_helper.js'; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 8 | |
| 9 | /** |
| 10 | * Maintains the stats table. |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 11 | */ |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 12 | export class StatsTable { |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 13 | constructor() {} |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 14 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 15 | /** |
| 16 | * Adds |report| to the stats table of |peerConnectionElement|. |
| 17 | * |
| 18 | * @param {!Element} peerConnectionElement The root element. |
| 19 | * @param {!Object} report The object containing stats, which is the object |
| 20 | * containing timestamp and values, which is an array of strings, whose |
| 21 | * even index entry is the name of the stat, and the odd index entry is |
| 22 | * the value. |
| 23 | */ |
| 24 | addStatsReport(peerConnectionElement, report) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 25 | const statsTable = this.ensureStatsTable_(peerConnectionElement, report); |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 26 | |
Philipp Hancke | f91ac40 | 2022-10-17 11:05:06 | [diff] [blame] | 27 | // Update the label since information may have changed. |
| 28 | statsTable.parentElement.firstElementChild.innerText = |
| 29 | generateStatsLabel(report); |
Philipp Hancke | b09c227 | 2021-11-08 10:45:02 | [diff] [blame] | 30 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 31 | if (report.stats) { |
| 32 | this.addStatsToTable_( |
| 33 | statsTable, report.stats.timestamp, report.stats.values); |
| 34 | } |
| 35 | } |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 36 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 37 | clearStatsLists(peerConnectionElement) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 38 | const containerId = peerConnectionElement.id + '-table-container'; |
Rebekah Potter | 8148f85 | 2022-11-29 18:04:37 | [diff] [blame] | 39 | // Disable getElementById restriction here, since |containerId| is not |
| 40 | // always a valid selector. |
| 41 | // eslint-disable-next-line no-restricted-properties |
| 42 | const container = document.getElementById(containerId); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 43 | if (container) { |
| 44 | peerConnectionElement.removeChild(container); |
| 45 | this.ensureStatsTableContainer_(peerConnectionElement); |
| 46 | } |
| 47 | } |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 48 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 49 | /** |
| 50 | * Ensure the DIV container for the stats tables is created as a child of |
| 51 | * |peerConnectionElement|. |
| 52 | * |
| 53 | * @param {!Element} peerConnectionElement The root element. |
| 54 | * @return {!Element} The stats table container. |
| 55 | * @private |
| 56 | */ |
| 57 | ensureStatsTableContainer_(peerConnectionElement) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 58 | const containerId = peerConnectionElement.id + '-table-container'; |
Rebekah Potter | 8148f85 | 2022-11-29 18:04:37 | [diff] [blame] | 59 | // Disable getElementById restriction here, since |containerId| is not |
| 60 | // always a valid selector. |
| 61 | // eslint-disable-next-line no-restricted-properties |
| 62 | let container = document.getElementById(containerId); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 63 | if (!container) { |
| 64 | container = document.createElement('div'); |
| 65 | container.id = containerId; |
| 66 | container.className = 'stats-table-container'; |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 67 | const head = document.createElement('div'); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 68 | head.textContent = 'Stats Tables'; |
| 69 | container.appendChild(head); |
Philipp Hancke | 9634113 | 2022-11-04 14:15:42 | [diff] [blame] | 70 | const label = document.createElement('label'); |
| 71 | label.innerText = 'Filter statistics by type including '; |
| 72 | container.appendChild(label); |
| 73 | const input = document.createElement('input'); |
| 74 | input.placeholder = 'separate multiple values by `,`'; |
| 75 | input.size = 25; |
| 76 | input.oninput = (e) => this.filterStats(e, container); |
| 77 | container.appendChild(input); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 78 | peerConnectionElement.appendChild(container); |
| 79 | } |
| 80 | return container; |
| 81 | } |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 82 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 83 | /** |
| 84 | * Ensure the stats table for track specified by |report| of PeerConnection |
| 85 | * |peerConnectionElement| is created. |
| 86 | * |
| 87 | * @param {!Element} peerConnectionElement The root element. |
| 88 | * @param {!Object} report The object containing stats, which is the object |
| 89 | * containing timestamp and values, which is an array of strings, whose |
| 90 | * even index entry is the name of the stat, and the odd index entry is |
| 91 | * the value. |
| 92 | * @return {!Element} The stats table element. |
| 93 | * @private |
| 94 | */ |
| 95 | ensureStatsTable_(peerConnectionElement, report) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 96 | const tableId = peerConnectionElement.id + '-table-' + report.id; |
Rebekah Potter | 8148f85 | 2022-11-29 18:04:37 | [diff] [blame] | 97 | // Disable getElementById restriction here, since |tableId| is not |
| 98 | // always a valid selector. |
| 99 | // eslint-disable-next-line no-restricted-properties |
| 100 | let table = document.getElementById(tableId); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 101 | if (!table) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 102 | const container = this.ensureStatsTableContainer_(peerConnectionElement); |
| 103 | const details = document.createElement('details'); |
Philipp Hancke | 9634113 | 2022-11-04 14:15:42 | [diff] [blame] | 104 | details.attributes['data-statsType'] = report.type; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 105 | container.appendChild(details); |
[email protected] | ca51ea0 | 2014-02-05 06:41:35 | [diff] [blame] | 106 | |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 107 | const summary = document.createElement('summary'); |
Philipp Hancke | f91ac40 | 2022-10-17 11:05:06 | [diff] [blame] | 108 | summary.textContent = generateStatsLabel(report); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 109 | details.appendChild(summary); |
[email protected] | 31e9184 | 2013-11-08 19:06:15 | [diff] [blame] | 110 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 111 | table = document.createElement('table'); |
| 112 | details.appendChild(table); |
| 113 | table.id = tableId; |
| 114 | table.border = 1; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 115 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 116 | table.appendChild($('trth-template').content.cloneNode(true)); |
| 117 | table.rows[0].cells[0].textContent = 'Statistics ' + report.id; |
Philipp Hancke | c68bc70c | 2024-06-10 14:47:05 | [diff] [blame] | 118 | table['data-peerconnection-id'] = peerConnectionElement.id; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 119 | } |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 120 | return table; |
| 121 | } |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 122 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 123 | /** |
| 124 | * Update |statsTable| with |time| and |statsData|. |
| 125 | * |
| 126 | * @param {!Element} statsTable Which table to update. |
| 127 | * @param {number} time The number of milliseconds since epoch. |
| 128 | * @param {Array<string>} statsData An array of stats name and value pairs. |
| 129 | * @private |
| 130 | */ |
| 131 | addStatsToTable_(statsTable, time, statsData) { |
Henrik Boström | 23dec44 | 2023-07-25 12:13:35 | [diff] [blame] | 132 | const definedMetrics = new Set(); |
| 133 | for (let i = 0; i < statsData.length - 1; i = i + 2) { |
| 134 | definedMetrics.add(statsData[i]); |
| 135 | } |
| 136 | // For any previously reported metric that is no longer defined, replace its |
| 137 | // now obsolete value with the magic string "(removed)". |
| 138 | const metricsContainer = statsTable.firstChild; |
| 139 | for (let i = 0; i < metricsContainer.children.length; ++i) { |
| 140 | const metricElement = metricsContainer.children[i]; |
Henrik Boström | 65ecac6d | 2023-09-14 09:10:43 | [diff] [blame] | 141 | // `metricElement` IDs have the format `bla-bla-bla-bla-${metricName}`. |
| 142 | let metricName = |
Henrik Boström | 23dec44 | 2023-07-25 12:13:35 | [diff] [blame] | 143 | metricElement.id.substring(metricElement.id.lastIndexOf('-') + 1); |
Henrik Boström | 65ecac6d | 2023-09-14 09:10:43 | [diff] [blame] | 144 | if (metricName.endsWith(']')) { |
| 145 | // Computed metrics may contain the '-' character (e.g. |
| 146 | // `DifferenceCalculator` based metrics) in which case `metricName` will |
| 147 | // not have been parsed correctly. Instead look for starting '['. |
| 148 | metricName = |
| 149 | metricElement.id.substring(metricElement.id.indexOf('[')); |
| 150 | } |
Henrik Boström | 23dec44 | 2023-07-25 12:13:35 | [diff] [blame] | 151 | if (metricName && metricName != 'timestamp' && |
| 152 | !definedMetrics.has(metricName)) { |
| 153 | this.updateStatsTableRow_(statsTable, metricName, '(removed)'); |
| 154 | } |
| 155 | } |
| 156 | // Add or update all "metric: value" that have a defined value. |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 157 | const date = new Date(time); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 158 | this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString()); |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 159 | for (let i = 0; i < statsData.length - 1; i = i + 2) { |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 160 | this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Update the value column of the stats row of |rowName| to |value|. |
| 166 | * A new row is created is this is the first report of this stats. |
| 167 | * |
| 168 | * @param {!Element} statsTable Which table to update. |
| 169 | * @param {string} rowName The name of the row to update. |
| 170 | * @param {string} value The new value to set. |
| 171 | * @private |
| 172 | */ |
| 173 | updateStatsTableRow_(statsTable, rowName, value) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 174 | const trId = statsTable.id + '-' + rowName; |
Rebekah Potter | 8148f85 | 2022-11-29 18:04:37 | [diff] [blame] | 175 | // Disable getElementById restriction here, since |trId| is not always |
| 176 | // a valid selector. |
| 177 | // eslint-disable-next-line no-restricted-properties |
| 178 | let trElement = document.getElementById(trId); |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 179 | const activeConnectionClass = 'stats-table-active-connection'; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 180 | if (!trElement) { |
| 181 | trElement = document.createElement('tr'); |
| 182 | trElement.id = trId; |
| 183 | statsTable.firstChild.appendChild(trElement); |
Philipp Hancke | c68bc70c | 2024-06-10 14:47:05 | [diff] [blame] | 184 | const item = $('statsrow-template').content.cloneNode(true); |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 185 | item.querySelector('td').textContent = rowName; |
| 186 | trElement.appendChild(item); |
| 187 | } |
| 188 | trElement.cells[1].textContent = value; |
Philipp Hancke | c68bc70c | 2024-06-10 14:47:05 | [diff] [blame] | 189 | if (rowName.endsWith('Id')) { |
| 190 | // unicode link symbol |
| 191 | trElement.cells[2].children[0].textContent = ' \u{1F517}'; |
| 192 | trElement.cells[2].children[0].href = |
| 193 | '#' + statsTable['data-peerconnection-id'] + '-table-' + value; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 194 | } |
| 195 | } |
Philipp Hancke | 9634113 | 2022-11-04 14:15:42 | [diff] [blame] | 196 | |
| 197 | /** |
| 198 | * Apply a filter to the stats table |
| 199 | * @param event InputEvent from the filter input field. |
| 200 | * @param container stats table container element. |
| 201 | * @private |
| 202 | */ |
| 203 | filterStats(event, container) { |
[email protected] | deb232b | 2022-11-28 11:22:16 | [diff] [blame] | 204 | const filter = event.target.value; |
Philipp Hancke | 9634113 | 2022-11-04 14:15:42 | [diff] [blame] | 205 | const filters = filter.split(','); |
| 206 | container.childNodes.forEach(node => { |
| 207 | if (node.nodeName !== 'DETAILS') { |
| 208 | return; |
| 209 | } |
| 210 | const statsType = node.attributes['data-statsType']; |
| 211 | if (!filter || filters.includes(statsType) || |
| 212 | filters.find(f => statsType.includes(f))) { |
| 213 | node.style.display = 'block'; |
| 214 | } else { |
| 215 | node.style.display = 'none'; |
| 216 | } |
| 217 | }); |
| 218 | } |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 219 | } |