if (typeof simulate === 'undefined') { var simulate = false; } $(function(){ jQuery.fx.interval = 50; if (simulate) { setTimeout(initializeTest, 1000); return; } checkInstalledPlugins(); initializeTest(); }); // CONSTANTS // Testing phases var PHASE_LOADING = 0; var PHASE_WELCOME = 1; var PHASE_PREPARING = 2; var PHASE_UPLOAD = 3; var PHASE_DOWNLOAD = 4; var PHASE_RESULTS = 5; // STATUS VARIABLES var use_websocket_client = false; var websocket_client = null; var currentPhase = PHASE_LOADING; var currentPage = 'welcome'; var transitionSpeed = 400; // A front-end implementation could define some specific server. If not, then // just use the current server's hostname. if (typeof window.ndtServer === 'undefined') { var ndtServer = location.hostname; } // Gauges used for showing download/upload speed var downloadGauge, uploadGauge; var gaugeUpdateInterval; var gaugeMaxValue = 1000; // PRIMARY METHODS function initializeTest() { // Initialize gauges initializeGauges(); // Initialize start buttons $('.start.button').click(startTest); // Results view selector $('#results .view-selector .summary').click(showResultsSummary); $('#results .view-selector .details').click(showResultsDetails); $('#results .view-selector .advanced').click(showResultsAdvanced); $('body').removeClass('initializing'); $('body').addClass('ready'); //return showPage('results'); setPhase(PHASE_WELCOME); } function startTest(evt) { evt.stopPropagation(); evt.preventDefault(); createBackend(); if (!isPluginLoaded()) { $('#warning-plugin').show(); return; } $('#warning-plugin').hide(); $('#javaButton').attr('disabled', true); $('#websocketButton').attr('disabled', true); showPage('test', resetGauges); $('#rttValue').html(''); if (simulate) return simulateTest(); currentPhase = PHASE_WELCOME; testNDT().run_test(ndtServer); monitorTest(); } function simulateTest() { setPhase(PHASE_RESULTS); return; setPhase(PHASE_PREPARING); setTimeout(function(){ setPhase(PHASE_UPLOAD) }, 2000); setTimeout(function(){ setPhase(PHASE_DOWNLOAD) }, 4000); setTimeout(function(){ setPhase(PHASE_RESULTS) }, 6000); } function monitorTest() { var message = testError(); var currentStatus = testStatus(); /* var currentStatus = testStatus(); debug(currentStatus); var diagnosis = testDiagnosis(); debug(diagnosis); */ if (message.match(/not run/) && currentPhase != PHASE_LOADING) { setPhase(PHASE_WELCOME); return false; } if (message.match(/completed/) && currentPhase < PHASE_RESULTS) { setPhase(PHASE_RESULTS); return true; } if (message.match(/failed/) && currentPhase < PHASE_RESULTS) { setPhase(PHASE_RESULTS); return false; } if (currentStatus.match(/Outbound/) && currentPhase < PHASE_UPLOAD) { setPhase(PHASE_UPLOAD); } if (currentStatus.match(/Inbound/) && currentPhase < PHASE_DOWNLOAD) { setPhase(PHASE_DOWNLOAD); } if (!currentStatus.match(/Middleboxes/) && !currentStatus.match(/notStarted/) && !remoteServer().match(/ndt/) && currentPhase == PHASE_PREPARING) { debug('Remote server is ' + remoteServer()); setPhase(PHASE_UPLOAD); } if (remoteServer() !== 'unknown' && currentPhase < PHASE_PREPARING) { setPhase(PHASE_PREPARING); } setTimeout(monitorTest, 1000); } // PHASES function setPhase(phase) { switch (phase) { case PHASE_WELCOME: debug('WELCOME'); showPage('welcome'); break; case PHASE_PREPARING: uploadGauge.setValue(0); downloadGauge.setValue(0); debug('PREPARING TEST'); $('#loading').show(); $('#upload').hide(); $('#download').hide(); showPage('test', resetGauges); break; case PHASE_UPLOAD: var pcBuffSpdLimit, rtt, gaugeConfig = []; debug('UPLOAD TEST'); pcBuffSpdLimit = speedLimit(); rtt = averageRoundTrip(); if (isNaN(rtt)) { $('#rttValue').html('n/a'); } else { $('rttValue').html(printNumberValue(Math.round(rtt)) + ' ms'); } if (!isNaN(pcBuffSpdLimit)) { if (pcBuffSpdLimit > gaugeMaxValue) { pcBuffSpdLimit = gaugeMaxValue; } gaugeConfig.push({ from: 0, to: pcBuffSpdLimit, color: 'rgb(0, 255, 0)' }); gaugeConfig.push({ from: pcBuffSpdLimit, to: gaugeMaxValue, color: 'rgb(255, 0, 0)' }); uploadGauge.updateConfig({ highlights: gaugeConfig }); downloadGauge.updateConfig({ highlights: gaugeConfig }); } $('#loading').hide(); $('#upload').show(); gaugeUpdateInterval = setInterval(function(){ updateGaugeValue(); },1000); $('#test .remote.location .address').get(0).innerHTML = remoteServer(); break; case PHASE_DOWNLOAD: debug('DOWNLOAD TEST'); $('#upload').hide(); $('#download').show(); break; case PHASE_RESULTS: debug('SHOW RESULTS'); debug('Testing complete'); printDownloadSpeed(); printUploadSpeed(); $('#latency').html(printNumberValue(Math.round(averageRoundTrip()))); $('#jitter').html(printJitter(false)); $('#test-details').html(testDetails()); $('#test-advanced').append(testDiagnosis()); $('#javaButton').attr('disabled', false); showPage('results'); break; default: return false; } currentPhase = phase; } // PAGES function showPage(page, callback) { debug('Show page: ' + page); if (page == currentPage) { if (callback !== undefined) callback(); return true; } if (currentPage !== undefined) { $('#' + currentPage).fadeOut(transitionSpeed, function(){ $('#' + page).fadeIn(transitionSpeed, callback); }); } else { debug('No current page'); $('#' + page).fadeIn(transitionSpeed, callback); } currentPage = page; } // RESULTS function showResultsSummary() { showResultsPage('summary'); } function showResultsDetails() { showResultsPage('details'); } function showResultsAdvanced() { showResultsPage('advanced'); } function showResultsPage(page) { debug('Results: show ' + page); var pages = ['summary', 'details', 'advanced']; for (var i=0, len=pages.length; i < len; i++) { $('#results')[(page == pages[i]) ? 'addClass' : 'removeClass'](pages[i]); } } // GAUGE function initializeGauges() { var gaugeValues = []; for (var i=0; i<=10; i++) { gaugeValues.push(0.1 * gaugeMaxValue * i); } uploadGauge = new Gauge({ renderTo : 'uploadGauge', width : 270, height : 270, units : 'Mb/s', title : 'Upload', minValue : 0, maxValue : gaugeMaxValue, majorTicks : gaugeValues, highlights : [{ from: 0, to: gaugeMaxValue, color: 'rgb(0, 255, 0)' }] });; gaugeValues = []; for (var i=0; i<=10; i++) { gaugeValues.push(0.1 * gaugeMaxValue * i); } downloadGauge = new Gauge({ renderTo : 'downloadGauge', width : 270, height : 270, units : 'Mb/s', title : 'Download', minValue : 0, maxValue : gaugeMaxValue, majorTicks : gaugeValues, highlights : [{ from: 0, to: gaugeMaxValue, color: 'rgb(0, 255, 0)' }] });; } function resetGauges() { var gaugeConfig = []; gaugeConfig.push({ from: 0, to: gaugeMaxValue, color: 'rgb(0, 255, 0)' }); uploadGauge.updateConfig({ highlights: gaugeConfig }); uploadGauge.setValue(0); downloadGauge.updateConfig({ highlights: gaugeConfig }); downloadGauge.setValue(0); } function updateGaugeValue() { var downloadSpeedVal = downloadSpeed(); var uploadSpeedVal = uploadSpeed(false); if (currentPhase == PHASE_UPLOAD) { uploadGauge.updateConfig({ units: getSpeedUnit(uploadSpeedVal) }); uploadGauge.setValue(getJustfiedSpeed(uploadSpeedVal)); } else if (currentPhase == PHASE_DOWNLOAD) { downloadGauge.updateConfig({ units: getSpeedUnit(downloadSpeedVal) }); downloadGauge.setValue(getJustfiedSpeed(downloadSpeedVal)); } else { clearInterval(gaugeUpdateInterval); } } // TESTING JAVA/WEBSOCKET CLIENT function testNDT() { if (websocket_client) { return websocket_client; } return $('#NDT'); } function testStatus() { return testNDT().get_status(); } function testDiagnosis() { var div = document.createElement('div'); if (simulate) { div.innerHTML = 'Test diagnosis'; return div; } var diagnosisArray = testNDT().get_diagnosis().split('\n'); var txt = ''; var table; var isTable = false; diagnosisArray.forEach( function addRow(value) { if (isTable) { rowArray = value.split(':'); if (rowArray.length>1) { var row = table.insertRow(-1); rowArray.forEach( function addCell(cellValue, idx) { var cell =row.insertCell(idx); cell.innerHTML = cellValue; } ); } else { isTable = false; txt = txt + value; } } else { if (value.indexOf('=== Results sent by the server ===') != -1) { table = document.createElement('table'); isTable = true; } else { txt = txt + value + '\n'; } } } ); txt = txt + '=== Results sent by the server ==='; div.innerHTML = txt; if (isTable) { div.appendChild(table); } return div; } function testError() { return testNDT().get_errmsg(); } function remoteServer() { if (simulate) return '0.0.0.0'; return testNDT().get_host(); } function uploadSpeed(raw) { if (simulate) return 0; var speed = testNDT().getNDTvar('ClientToServerSpeed'); return raw ? speed : parseFloat(speed); } function downloadSpeed() { if (simulate) return 0; return parseFloat(testNDT().getNDTvar('ServerToClientSpeed')); } function averageRoundTrip() { if (simulate) return 0; return parseFloat(testNDT().getNDTvar('avgrtt')); } function jitter() { if (simulate) return 0; return parseFloat(testNDT().getNDTvar('Jitter')); } function speedLimit() { if (simulate) return 0; return parseFloat(testNDT().get_PcBuffSpdLimit()); } function printPacketLoss() { var packetLoss = parseFloat(testNDT().getNDTvar('loss')); packetLoss = (packetLoss*100).toFixed(2); return packetLoss; } function printJitter(boldValue) { var retStr = ''; var jitterValue = jitter(); if (jitterValue >= 1000) { retStr += (boldValue ? '<b>' : '') + printNumberValue(jitterValue/1000) + (boldValue ? '</b>' : '') + ' sec'; } else { retStr += (boldValue ? '<b>' : '') + printNumberValue(jitterValue) + (boldValue ? '</b>' : '') + ' msec'; } return retStr; } function getSpeedUnit(speedInKB) { var unit = ['kb/s', 'Mb/s', 'Gb/s', 'Tb/s', 'Pb/s', 'Eb/s']; var e = Math.floor(Math.log(speedInKB*1000) / Math.log(1000)); return unit[e]; } function getJustfiedSpeed(speedInKB) { var e = Math.floor(Math.log(speedInKB) / Math.log(1000)); return (speedInKB / Math.pow(1000, e)).toFixed(2); } function printDownloadSpeed() { var downloadSpeedVal = downloadSpeed(); $('#download-speed').html(getJustfiedSpeed(downloadSpeedVal)); $('#download-speed-units').html(getSpeedUnit(downloadSpeedVal)); } function printUploadSpeed() { var uploadSpeedVal = uploadSpeed(false); $('#upload-speed').html(getJustfiedSpeed(uploadSpeedVal)); $('#upload-speed-units').html(getSpeedUnit(uploadSpeedVal)); } function readNDTvar(variable) { var ret = testNDT().getNDTvar(variable); return !ret ? '-' : ret; } function printNumberValue(value) { return isNaN(value) ? '-' : value; } function testDetails() { if (simulate) return 'Test details'; var d = ''; var errorMsg = testError(); if (errorMsg.match(/failed/)) { d += 'Error occured while performing test: <br>'.bold(); if (errorMsg.match(/#2048/)) { d += 'Security error. This error may be caused by firewall issues, make sure that port 843 is available on the NDT server, and that you can access it.'.bold().fontcolor('red') + '<br><br>'; } else { d += errorMsg.bold().fontcolor('red') + '<br><br>'; } } d += 'Your system: ' + readNDTvar('OperatingSystem').bold() + '<br>'; d += 'Plugin version: ' + (readNDTvar('PluginVersion') + ' (' + readNDTvar('OsArchitecture') + ')<br>').bold(); d += '<br>'; d += 'TCP receive window: ' + readNDTvar('CurRwinRcvd').bold() + ' current, ' + readNDTvar('MaxRwinRcvd').bold() + ' maximum<br>'; d += '<b>' + printNumberValue(printPacketLoss()) + '</b> % of packets lost during test<br>'; d += 'Round trip time: ' + readNDTvar('MinRTT').bold() + ' msec (minimum), ' + readNDTvar('MaxRTT').bold() + ' msec (maximum), <b>' + printNumberValue(Math.round(averageRoundTrip())) + '</b> msec (average)<br>'; d += 'Jitter: ' + printNumberValue(printJitter(true)) + '<br>'; d += readNDTvar('waitsec').bold() + ' seconds spend waiting following a timeout<br>'; d += 'TCP time-out counter: ' + readNDTvar('CurRTO').bold() + '<br>'; d += readNDTvar('SACKsRcvd').bold() + ' selective acknowledgement packets received<br>'; d += '<br>'; if (readNDTvar('mismatch') == 'yes') { d += 'A duplex mismatch condition was detected.<br>'.fontcolor('red').bold(); } else { d += 'No duplex mismatch condition was detected.<br>'.fontcolor('green'); } if (readNDTvar('bad_cable') == 'yes') { d += 'The test detected a cable fault.<br>'.fontcolor('red').bold(); } else { d += 'The test did not detect a cable fault.<br>'.fontcolor('green'); } if (readNDTvar('congestion') == 'yes') { d += 'Network congestion may be limiting the connection.<br>'.fontcolor('red').bold(); } else { d += 'No network congestion was detected.<br>'.fontcolor('green'); } d += '<br>'; d += printNumberValue(readNDTvar('cwndtime')).bold() + ' % of the time was not spent in a receiver limited or sender limited state.<br>'; d += printNumberValue(readNDTvar('rwintime')).bold() + ' % of the time the connection is limited by the client machine\'s receive buffer.<br>'; d += 'Optimal receive buffer: ' + printNumberValue(readNDTvar('optimalRcvrBuffer')).bold() + ' bytes<br>'; d += 'Bottleneck link: ' + readNDTvar('accessTech').bold() + '<br>'; d += readNDTvar('DupAcksIn').bold() + ' duplicate ACKs set<br>'; return d; } // BACKEND METHODS function useJavaAsBackend() { $('#warning-websocket').hide(); $('#rtt').show(); $('#rttValue').show(); $('.warning-environment').innerHTML = ''; use_websocket_client = false; $('#websocketButton').removeClass('active'); $('#javaButton').addClass('active'); } function useWebsocketAsBackend() { $('#rtt').hide(); $('#rttValue').hide(); $('#warning-websocket').show(); use_websocket_client = true; $('#javaButton').removeClass('active'); $('#websocketButton').addClass('active'); } function createBackend() { $('#backendContainer').empty(); if (use_websocket_client) { websocket_client = new NDTWrapper(window.ndtServer); } else { var app = document.createElement('applet'); app.id = 'NDT'; app.name = 'NDT'; app.archive = 'Tcpbw100.jar'; app.code = 'edu.internet2.ndt.Tcpbw100.class'; app.width = '600'; app.height = '10'; $('#backendContainer').append(app); } } // UTILITIES function debug(message) { if (typeof allowDebug !== 'undefined') { if (allowDebug && window.console) console.debug(message); } } function isPluginLoaded() { try { testStatus(); return true; } catch(e) { return false; } } function checkInstalledPlugins() { var hasJava = false; var hasWebsockets = false; $('#warning-plugin').hide(); $('#warning-websocket').hide(); hasJava = true; if (typeof deployJava !== 'undefined') { if (deployJava.getJREs() == '') { hasJava = false; } } hasWebsockets = false; try { var ndt_js = new NDTjs(); if (ndt_js.checkBrowserSupport()) { hasWebsockets = true; } } catch(e) { hasWebsockets = false; } if (!hasWebsockets) { $('#websocketButton').attr('disabled', true); } if (!hasJava) { $('#javaButton').attr('disabled', true); } if (hasWebsockets) { useWebsocketAsBackend(); } else if (hasJava) { useJavaAsBackend(); } } // Attempts to determine the absolute path of a script, minus the name of the // script itself. function getScriptPath() { var scripts = document.getElementsByTagName('SCRIPT'); var fileRegex = new RegExp('\/ndt-wrapper\.js$'); var path = ''; if (scripts && scripts.length > 0) { for(var i in scripts) { if(scripts[i].src && scripts[i].src.match(fileRegex)) { path = scripts[i].src.replace(fileRegex, ''); break; } } } return path.substring(location.origin.length); };