| <!-- |
| Copyright 2013, Google Inc. |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| |
| * Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the following disclaimer |
| in the documentation and/or other materials provided with the |
| distribution. |
| * Neither the name of Google Inc. nor the names of its |
| contributors may be used to endorse or promote products derived from |
| this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| --> |
| |
| <html> |
| <head> |
| <title>XMLHttpRequest benchmark</title> |
| <script src="util.js"></script> |
| <script> |
| // Namespace for holding globals. |
| var benchmark = {}; |
| benchmark.startTimeInMs = 0; |
| |
| var xhrs = []; |
| |
| var addressBox = null; |
| |
| var timerID = null; |
| |
| function destroyAllXHRs() { |
| for (var i = 0; i < xhrs.length; ++i) { |
| xhrs[i].onreadystatechange = function() {}; |
| xhrs[i].abort(); |
| } |
| xhrs = []; |
| // gc() might be needed for Chrome/Blob |
| } |
| |
| function repeatString(str, count) { |
| var data = ''; |
| var expChunk = str; |
| var remain = count; |
| while (true) { |
| if (remain % 2) { |
| data += expChunk; |
| remain = (remain - 1) / 2; |
| } else { |
| remain /= 2; |
| } |
| |
| if (remain == 0) |
| break; |
| |
| expChunk = expChunk + expChunk; |
| } |
| return data; |
| } |
| |
| function sendBenchmarkStep(size, config) { |
| timerID = null; |
| |
| benchmark.startTimeInMs = null; |
| var totalSize = 0; |
| var totalReplied = 0; |
| |
| var onReadyStateChangeHandler = function () { |
| if (this.readyState != this.DONE) { |
| return; |
| } |
| |
| if (this.status != 200) { |
| addToLog('Failed (status=' + this.status + ')'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| if (config.verifyData && !verifyAcknowledgement(this.response, size)) { |
| destroyAllXHRs(); |
| return; |
| } |
| |
| totalReplied += size; |
| |
| if (totalReplied < totalSize) { |
| return; |
| } |
| |
| if (benchmark.startTimeInMs == null) { |
| addToLog('startTimeInMs not set'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| calculateAndLogResult(size, benchmark.startTimeInMs, totalSize, |
| config.printSize); |
| |
| destroyAllXHRs(); |
| |
| runNextTask(); |
| }; |
| |
| for (var i = 0; i < config.numXHRs; ++i) { |
| var xhr = new XMLHttpRequest(); |
| xhr.onreadystatechange = onReadyStateChangeHandler; |
| xhrs.push(xhr); |
| } |
| |
| var dataArray = []; |
| |
| for (var i = 0; i < xhrs.length; ++i) { |
| var data = null; |
| if (config.responseType == 'arraybuffer' || |
| config.responseType == 'blob') { |
| data = new ArrayBuffer(size); |
| |
| fillArrayBuffer(data, 0x61); |
| |
| if (config.responseType == 'blob') { |
| data = new Blob([data]); |
| } |
| } else { |
| data = repeatString('a', size); |
| } |
| |
| dataArray.push(data); |
| } |
| |
| |
| benchmark.startTimeInMs = getTimeStamp(); |
| |
| for (var i = 0; i < xhrs.length; ++i) { |
| var data = dataArray[i]; |
| var xhr = xhrs[i]; |
| xhr.open('POST', addressBox.value + '_send'); |
| xhr.send(data); |
| |
| totalSize += size; |
| } |
| } |
| |
| function receiveBenchmarkStep(size, config) { |
| timerID = null; |
| |
| benchmark.startTimeInMs = null; |
| var totalSize = 0; |
| var totalReplied = 0; |
| |
| var checkResultAndContinue = function (bytesReceived, verificationResult) { |
| if (!verificationResult) { |
| addToLog('Response verification failed'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| totalReplied += bytesReceived; |
| |
| if (totalReplied < totalSize) { |
| return; |
| } |
| |
| if (benchmark.startTimeInMs == null) { |
| addToLog('startTimeInMs not set'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| calculateAndLogResult(size, benchmark.startTimeInMs, totalSize, |
| config.printSize); |
| |
| destroyAllXHRs(); |
| |
| runNextTask(); |
| } |
| |
| var onReadyStateChangeHandler = function () { |
| if (this.readyState != this.DONE) { |
| return; |
| } |
| |
| if (this.status != 200) { |
| addToLog('Failed (status=' + this.status + ')'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| var bytesReceived = -1; |
| if (this.responseType == 'arraybuffer') { |
| bytesReceived = this.response.byteLength; |
| } else if (this.responseType == 'blob') { |
| bytesReceived = this.response.size; |
| } else { |
| bytesReceived = this.response.length; |
| } |
| if (bytesReceived != size) { |
| addToLog('Expected ' + size + 'B but received ' + bytesReceived + 'B'); |
| destroyAllXHRs(); |
| return; |
| } |
| |
| if (this.responseType == 'arraybuffer') { |
| checkResultAndContinue(bytesReceived, |
| !config.verifyData || verifyArrayBuffer(this.response, 0x61)); |
| } else if (this.responseType == 'blob') { |
| if (config.verifyData) |
| verifyBlob(this.response, 0x61, checkResultAndContinue); |
| else |
| checkResultAndContinue(bytesReceived, true); |
| } else { |
| checkResultAndContinue( |
| bytesReceived, |
| !config.verifyData || |
| this.response == repeatString('a', this.response.length)); |
| } |
| }; |
| |
| for (var i = 0; i < config.numXHRs; ++i) { |
| var xhr = new XMLHttpRequest(); |
| xhr.onreadystatechange = onReadyStateChangeHandler; |
| xhrs.push(xhr); |
| } |
| |
| benchmark.startTimeInMs = getTimeStamp(); |
| |
| for (var i = 0; i < xhrs.length; ++i) { |
| var xhr = xhrs[i]; |
| xhr.open('POST', addressBox.value + '_receive'); |
| xhr.responseType = config.responseType; |
| xhr.send(size + ' none'); |
| |
| totalSize += size; |
| } |
| } |
| |
| function getConfig(responseType) { |
| return { |
| printSize: getBoolFromCheckBox('printsize'), |
| responseType: responseType, |
| numXHRs: getIntFromInput('numXHRs'), |
| // Initial size of messages. |
| numIterations: getIntFromInput('numiterations'), |
| startSize: getIntFromInput('startsize'), |
| // Stops benchmark when the size of message exceeds this threshold. |
| stopThreshold: getIntFromInput('stopthreshold'), |
| // If the size of each message is small, send/receive multiple messages |
| // until the sum of sizes reaches this threshold. |
| // minTotal: getIntFromInput('mintotal'), |
| // minTotal is not yet implemented on XHR benchmark |
| multipliers: getIntArrayFromInput('multipliers'), |
| verifyData: getBoolFromCheckBox('verifydata') |
| }; |
| } |
| |
| function startBenchmark(config) { |
| clearTimeout(timerID); |
| destroyAllXHRs(); |
| |
| runNextTask(); |
| } |
| |
| function getStringFromRadioBox(name) { |
| return document.getElementById('benchmark_form')[name].value; |
| } |
| |
| // TODO(hiroshige): the following code is the same as benchmark.html |
| // and some of them should be merged into e.g. util.js |
| |
| var tasks = []; |
| |
| function runNextTask() { |
| var task = tasks.shift(); |
| if (task == undefined) { |
| addToLog('Finished'); |
| destroyAllXHRs(); |
| return; |
| } |
| timerID = setTimeout(task, 0); |
| } |
| |
| function buildLegendString(config) { |
| var legend = '' |
| if (config.printSize) |
| legend = 'Message size in KiB, Time/message in ms, '; |
| legend += 'Speed in kB/s'; |
| return legend; |
| } |
| |
| function getBoolFromCheckBox(id) { |
| return document.getElementById(id).checked; |
| } |
| |
| function getIntArrayFromInput(id) { |
| var strArray = document.getElementById(id).value.split(','); |
| return strArray.map(function(str) { return parseInt(str, 10); }); |
| } |
| |
| function addTasks(config, stepFunc) { |
| for (var i = 0; i < config.numIterations; ++i) { |
| var multiplierIndex = 0; |
| for (var size = config.startSize; |
| size <= config.stopThreshold; |
| ++multiplierIndex) { |
| var runner = stepFunc.bind( |
| null, |
| size, |
| config); |
| tasks.push(runner); |
| size *= config.multipliers[ |
| multiplierIndex % config.multipliers.length]; |
| } |
| } |
| } |
| |
| function addResultReportingTask(config, title) { |
| tasks.push(function(){ |
| addToSummary(title); |
| reportAverageData(); |
| clearAverageData(); |
| runNextTask(); |
| }); |
| } |
| |
| // -------------------------------- |
| |
| function sendBenchmark() { |
| var responseType = getStringFromRadioBox('responsetyperadio'); |
| var config = getConfig(responseType); |
| |
| addToLog('Send benchmark'); |
| addToLog(buildLegendString(config)); |
| |
| tasks = []; |
| clearAverageData(); |
| addTasks(config, sendBenchmarkStep); |
| addResultReportingTask(config, 'Send Benchmark'); |
| startBenchmark(config); |
| } |
| |
| function receiveBenchmark() { |
| var responseType = getStringFromRadioBox('responsetyperadio'); |
| var config = getConfig(responseType); |
| |
| addToLog('Receive benchmark'); |
| addToLog(buildLegendString(config)); |
| |
| tasks = []; |
| clearAverageData(); |
| addTasks(config, receiveBenchmarkStep); |
| addResultReportingTask(config, 'Receive Benchmark'); |
| startBenchmark(config); |
| } |
| |
| function batchBenchmark() { |
| addToLog('Batch benchmark'); |
| |
| tasks = []; |
| clearAverageData(); |
| |
| var responseTypes = ['text', 'blob', 'arraybuffer']; |
| var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep]; |
| var names = ['Send', 'Receive']; |
| for (var i = 0; i < stepFuncs.length; ++i) { |
| for (var j = 0; j < responseTypes.length; ++j) { |
| var responseType = responseTypes[j]; |
| var config = getConfig(responseType); |
| addTasks(config, stepFuncs[i]); |
| addResultReportingTask(config, |
| names[i] + ' benchmark (' + config.responseType + ', numXHRs=' + |
| config.numXHRs + ', numIterations=' + |
| config.numIterations + ')'); |
| } |
| } |
| |
| startBenchmark(config); |
| } |
| |
| |
| function stop() { |
| destroyAllXHRs(); |
| clearTimeout(timerID); |
| timerID = null; |
| addToLog('Stopped'); |
| } |
| |
| function init() { |
| addressBox = document.getElementById('address'); |
| logBox = document.getElementById('log'); |
| |
| summaryBox = document.getElementById('summary'); |
| |
| // Special address of pywebsocket for XHR benchmark. |
| addressBox.value = '/073be001e10950692ccbf3a2ad21c245'; |
| |
| addToLog(window.navigator.userAgent.toLowerCase()); |
| } |
| </script> |
| </head> |
| <body onload="init()"> |
| |
| <form id="benchmark_form"> |
| url prefix <input type="text" id="address" size="40"> |
| <input type="button" value="send" onclick="sendBenchmark()"> |
| <input type="button" value="receive" onclick="receiveBenchmark()"> |
| <input type="button" value="batch" onclick="batchBenchmark()"> |
| <input type="button" value="stop" onclick="stop()"> |
| |
| <br/> |
| |
| <input type="checkbox" id="printsize" checked> |
| <label for="printsize">Print size and time per message</label> |
| <input type="checkbox" id="verifydata" checked> |
| <label for="verifydata">Verify data</label> |
| |
| <br/> |
| |
| Parameters: |
| |
| <br/> |
| |
| <table> |
| <tr> |
| <td>Num XHRs</td> |
| <td><input type="text" id="numXHRs" value="1"></td> |
| </tr> |
| <tr> |
| <td>Number of Iterations</td> |
| <td><input type="text" id="numiterations" value="1"></td> |
| </tr> |
| <tr> |
| <td>Start size</td> |
| <td><input type="text" id="startsize" value="10240"></td> |
| </tr> |
| <tr> |
| <td>Stop threshold</td> |
| <td><input type="text" id="stopthreshold" value="102400000"></td> |
| </tr> |
| <tr> |
| <td>Minimum total</td> |
| <td><input type="text" id="mintotal" value="102400000"></td> |
| </tr> |
| <tr> |
| <td>Multipliers</td> |
| <td><input type="text" id="multipliers" value="5, 2"></td> |
| </tr> |
| </table> |
| |
| Set data type |
| <input type="radio" |
| name="responsetyperadio" |
| id="responsetyperadiotext" |
| value="text" |
| checked><label for="responsetyperadiotext">text</label> |
| <input type="radio" |
| name="responsetyperadio" |
| id="responsetyperadioblob" |
| value="blob" |
| ><label for="responsetyperadioblob">blob</label> |
| <input type="radio" |
| name="responsetyperadio" |
| id="responsetyperadioarraybuffer" |
| value="arraybuffer" |
| ><label for="responsetyperadioarraybuffer">arraybuffer</label> |
| </form> |
| |
| <div id="log_div"> |
| <textarea |
| id="log" rows="20" style="width: 100%" readonly></textarea> |
| </div> |
| <div id="summary_div"> |
| Summary |
| <textarea |
| id="summary" rows="20" style="width: 100%" readonly></textarea> |
| </div> |
| |
| Note: Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated. |
| |
| </body> |
| </html> |