// Copyright 2014 Google Inc. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the COPYING file or at // https://developers.google.com/open-source/licenses/bsd var isWorker = typeof importScripts !== "undefined"; if (isWorker) { // Running on a worker importScripts('util.js', 'util_worker.js'); } // Namespace for holding globals. var benchmark = {}; benchmark.startTimeInMs = 0; var xhrs = []; var timerID = null; function destroyAllXHRs() { for (var i = 0; i < xhrs.length; ++i) { xhrs[i].onreadystatechange = null; // Abort XHRs if they are not yet DONE state. // Calling abort() here (i.e. in onreadystatechange handler) // causes "NetworkError" messages in DevTools in sync mode, // even if it is after transition to DONE state. if (xhrs[i].readyState != XMLHttpRequest.DONE) 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) { config.addToLog('Failed (status=' + this.status + ')'); destroyAllXHRs(); return; } if (config.verifyData && !verifyAcknowledgement(config, this.response, size)) { destroyAllXHRs(); return; } totalReplied += size; if (totalReplied < totalSize) { return; } if (benchmark.startTimeInMs == null) { config.addToLog('startTimeInMs not set'); destroyAllXHRs(); return; } calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); destroyAllXHRs(); runNextTask(config); }; 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.dataType == 'arraybuffer' || config.dataType == 'blob') { data = new ArrayBuffer(size); fillArrayBuffer(data, 0x61); if (config.dataType == 'blob') { data = new Blob([data]); } } else { data = repeatString('a', size); } dataArray.push(data); } benchmark.startTimeInMs = getTimeStamp(); totalSize = size * xhrs.length; for (var i = 0; i < xhrs.length; ++i) { var data = dataArray[i]; var xhr = xhrs[i]; xhr.open('POST', config.prefixUrl + '_send', config.async); xhr.send(data); } } function receiveBenchmarkStep(size, config) { timerID = null; benchmark.startTimeInMs = null; var totalSize = 0; var totalReplied = 0; var checkResultAndContinue = function (bytesReceived, verificationResult) { if (!verificationResult) { config.addToLog('Response verification failed'); destroyAllXHRs(); return; } totalReplied += bytesReceived; if (totalReplied < totalSize) { return; } if (benchmark.startTimeInMs == null) { config.addToLog('startTimeInMs not set'); destroyAllXHRs(); return; } calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); destroyAllXHRs(); runNextTask(config); } var onReadyStateChangeHandler = function () { if (this.readyState != this.DONE) { return; } if (this.status != 200) { config.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) { config.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(config, 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(); totalSize = size * xhrs.length; for (var i = 0; i < xhrs.length; ++i) { var xhr = xhrs[i]; xhr.open('POST', config.prefixUrl + '_receive', config.async); xhr.responseType = config.dataType; xhr.send(size + ' none'); } } function getConfigString(config) { return '(' + config.dataType + ', verifyData=' + config.verifyData + ', ' + (isWorker ? 'Worker' : 'Main') + ', ' + (config.async ? 'Async' : 'Sync') + ', numXHRs=' + config.numXHRs + ', numIterations=' + config.numIterations + ', numWarmUpIterations=' + config.numWarmUpIterations + ')'; } function startBenchmark(config) { clearTimeout(timerID); destroyAllXHRs(); runNextTask(config); } // 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(config) { var task = tasks.shift(); if (task == undefined) { config.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 addTasks(config, stepFunc) { for (var i = 0; i < config.numWarmUpIterations + config.numIterations; ++i) { // Ignore the first |config.numWarmUpIterations| iterations. if (i == config.numWarmUpIterations) addResultClearingTask(config); var multiplierIndex = 0; for (var size = config.startSize; size <= config.stopThreshold; ++multiplierIndex) { var task = stepFunc.bind( null, size, config); tasks.push(task); size *= config.multipliers[ multiplierIndex % config.multipliers.length]; } } } function addResultReportingTask(config, title) { tasks.push(function(){ timerID = null; config.addToSummary(title); reportAverageData(config); clearAverageData(); runNextTask(config); }); } function addResultClearingTask(config) { tasks.push(function(){ timerID = null; clearAverageData(); runNextTask(config); }); } // -------------------------------- function sendBenchmark(config) { config.addToLog('Send benchmark'); config.addToLog(buildLegendString(config)); tasks = []; clearAverageData(); addTasks(config, sendBenchmarkStep); addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config)); startBenchmark(config); } function receiveBenchmark(config) { config.addToLog('Receive benchmark'); config.addToLog(buildLegendString(config)); tasks = []; clearAverageData(); addTasks(config, receiveBenchmarkStep); addResultReportingTask(config, 'Receive Benchmark ' + getConfigString(config)); startBenchmark(config); } function batchBenchmark(originalConfig) { originalConfig.addToLog('Batch benchmark'); tasks = []; clearAverageData(); var dataTypes = ['text', 'blob', 'arraybuffer']; var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep]; var names = ['Send', 'Receive']; var async = [true, false]; for (var i = 0; i < stepFuncs.length; ++i) { for (var j = 0; j < dataTypes.length; ++j) { for (var k = 0; k < async.length; ++k) { var config = cloneConfig(originalConfig); config.dataType = dataTypes[j]; config.async = async[k]; // Receive && Non-Worker && Sync is not supported by the spec if (stepFuncs[i] === receiveBenchmarkStep && !isWorker && !config.async) continue; addTasks(config, stepFuncs[i]); addResultReportingTask(config, names[i] + ' benchmark ' + getConfigString(config)); } } } startBenchmark(config); } function stop(config) { destroyAllXHRs(); clearTimeout(timerID); timerID = null; config.addToLog('Stopped'); } onmessage = function (message) { var config = message.data.config; config.addToLog = workerAddToLog; config.addToSummary = workerAddToSummary; config.measureValue = workerMeasureValue; if (message.data.type === 'sendBenchmark') sendBenchmark(config); else if (message.data.type === 'receiveBenchmark') receiveBenchmark(config); else if (message.data.type === 'batchBenchmark') batchBenchmark(config); else if (message.data.type === 'stop') stop(config); };