blob: ffa4c1e02df6d21e515e1d37b3ae61fd015fac4b [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2013 The Chromium Authors
[email protected]2746df92013-04-26 23:42:322// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
dpapad303c92812023-10-31 02:08:355import {$} from 'chrome://resources/js/util.js';
rbpotter5796b002021-03-10 18:49:216
Henrik Boströmbbf023e2023-01-24 13:04:597import {generateStatsLabel} from './stats_helper.js';
[email protected]2746df92013-04-26 23:42:328
9/**
10 * Maintains the stats table.
[email protected]2746df92013-04-26 23:42:3211 */
rbpotter5796b002021-03-10 18:49:2112export class StatsTable {
Philipp Hanckeba4adef2023-12-20 14:43:1313 constructor() {}
[email protected]2746df92013-04-26 23:42:3214
rbpotter5796b002021-03-10 18:49:2115 /**
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 Hancke0635d3142021-03-16 20:02:0925 const statsTable = this.ensureStatsTable_(peerConnectionElement, report);
[email protected]2746df92013-04-26 23:42:3226
Philipp Hanckef91ac402022-10-17 11:05:0627 // Update the label since information may have changed.
28 statsTable.parentElement.firstElementChild.innerText =
29 generateStatsLabel(report);
Philipp Hanckeb09c2272021-11-08 10:45:0230
rbpotter5796b002021-03-10 18:49:2131 if (report.stats) {
32 this.addStatsToTable_(
33 statsTable, report.stats.timestamp, report.stats.values);
34 }
35 }
[email protected]2746df92013-04-26 23:42:3236
rbpotter5796b002021-03-10 18:49:2137 clearStatsLists(peerConnectionElement) {
Philipp Hancke0635d3142021-03-16 20:02:0938 const containerId = peerConnectionElement.id + '-table-container';
Rebekah Potter8148f852022-11-29 18:04:3739 // 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);
rbpotter5796b002021-03-10 18:49:2143 if (container) {
44 peerConnectionElement.removeChild(container);
45 this.ensureStatsTableContainer_(peerConnectionElement);
46 }
47 }
Henrik Boströmb6d732af2019-04-23 11:32:2948
rbpotter5796b002021-03-10 18:49:2149 /**
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 Hancke0635d3142021-03-16 20:02:0958 const containerId = peerConnectionElement.id + '-table-container';
Rebekah Potter8148f852022-11-29 18:04:3759 // 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);
rbpotter5796b002021-03-10 18:49:2163 if (!container) {
64 container = document.createElement('div');
65 container.id = containerId;
66 container.className = 'stats-table-container';
Philipp Hancke0635d3142021-03-16 20:02:0967 const head = document.createElement('div');
rbpotter5796b002021-03-10 18:49:2168 head.textContent = 'Stats Tables';
69 container.appendChild(head);
Philipp Hancke96341132022-11-04 14:15:4270 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);
rbpotter5796b002021-03-10 18:49:2178 peerConnectionElement.appendChild(container);
79 }
80 return container;
81 }
[email protected]2746df92013-04-26 23:42:3282
rbpotter5796b002021-03-10 18:49:2183 /**
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 Hancke0635d3142021-03-16 20:02:0996 const tableId = peerConnectionElement.id + '-table-' + report.id;
Rebekah Potter8148f852022-11-29 18:04:3797 // 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);
rbpotter5796b002021-03-10 18:49:21101 if (!table) {
Philipp Hancke0635d3142021-03-16 20:02:09102 const container = this.ensureStatsTableContainer_(peerConnectionElement);
103 const details = document.createElement('details');
Philipp Hancke96341132022-11-04 14:15:42104 details.attributes['data-statsType'] = report.type;
rbpotter5796b002021-03-10 18:49:21105 container.appendChild(details);
[email protected]ca51ea02014-02-05 06:41:35106
Philipp Hancke0635d3142021-03-16 20:02:09107 const summary = document.createElement('summary');
Philipp Hanckef91ac402022-10-17 11:05:06108 summary.textContent = generateStatsLabel(report);
rbpotter5796b002021-03-10 18:49:21109 details.appendChild(summary);
[email protected]31e91842013-11-08 19:06:15110
rbpotter5796b002021-03-10 18:49:21111 table = document.createElement('table');
112 details.appendChild(table);
113 table.id = tableId;
114 table.border = 1;
[email protected]2746df92013-04-26 23:42:32115
rbpotter5796b002021-03-10 18:49:21116 table.appendChild($('trth-template').content.cloneNode(true));
117 table.rows[0].cells[0].textContent = 'Statistics ' + report.id;
Philipp Hanckec68bc70c2024-06-10 14:47:05118 table['data-peerconnection-id'] = peerConnectionElement.id;
[email protected]2746df92013-04-26 23:42:32119 }
rbpotter5796b002021-03-10 18:49:21120 return table;
121 }
[email protected]2746df92013-04-26 23:42:32122
rbpotter5796b002021-03-10 18:49:21123 /**
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öm23dec442023-07-25 12:13:35132 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öm65ecac6d2023-09-14 09:10:43141 // `metricElement` IDs have the format `bla-bla-bla-bla-${metricName}`.
142 let metricName =
Henrik Boström23dec442023-07-25 12:13:35143 metricElement.id.substring(metricElement.id.lastIndexOf('-') + 1);
Henrik Boström65ecac6d2023-09-14 09:10:43144 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öm23dec442023-07-25 12:13:35151 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 Hancke0635d3142021-03-16 20:02:09157 const date = new Date(time);
rbpotter5796b002021-03-10 18:49:21158 this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString());
Philipp Hancke0635d3142021-03-16 20:02:09159 for (let i = 0; i < statsData.length - 1; i = i + 2) {
rbpotter5796b002021-03-10 18:49:21160 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 Hancke0635d3142021-03-16 20:02:09174 const trId = statsTable.id + '-' + rowName;
Rebekah Potter8148f852022-11-29 18:04:37175 // 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 Hancke0635d3142021-03-16 20:02:09179 const activeConnectionClass = 'stats-table-active-connection';
rbpotter5796b002021-03-10 18:49:21180 if (!trElement) {
181 trElement = document.createElement('tr');
182 trElement.id = trId;
183 statsTable.firstChild.appendChild(trElement);
Philipp Hanckec68bc70c2024-06-10 14:47:05184 const item = $('statsrow-template').content.cloneNode(true);
rbpotter5796b002021-03-10 18:49:21185 item.querySelector('td').textContent = rowName;
186 trElement.appendChild(item);
187 }
188 trElement.cells[1].textContent = value;
Philipp Hanckec68bc70c2024-06-10 14:47:05189 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;
rbpotter5796b002021-03-10 18:49:21194 }
195 }
Philipp Hancke96341132022-11-04 14:15:42196
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]deb232b2022-11-28 11:22:16204 const filter = event.target.value;
Philipp Hancke96341132022-11-04 14:15:42205 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 }
rbpotter5796b002021-03-10 18:49:21219}