1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Returns true if res is a local rtp
*
* @param {Object} statObject
* One of the objects comprising the report received from getStats()
* @returns {boolean}
* True if object is a local rtp
*/
function isLocalRtp(statObject) {
return (typeof statObject === 'object' &&
statObject.isRemote === false);
}
/**
* Dumps the local, dynamic parts of the stats object as a formatted block
* Used for capturing and monitoring test status during execution
*
* @param {Object} stats
* Stats object to use for output
* @param {string} label
* Used in the header of the output
*/
function outputPcStats(stats, label) {
var outputStr = '\n\n';
function appendOutput(line) {
outputStr += line.toString() + '\n';
}
var firstRtp = true;
for (var prop in stats) {
if (isLocalRtp(stats[prop])) {
var rtp = stats[prop];
if (firstRtp) {
appendOutput(label.toUpperCase() + ' STATS ' +
'(' + new Date(rtp.timestamp).toISOString() + '):');
firstRtp = false;
}
appendOutput(' ' + rtp.id + ':');
if (rtp.type === 'inboundrtp') {
appendOutput(' bytesReceived: ' + rtp.bytesReceived);
appendOutput(' jitter: ' + rtp.jitter);
appendOutput(' packetsLost: ' + rtp.packetsLost);
appendOutput(' packetsReceived: ' + rtp.packetsReceived);
} else {
appendOutput(' bytesSent: ' + rtp.bytesSent);
appendOutput(' packetsSent: ' + rtp.packetsSent);
}
}
}
outputStr += '\n\n';
dump(outputStr);
}
var _lastStats = {};
const MAX_ERROR_CYCLES = 5;
var _errorCount = {};
/**
* Verifies the peer connection stats interval over interval
*
* @param {Object} stats
* Stats object to use for verification
* @param {string} label
* Identifies the peer connection. Differentiates stats for
* interval-over-interval verification in cases where more than one set
* is being verified
*/
function verifyPcStats(stats, label) {
const INCREASING_INBOUND_STAT_NAMES = [
'bytesReceived',
'packetsReceived'
];
const INCREASING_OUTBOUND_STAT_NAMES = [
'bytesSent',
'packetsSent'
];
if (_lastStats[label] !== undefined) {
var errorsInCycle = false;
function verifyIncrease(rtpName, statNames) {
var timestamp = new Date(stats[rtpName].timestamp).toISOString();
statNames.forEach(function (statName) {
var passed = stats[rtpName][statName] >
_lastStats[label][rtpName][statName];
if (!passed) {
errorsInCycle = true;
}
ok(passed,
timestamp + '.' + label + '.' + rtpName + '.' + statName,
label + '.' + rtpName + '.' + statName + ' increased (value=' +
stats[rtpName][statName] + ')');
});
}
for (var prop in stats) {
if (isLocalRtp(stats[prop])) {
if (stats[prop].type === 'inboundrtp') {
verifyIncrease(prop, INCREASING_INBOUND_STAT_NAMES);
} else {
verifyIncrease(prop, INCREASING_OUTBOUND_STAT_NAMES);
}
}
}
if (errorsInCycle) {
_errorCount[label] += 1;
info(label +": increased error counter to " + _errorCount[label]);
} else {
// looks like we recovered from a temp glitch
if (_errorCount[label] > 0) {
info(label + ": reseting error counter to zero");
}
_errorCount[label] = 0;
}
} else {
_errorCount[label] = 0;
}
_lastStats[label] = stats;
}
/**
* Retrieves and performs a series of operations on PeerConnection stats
*
* @param {PeerConnectionWrapper} pc
* PeerConnectionWrapper from which to get stats
* @param {string} label
* Label for the peer connection, passed to each stats callback
* @param {Array} operations
* Array of stats callbacks, each as function (stats, label)
*/
function processPcStats(pc, label, operations) {
pc.getStats(null, function (stats) {
operations.forEach(function (operation) {
operation(stats, label);
});
});
}
/**
* Outputs and verifies the status for local and/or remote PeerConnection as
* appropriate
*
* @param {Object} test
* Test containing the peer connection(s) for verification
*/
function verifyConnectionStatus(test) {
const OPERATIONS = [outputPcStats, verifyPcStats];
if (test.pcLocal) {
processPcStats(test.pcLocal, 'LOCAL', OPERATIONS);
}
if (test.pcRemote) {
processPcStats(test.pcRemote, 'REMOTE', OPERATIONS);
}
}
/**
* Generates a setInterval wrapper command link for use in pc.js command chains
*
* This function returns a promise that will resolve once the link repeatedly
* calls the given callback function every interval ms
* until duration ms have passed, then it will continue the test.
*
* @param {function} callback
* Function to be called on each interval
* @param {number} [interval=1000]
* Frequency in milliseconds with which callback will be called
* @param {number} [duration=3 hours]
* Length of time in milliseconds for which callback will be called
*/
function generateIntervalCommand(callback, interval, duration) {
interval = interval || 1000;
duration = duration || 1000 * 3600 * 3;
return function INTERVAL_COMMAND(test) {
return new Promise (resolve=>{
var startTime = Date.now();
var intervalId = setInterval(function () {
if (callback) {
callback(test);
}
var failed = false;
Object.keys(_errorCount).forEach(function (label) {
if (_errorCount[label] > MAX_ERROR_CYCLES) {
ok(false, "Encountered more then " + MAX_ERROR_CYCLES + " cycles" +
" with errors on " + label);
failed = true;
}
});
var timeElapsed = Date.now() - startTime;
if ((timeElapsed >= duration) || failed) {
clearInterval(intervalId);
resolve();
}
}, interval);
});
};
}
|