webrtc-native
Version:
WebRTC for NodeJS
269 lines (227 loc) • 7.17 kB
JavaScript
;
var wrtc = require('..');
var tape = require('tape');
var args = require('minimist')(process.argv.slice(2));
var SimplePeer = require('simple-peer');
module.exports = bwtest;
bwtest.tape = bwtape;
if (require.main === module) {
main();
}
/**
* called when running this script directly from cli
*/
function main() {
if (typeof(args.iceConfig) === 'string') {
// parse json of iceConfig to allow running like this:
// node test/bwtest --iceConfig '{"ordered": false}'
args.iceConfig = JSON.parse(args.iceConfig);
}
console.log('bwtest args:', args);
bwtest(args);
}
/**
* setup tape tests for bwtest
*/
function bwtape() {
tape('bwtest default', function(t) {
t.plan(1);
bwtest({
packetCount: 500,
}, function(err) {
t.error(err, 'bwtest check for error');
});
});
tape('bwtest no buffering', function(t) {
t.plan(1);
bwtest({
packetCount: 500,
congestHighThreshold: 0,
congestLowThreshold: 0
}, function(err) {
t.error(err, 'bwtest check for error');
});
});
tape('bwtest unordered and unreliable', function(t) {
t.plan(1);
bwtest({
packetCount: 500,
iceConfig: {
ordered: false,
maxRetransmits: 5
}
}, function(err) {
t.error(err, 'bwtest check for error');
});
});
}
/**
*
* BWTEST
*
* run a webrtc bandwidth test with two peers running in this process.
*
* @param options (optional) - see defaults inside for list of options.
* @param callback function(err) called on success/failure.
*
*/
function bwtest(options, callback) {
// options is optional
if (typeof(options) === 'function') {
callback = options;
options = null;
}
// defaults
callback = callback || function() {};
options = options || {};
options.packetCount = options.packetCount || 5000;
options.packetSize = options.packetSize || 16 * 1024;
options.bufferedDelayMs = options.bufferedDelayMs || 5;
options.congestHighThreshold = options.congestHighThreshold || 1024 * 1024;
options.congestLowThreshold = options.congestLowThreshold || 256 * 1024;
options.iceConfig = options.iceConfig || defaultIceConfig();
var n = 0;
var congested = 0;
var stats = {
startTime: 0,
count: 0,
bytes: 0,
congestCount: 0,
congestTime: 0
};
// setup two peers with simple-peer
var peer1 = new SimplePeer({
wrtc: wrtc
});
var peer2 = new SimplePeer({
wrtc: wrtc,
initiator: true,
config: options.iceConfig
});
// when peer1 has signaling data, give it to peer2, and vice versa
peer1.on('signal', peer2.signal.bind(peer2));
peer2.on('signal', peer1.signal.bind(peer1));
// wait for 'connect' event before using the data channel
peer1.on('error', failure);
peer2.on('error', failure);
peer1.on('connect', start);
peer2.on('data', receive);
// functions //
/**
* start the test once peers are connected
*/
function start() {
stats.startTime = Date.now();
send();
}
/**
* send next packet and recurse
*/
function send() {
if (n >= options.packetCount) {
console.log('SEND DONE!', info());
return;
}
if (n % 100 === 0) {
console.log('SENDING:', info());
}
if (congestion()) {
setTimeout(send, options.bufferedDelayMs);
return;
}
// TODO allocating new buffer per send as workaround to repeated Externalize()
// after fixing the issues around that we can move back to higher scope.
var buffer = new ArrayBuffer(options.packetSize);
peer1.send(buffer);
n += 1;
if (global.setImmediate) {
global.setImmediate(send);
} else {
setTimeout(send, 0);
}
}
/**
* callback for the receiver to update stats and finish the test
*/
function receive(data) {
stats.count += 1;
stats.bytes += data.length;
if (stats.count >= options.packetCount) {
console.log('RECEIVE DONE!', info());
// closing the channels so the process can exit
peer1.destroy();
peer2.destroy();
callback();
}
}
/**
* failure handler
*/
function failure(err) {
// make sure to call the callback with error, even if anything here will throw
setTimeout(callback.bind(null, err), 0);
console.error('ERROR!', info(), err.stack || err);
// closing the channels so the process can exit
peer1.destroy();
peer2.destroy();
}
/**
* handle channel congestion using bufferedAmount
*/
function congestion() {
var bufferedAmount = peer1._channel && peer1._channel.bufferedAmount || 0;
if ((bufferedAmount > options.congestHighThreshold) ||
(congested && bufferedAmount > options.congestLowThreshold)) {
if (!congested) {
congested = Date.now();
}
stats.congestCount += 1;
} else {
if (congested) {
stats.congestTime += Date.now() - congested;
}
congested = 0;
}
return congested;
}
/**
* return information string on the test progress
*/
function info() {
var now = Date.now();
if (congested) {
stats.congestTime += Date.now() - congested;
congested = now;
}
var took = (now - stats.startTime) / 1000;
var bufferedAmount = peer1._channel && peer1._channel.bufferedAmount;
return 'sent ' + n + ' received ' + stats.count + '. ' +
'congestion #' + stats.congestCount + ' ' +
(stats.congestTime / 1024).toFixed(3) + ' seconds. ' +
(bufferedAmount ? 'bufferedAmount ' + bufferedAmount + '. ' : '') +
'took ' + took.toFixed(3) + ' seconds. ' +
'bandwidth ' + (stats.bytes / took / 1024).toFixed(0) + ' KB/s. ';
}
/**
* default ice config is just here for documenting the options - see inside.
*/
function defaultIceConfig() {
return {
/*
* data channels are ordered by default - using unordered channel improves
* performance but will likely require ordering in another app layer
*/
// ordered: false,
/*
* data channels are reliable by default - using either maxRetransmits
* or maxPacketLifeTime will change to unreliable mode
*/
// maxRetransmits: 5,
// maxPacketLifeTime: 3000,
/*
* iceServers with stun urls. not needed for this in-process test.
*/
// iceServers: [{url: 'stun:23.21.150.121'}],
};
}
}