Skip to content

Commit 876ee32

Browse files
committed
introduce the concept of a 'boring' time series and make candidate grid foldable
1 parent 99f3825 commit 876ee32

File tree

3 files changed

+95
-48
lines changed

3 files changed

+95
-48
lines changed

import-common.js

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
const SDPUtils = window.adapter.sdp;
22

3+
import {
4+
isBoring,
5+
} from './timeseries.js';
6+
37
function filterStatsGraphs(event, container) {
48
const filter = event.target.value;
59
const filters = filter.split(',');
@@ -21,9 +25,7 @@ export function processDescriptionEvent(container, eventType, description, last_
2125
const {type, sdp} = description;
2226
const sections = SDPUtils.splitSections(sdp);
2327
container.innerText += ' (type: "' + type + '", ' + sections.length + ' sections)';
24-
if (last_sections) {
25-
container.innerText += ' munged';
26-
}
28+
2729
const copyBtn = document.createElement('button');
2830
copyBtn.innerText = '\uD83D\uDCCB'; // clipboard
2931
copyBtn.className = 'copyBtn';
@@ -32,6 +34,7 @@ export function processDescriptionEvent(container, eventType, description, last_
3234
};
3335
container.appendChild(copyBtn);
3436

37+
let munged = false;
3538
const el = document.createElement('pre');
3639
sections.forEach((section, index) => {
3740
const lines = SDPUtils.splitLines(section);
@@ -59,25 +62,30 @@ export function processDescriptionEvent(container, eventType, description, last_
5962
}
6063
details.open = false;
6164
}
62-
if (last_sections && last_sections[index] !== sections[index]) {
63-
// Ignore triggering from simple reordering which is ok-ish.
64-
const last_lines = SDPUtils.splitLines(last_sections[index]).sort();
65-
const current_lines = SDPUtils.splitLines(sections[index]).sort();
66-
const mungedIndex = last_lines.findIndex((line, index) => line !== current_lines[index]);
67-
if (mungedIndex !== -1) {
68-
summary.innerText += ' munged';
69-
summary.style.backgroundColor = '#FBCEB1';
70-
summary.title = 'First munged line: ' + current_lines[mungedIndex];
71-
details.style.backgroundColor = '#FBCEB1';
72-
details.open = true;
73-
} else {
74-
summary.innerText += ' reordered';
75-
}
65+
}
66+
if (last_sections && last_sections[index] !== sections[index]) {
67+
// Ignore triggering from simple reordering which is ok-ish.
68+
const last_lines = SDPUtils.splitLines(last_sections[index]).sort();
69+
const current_lines = SDPUtils.splitLines(sections[index]).sort();
70+
const mungedIndex = last_lines.findIndex((line, index) => line !== current_lines[index]);
71+
if (mungedIndex !== -1) {
72+
summary.innerText += ' munged';
73+
summary.style.backgroundColor = '#FBCEB1';
74+
summary.title = 'First munged line: ' + current_lines[mungedIndex];
75+
details.style.backgroundColor = '#FBCEB1';
76+
details.open = true;
77+
} else {
78+
summary.innerText += ' reordered';
79+
summary.style.backgroundColor = '#FBCEB1';
7680
}
81+
munged = true;
7782
}
7883
details.appendChild(summary);
7984
el.appendChild(details);
8085
});
86+
if (munged) {
87+
container.innerText += ' munged';
88+
}
8189
container.appendChild(el);
8290
}
8391

@@ -109,19 +117,26 @@ export function createContainers(connid, url, containers) {
109117
connectionState.textContent = 'Connection state:';
110118
container.appendChild(connectionState);
111119

120+
const candidateContainer = document.createElement('details');
121+
container.style.margin = '10px';
122+
const candidateSummary = document.createElement('summary');
123+
candidateSummary.innerText = 'ICE candidate grid';
124+
candidateContainer.appendChild(candidateSummary);
112125
const candidates = document.createElement('table');
113126
candidates.className = 'candidatepairtable';
114-
container.appendChild(candidates);
127+
candidateContainer.appendChild(candidates);
128+
container.appendChild(candidateContainer);
115129

116130
const updateLog = document.createElement('table');
117131
const head = document.createElement('tr');
118132
updateLog.appendChild(head);
119133

120134
el = document.createElement('th');
121-
el.innerText = 'connection ' + connid;
135+
el.innerText = 'Time';
122136
head.appendChild(el);
123137

124138
el = document.createElement('th');
139+
el.innerText = 'Event';
125140
head.appendChild(el);
126141

127142
container.appendChild(updateLog);
@@ -138,7 +153,7 @@ export function createContainers(connid, url, containers) {
138153
input.oninput = (e) => filterStatsGraphs(e, graphs);
139154
graphHeader.appendChild(input);
140155

141-
container.appendChild(graphHeader);
156+
graphs.appendChild(graphHeader);
142157
container.appendChild(graphs);
143158

144159
containers[connid] = {
@@ -312,7 +327,7 @@ export function processGetUserMedia(data, parentElement) {
312327

313328
parentElement.appendChild(container);
314329
data.forEach(gumEvent => {
315-
const id = ['gum-row', gumEvent.pid, gumEvent.rid, gumEvent.request_id].join('-');
330+
const id = [container.id, 'gum-row', gumEvent.pid, gumEvent.rid, gumEvent.request_id].join('-');
316331
if (!gumEvent.origin) {
317332
// Not a getUserMedia call but a response, update the row with the request.
318333
const existingRow = document.getElementById(id);
@@ -357,9 +372,9 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
357372
};
358373
reports.sort().forEach(report => {
359374
const [name, data, statsType] = report;
375+
// set up a x-axis plotbands:
376+
// https://www.highcharts.com/docs/chart-concepts/plot-bands-and-plot-lines
360377
if (name === 'active' && statsType === 'outbound-rtp') {
361-
// set up a x-axis plotbands:
362-
// https://www.highcharts.com/docs/chart-concepts/plot-bands-and-plot-lines
363378
data.filter((el, index, values) => {
364379
return !(index > 0 && index < values.length - 1 && values[index - 1][1] == el[1]);
365380
}).forEach((item, index, values) => {
@@ -378,8 +393,6 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
378393
return;
379394
}
380395
if (name === 'qualityLimitationReason' && statsType === 'outbound-rtp') {
381-
// set up a x-axis plotbands:
382-
// https://www.highcharts.com/docs/chart-concepts/plot-bands-and-plot-lines
383396
data.filter((el, index, values) => {
384397
return !(index > 0 && index < values.length - 1 && values[index - 1][1] == el[1]);
385398
}).forEach((item, index, values) => {
@@ -398,8 +411,6 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
398411
return;
399412
}
400413
if (['encoderImplementation', 'decoderImplementation'].includes(name) && ['inbound-rtp', 'outbound-rtp'].includes(statsType)) {
401-
// set up a x-axis plotbands:
402-
// https://www.highcharts.com/docs/chart-concepts/plot-bands-and-plot-lines
403414
data.filter((el, index, values) => {
404415
return !(index > 0 && index < values.length - 1 && values[index - 1][1] == el[1]);
405416
}).forEach((item, index, values) => {
@@ -416,8 +427,6 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
416427
return;
417428
}
418429
if (name === 'scalabilityMode' && statsType === 'outbound-rtp') {
419-
// set up a x-axis plotbands:
420-
// https://www.highcharts.com/docs/chart-concepts/plot-bands-and-plot-lines
421430
data.filter((el, index, values) => {
422431
return !(index > 0 && index < values.length - 1 && values[index - 1][1] == el[1]);
423432
}).forEach((item, index, values) => {
@@ -440,7 +449,7 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
440449
}
441450

442451
const statsForLabels = [
443-
'kind', 'mid', 'rid',
452+
'kind', 'mid', 'rid', 'encodingIndex',
444453
'ssrc', 'rtxSsrc', 'fecSsrc',
445454
'encoderImplementation', 'decoderImplementation', 'scalabilityMode',
446455
'scalabilityMode', '[codec]',
@@ -458,24 +467,12 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
458467
'streamIdentifier', 'trackIdentifier',
459468
'priority', 'port',
460469
'ssrc', 'rtxSsrc', 'fecSsrc',
461-
'mid', 'rid',
470+
'mid', 'rid', 'encodingIndex',
462471
];
463472
if (ignoredSeries.includes(name)) {
464473
return;
465474
}
466475

467-
const hiddenSeries = [
468-
'bytesReceived', 'bytesSent',
469-
'headerBytesReceived', 'headerBytesSent',
470-
'packetsReceived', 'packetsSent',
471-
'qpSum',
472-
'framesEncoded', 'framesDecoded', 'totalEncodeTime',
473-
'lastPacketReceivedTimestamp', 'lastPacketSentTimestamp',
474-
'remoteTimestamp', 'estimatedPlayoutTimestamp',
475-
'audioInputLevel', 'audioOutputLevel',
476-
'totalSamplesDuration', 'totalSamplesReceived',
477-
'jitterBufferEmittedCount',
478-
];
479476
const secondYAxis = [
480477
// candidate-pair
481478
'consentRequestsSent', 'requestsSent', 'requestsReceived', 'responsesSent', 'responsesReceived',
@@ -487,13 +484,16 @@ export function createGraphOptions(statsId, statsType, reports, referenceTime) {
487484
'[framesSent/s]', '[framesEncoded/s]', '[keyFramesEncoded/s]', 'frameWidth', 'frameHeight',
488485
];
489486

487+
// Hide "boring" series. Graphs with all boring series should be deprioritized too.
488+
const hidden = isBoring(name, data);
490489
series.push({
491490
name,
492491
data,
493-
visible: !hiddenSeries.includes(name),
492+
visible: !hidden,
494493
yAxis: secondYAxis.includes(name) ? 1 : 0,
495494
});
496495
});
496+
labels['visibleSeries'] = series.filter(s => s.visible).length + '/' + series.length;
497497

498498
// Optionally start all graphs at the same point in time.
499499
if (referenceTime) {

import.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,14 @@ export class WebRTCInternalsDumpImporter {
9292
state.lastRemoteDescription = undefined;
9393
}
9494
});
95+
// update state displays
9596
connection.updateLog.forEach(traceEvent => {
96-
// update state displays
9797
if (traceEvent.type === 'iceconnectionstatechange') {
9898
this.containers[connectionId].iceConnectionState.textContent += ' => ' + traceEvent.value;
9999
}
100100
if (traceEvent.type === 'connectionstatechange') {
101101
this.containers[connectionId].connectionState.textContent += ' => ' + traceEvent.value;
102102
}
103-
});
104-
connection.updateLog.forEach(traceEvent => {
105103
// FIXME: would be cool if a click on this would jump to the table row
106104
if (traceEvent.type === 'signalingstatechange') {
107105
this.containers[connectionId].signalingState.textContent += ' => ' + traceEvent.value;
@@ -150,7 +148,7 @@ export class WebRTCInternalsDumpImporter {
150148
const title = [
151149
'type', 'kind',
152150
'ssrc', 'rtxSsrc', 'fecSsrc',
153-
'mid', 'rid',
151+
'mid', 'rid', 'encodingIndex',
154152
'label',
155153
'[codec]',
156154
'encoderImplementation', 'decoderImplementation',

timeseries.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Determines whether a timeseries is "boring". This is defined as follows:
3+
* - a list of known boring series
4+
* - the name starts with 'total'.
5+
* - min and max are the same (which filters out static values)
6+
* - TODO: linear regression?
7+
* - TODO: less than 10 seconds?
8+
* - TODO: constant or linear after initial ramp-up?
9+
*
10+
* @param {string} name - the stats property name of the series.
11+
* @param {Array} series - timeseries as an array of [time, value].
12+
*/
13+
export function isBoring(name, series) {
14+
// Series which are interesting even if their value does not change.
15+
const interestingSeries = [
16+
'width', 'height',
17+
'frameWidth', 'frameHeight',
18+
'framesPerSecond',
19+
'targetBitrate',
20+
'nackCount', 'pliCount', 'firCount',
21+
];
22+
if (interestingSeries.includes(name)) return false;
23+
24+
if (name.startsWith('total')) return true;
25+
// Some of these should have been named total...
26+
const hiddenSeries = [
27+
'bytesReceived', 'bytesSent', 'retransmittedBytesSent', 'retransmittedBytesReceived',
28+
'headerBytesReceived', 'headerBytesSent',
29+
'packetsReceived', 'packetsSent', 'retransmittedBytesSent', 'retransmittedBytesReceived',
30+
'qpSum',
31+
'framesEncoded', 'framesDecoded', 'framesReceived', 'framesAssembled', 'framesAssembledFromMultiplePackets',
32+
'lastPacketReceivedTimestamp', 'lastPacketSentTimestamp',
33+
'remoteTimestamp', 'estimatedPlayoutTimestamp',
34+
'audioLevel',
35+
'jitterBufferEmittedCount', 'jitterBufferDelay', 'jitterBufferTargetDelay', 'jitterBufferMinimumDelay',
36+
'selectedCandidatePairChanges',
37+
'requestsSent', 'responsesReceived', 'requestsReceived', 'responsesSent', 'consentRequestsSent', // ICE
38+
'reportsSent', 'roundTripTimeMeasurements', // RTCP
39+
];
40+
if (hiddenSeries.includes(name)) return true;
41+
42+
const values = series.slice(1).map(item => item[1]);
43+
if (values[0] === values[values.length] - 1) {
44+
const min = Math.min.apply(null, values);
45+
const max = Math.max.apply(null, values);
46+
if (min === max) return true;
47+
}
48+
return false;
49+
}

0 commit comments

Comments
 (0)