зеркало из https://github.com/mozilla/pluotsorbet.git
477 строки
15 KiB
JavaScript
477 строки
15 KiB
JavaScript
var Benchmark = (function() {
|
|
|
|
function mean(array) {
|
|
function add(a, b) {
|
|
return a + b;
|
|
}
|
|
return array.reduce(add, 0) / array.length;
|
|
}
|
|
|
|
var defaultStorage = {
|
|
// 30 is usually considered a large enough sample size for the central limit theorem
|
|
// to take effect, unless the distribution is too weird
|
|
numRounds: 30,
|
|
roundDelay: 5000, // ms to delay starting next round of tests
|
|
baseline: {},
|
|
current: {},
|
|
running: false,
|
|
round: 0,
|
|
deleteFs: false,
|
|
deleteJitCache: false,
|
|
buildBaseline: false,
|
|
recordMemory: true
|
|
};
|
|
|
|
var NO_SECURITY = typeof netscape !== "undefined" && netscape.security.PrivilegeManager;
|
|
|
|
function enableSuperPowers() {
|
|
// To enable chrome privileges use a separate profile and set the pref
|
|
// security.turn_off_all_security_so_that_viruses_can_take_over_this_computer
|
|
// to boolean true. To do this on a device, see:
|
|
// https://wiki.mozilla.org/B2G/QA/Tips_And_Tricks#For_changing_the_preference:
|
|
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
|
}
|
|
|
|
function forceCollectors() {
|
|
if (!NO_SECURITY) {
|
|
return Promise.resolve();
|
|
}
|
|
return new Promise(function(resolve, reject) {
|
|
enableSuperPowers();
|
|
console.log("Starting minimize memory.");
|
|
var gMgr = Components.classes["@mozilla.org/memory-reporter-manager;1"].getService(Components.interfaces.nsIMemoryReporterManager);
|
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
|
Services.obs.notifyObservers(null, "child-mmu-request", null);
|
|
gMgr.minimizeMemoryUsage(function() {
|
|
console.log("Finished minimize memory.");
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
var STORAGE_KEY = "benchmark";
|
|
var storage;
|
|
function initStorage(defaults) {
|
|
if (!(STORAGE_KEY in localStorage)) {
|
|
storage = defaults;
|
|
} else {
|
|
storage = JSON.parse(localStorage[STORAGE_KEY]);
|
|
for (var key in defaults) {
|
|
if (key in storage) {
|
|
continue;
|
|
}
|
|
storage[key] = defaults[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
function saveStorage() {
|
|
localStorage[STORAGE_KEY] = JSON.stringify(storage);
|
|
}
|
|
|
|
initStorage(defaultStorage);
|
|
var LEFT = 0; var CENTER = 1; var RIGHT = 2;
|
|
function prettyTable(rows, alignment) {
|
|
function pad(str, repeat, n, align) {
|
|
if (align === LEFT) {
|
|
return str.padRight(repeat, n);
|
|
} else if (align === CENTER) {
|
|
var middle = ((n - str.length) / 2) | 0;
|
|
return str.padRight(repeat, middle + str.length).padLeft(repeat, n);
|
|
} else if (align === RIGHT) {
|
|
return str.padLeft(repeat, n);
|
|
}
|
|
throw new Error("Bad align value." + align);
|
|
}
|
|
var maxColumnLengths = [];
|
|
var numColumns = rows[0].length;
|
|
for (var colIndex = 0; colIndex < numColumns; colIndex++) {
|
|
var maxLength = 0;
|
|
for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
var strLen = rows[rowIndex][colIndex].toString().length;
|
|
if (rows[rowIndex].untrusted) {
|
|
strLen += 2;
|
|
}
|
|
maxLength = Math.max(strLen, maxLength);
|
|
}
|
|
maxColumnLengths[colIndex] = maxLength;
|
|
}
|
|
var out = "";
|
|
for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
out += "| ";
|
|
for (var colIndex = 0; colIndex < numColumns; colIndex++) {
|
|
var str = rows[rowIndex][colIndex].toString();
|
|
if (rows[rowIndex].untrusted) {
|
|
str = "*" + str + "*";
|
|
}
|
|
out += pad(str, " ", maxColumnLengths[colIndex], rowIndex === 0 ? CENTER : alignment[colIndex]) + " | ";
|
|
}
|
|
out += "\n";
|
|
if (rowIndex === 0) {
|
|
out += "|";
|
|
for (var colIndex = 0; colIndex < numColumns; colIndex++) {
|
|
var align = alignment[colIndex];
|
|
if (align === 0) {
|
|
out += ":".padRight("-", maxColumnLengths[colIndex] + 2);
|
|
} else if (align === 1) {
|
|
out += ":".padLeft("-", maxColumnLengths[colIndex] + 1) + ":";
|
|
} else if (align === 2) {
|
|
out += ":".padLeft("-", maxColumnLengths[colIndex] + 2);
|
|
}
|
|
out += "|";
|
|
}
|
|
out += "\n";
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function numberWithCommas(x) {
|
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
function msFormatter(x) {
|
|
return numberWithCommas(Math.round(x)) + "ms";
|
|
}
|
|
|
|
function byteFormatter(x) {
|
|
return numberWithCommas(Math.round(x / 1024)) + "kb";
|
|
}
|
|
|
|
var valueFormatters = {
|
|
startupTime: msFormatter,
|
|
vmStartupTime: msFormatter,
|
|
bgStartupTime: msFormatter,
|
|
fgStartupTime: msFormatter,
|
|
totalSize: byteFormatter,
|
|
domSize: byteFormatter,
|
|
styleSize: byteFormatter,
|
|
jsObjectsSize: byteFormatter,
|
|
jsStringsSize: byteFormatter,
|
|
jsOtherSize: byteFormatter,
|
|
otherSize: byteFormatter,
|
|
USS: byteFormatter,
|
|
peakRSS: byteFormatter,
|
|
};
|
|
|
|
var untrustedValues = [
|
|
"totalSize", "domSize", "styleSize", "jsObjectsSize",
|
|
"jsStringsSize", "jsOtherSize", "otherSize", "USS",
|
|
"peakRSS",
|
|
];
|
|
|
|
function sampleMemory() {
|
|
if (!NO_SECURITY) {
|
|
return Promise.resolve({});
|
|
}
|
|
return forceCollectors().then(function() {
|
|
var memoryReporter = Components.classes["@mozilla.org/memory-reporter-manager;1"].getService(Components.interfaces.nsIMemoryReporterManager);
|
|
|
|
var jsObjectsSize = {};
|
|
var jsStringsSize = {};
|
|
var jsOtherSize = {};
|
|
var domSize = {};
|
|
var styleSize = {};
|
|
var otherSize = {};
|
|
var totalSize = {};
|
|
var jsMilliseconds = {};
|
|
var nonJSMilliseconds = {};
|
|
|
|
try {
|
|
memoryReporter.sizeOfTab(window.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
|
|
domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
|
|
var memValues = {
|
|
totalSize: totalSize.value,
|
|
domSize: domSize.value,
|
|
styleSize: styleSize.value,
|
|
jsObjectsSize: jsObjectsSize.value,
|
|
jsStringsSize: jsStringsSize.value,
|
|
jsOtherSize: jsOtherSize.value,
|
|
otherSize: otherSize.value,
|
|
};
|
|
|
|
// residentUnique is not available on all platforms.
|
|
try {
|
|
memValues.USS = memoryReporter.residentUnique;
|
|
} catch (e) {
|
|
}
|
|
|
|
// residentPeak is not available on all platforms.
|
|
try {
|
|
memValues.peakRSS = memoryReporter.residentPeak;
|
|
} catch (e) {
|
|
}
|
|
|
|
return memValues;
|
|
});
|
|
}
|
|
|
|
var startup = {
|
|
run: function(settings) {
|
|
storage.round = 0;
|
|
var current = storage.current = {};
|
|
current.startupTime = [];
|
|
current.vmStartupTime = [];
|
|
current.bgStartupTime = [];
|
|
current.fgStartupTime = [];
|
|
if (settings.recordMemory) {
|
|
storage.recordMemory = true;
|
|
current.totalSize = [];
|
|
current.domSize = [];
|
|
current.styleSize = [];
|
|
current.jsObjectsSize = [];
|
|
current.jsStringsSize = [];
|
|
current.jsOtherSize = [];
|
|
current.otherSize = [];
|
|
current.USS = [];
|
|
current.peakRSS = [];
|
|
}
|
|
storage.running = true;
|
|
storage.numRounds = "numRounds" in settings ? settings.numRounds : defaultStorage.numRounds;
|
|
storage.roundDelay = "roundDelay" in settings ? settings.roundDelay : defaultStorage.roundDelay;
|
|
storage.deleteFs = "deleteFs" in settings ? settings.deleteFs : defaultStorage.deleteFs;
|
|
storage.deleteJitCache = "deleteJitCache" in settings ? settings.deleteJitCache : defaultStorage.deleteJitCache;
|
|
storage.buildBaseline = "buildBaseline" in settings ? settings.buildBaseline : defaultStorage.buildBaseline;
|
|
if (storage.buildBaseline) {
|
|
storage.baseline = {};
|
|
}
|
|
saveStorage();
|
|
this.runNextRound();
|
|
},
|
|
startTimer: function(which, now) {
|
|
if (!storage.running) {
|
|
console.log("startTimer called while benchmark not running");
|
|
return;
|
|
}
|
|
if (!this.startTime) {
|
|
this.startTime = {};
|
|
}
|
|
this.startTime[which] = now;
|
|
},
|
|
stopTimer: function(which, now) {
|
|
if (!storage.running) {
|
|
console.log("stopTimer called while benchmark not running");
|
|
return;
|
|
}
|
|
if (this.startTime[which] === null) {
|
|
console.log("stopTimer called without previous call to startTimer");
|
|
return;
|
|
}
|
|
var took = now - this.startTime[which];
|
|
storage.current[which].push(took);
|
|
if (which === "startupTime") {
|
|
storage.round++;
|
|
saveStorage();
|
|
this.runNextRound();
|
|
}
|
|
},
|
|
sampleMemoryToStorage: function() {
|
|
return sampleMemory().then(function(mem) {
|
|
for (var p in mem) {
|
|
storage.current[p].push(mem[p]);
|
|
}
|
|
saveStorage();
|
|
});
|
|
},
|
|
runNextRound: function() {
|
|
var self = this;
|
|
var done = storage.round >= storage.numRounds;
|
|
function run() {
|
|
var promise;
|
|
if (storage.round === 0) {
|
|
promise = Promise.resolve();
|
|
} else {
|
|
promise = self.sampleMemoryToStorage();
|
|
}
|
|
|
|
promise.then(function() {
|
|
if (done) {
|
|
self.finish();
|
|
return;
|
|
}
|
|
DumbPipe.close(DumbPipe.open("gcReload", {}));
|
|
}).catch(function (e) {
|
|
console.error(e)
|
|
});
|
|
}
|
|
if (storage.deleteFs) {
|
|
console.log("Deleting fs.");
|
|
indexedDB.deleteDatabase("asyncStorage");
|
|
}
|
|
if (storage.deleteJitCache) {
|
|
console.log("Deleting jit cache.");
|
|
indexedDB.deleteDatabase("CompiledMethodCache");
|
|
}
|
|
if (storage.round !== 0) {
|
|
console.log("Scheduling round " + (storage.round) + " of " + storage.numRounds + " finalization in " + storage.roundDelay + "ms");
|
|
setTimeout(run, storage.roundDelay);
|
|
} else {
|
|
run();
|
|
}
|
|
},
|
|
finish: function() {
|
|
storage.running = false;
|
|
saveStorage();
|
|
var labels = ["Test", "Baseline Mean", "Mean", "+/-", "%", "P", "Min", "Max"];
|
|
var rows = [labels];
|
|
for (var key in storage.current) {
|
|
var samples = storage.current[key];
|
|
var baselineSamples = storage.baseline[key] || [];
|
|
var hasBaseline = baselineSamples.length > 0;
|
|
var formatter = valueFormatters[key];
|
|
|
|
var row = [key];
|
|
row.untrusted = untrustedValues.indexOf(key) != -1;
|
|
rows.push(row);
|
|
var currentMean = mean(samples);
|
|
var baselineMean = mean(baselineSamples);
|
|
row.push(hasBaseline ? formatter(baselineMean) + "" : "n/a");
|
|
row.push(formatter(currentMean) + "");
|
|
row.push(hasBaseline ? formatter(currentMean - baselineMean) + "" : "n/a");
|
|
row.push(hasBaseline ? (100 * (currentMean - baselineMean) / baselineMean).toFixed(2) : "n/a");
|
|
var pMessage = "n/a";
|
|
if (hasBaseline) {
|
|
var p = (baselineSamples.length < 2) ? 1 : ttest(baselineSamples, samples).pValue();
|
|
if (p < 0.05) {
|
|
pMessage = currentMean < baselineMean ? "BETTER" : "WORSE";
|
|
} else {
|
|
pMessage = "SAME";
|
|
}
|
|
} else {
|
|
pMessage = "n/a";
|
|
}
|
|
row.push(pMessage);
|
|
row.push(formatter(Math.min.apply(null, samples)));
|
|
row.push(formatter(Math.max.apply(null, samples)));
|
|
}
|
|
if (storage.buildBaseline) {
|
|
storage.baseline = storage.current;
|
|
storage.buildBaseline = false;
|
|
console.log("FINISHED BUILDING BASELINE");
|
|
}
|
|
console.log("Raw Values:\n" + "Current: " + JSON.stringify(storage.current) + "\nBaseline: " + JSON.stringify(storage.baseline))
|
|
var configRows = [
|
|
["Config", "Value"],
|
|
["User Agent", window.navigator.userAgent],
|
|
["Rounds", storage.numRounds],
|
|
["Delay(ms)", storage.roundDelay],
|
|
["Delete FS", storage.deleteFs ? "yes" : "no"],
|
|
["Delete JIT CACHE", storage.deleteJitCache ? "yes" : "no"],
|
|
];
|
|
var out = "\n" +
|
|
prettyTable(configRows, [LEFT, LEFT]) + "\n" +
|
|
prettyTable(rows, [LEFT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT]);
|
|
console.log(out);
|
|
saveStorage();
|
|
}
|
|
|
|
};
|
|
|
|
// Start right away instead of in init() so we can see any speedups in script loading.
|
|
if (storage.running) {
|
|
var now = performance.now();
|
|
startup.startTimer("startupTime", now);
|
|
startup.startTimer("vmStartupTime", now);
|
|
}
|
|
|
|
var numRoundsEl;
|
|
var roundDelayEl;
|
|
var deleteFsEl;
|
|
var deleteJitCacheEl;
|
|
var startButton;
|
|
var baselineButton;
|
|
|
|
function getSettings() {
|
|
return {
|
|
numRounds: numRoundsEl.value | 0,
|
|
roundDelay: roundDelayEl.value | 0,
|
|
deleteFs: !!deleteFsEl.checked,
|
|
deleteJitCache: !!deleteJitCacheEl.checked,
|
|
recordMemory: NO_SECURITY
|
|
};
|
|
}
|
|
|
|
function start() {
|
|
startup.run(getSettings());
|
|
}
|
|
|
|
function buildBaseline() {
|
|
var settings = getSettings();
|
|
settings.buildBaseline = true;
|
|
startup.run(settings);
|
|
}
|
|
|
|
return {
|
|
initUI: function() {
|
|
numRoundsEl = document.getElementById("benchmark-num-rounds");
|
|
roundDelayEl = document.getElementById("benchmark-round-delay");
|
|
deleteFsEl = document.getElementById("benchmark-delete-fs");
|
|
deleteJitCacheEl = document.getElementById("benchmark-delete-jit-cache");
|
|
startButton = document.getElementById("benchmark-startup-run");
|
|
baselineButton = document.getElementById("benchmark-startup-baseline");
|
|
|
|
numRoundsEl.value = storage.numRounds;
|
|
roundDelayEl.value = storage.roundDelay;
|
|
deleteFsEl.checked = storage.deleteFs;
|
|
deleteJitCacheEl.checked = storage.deleteJitCache;
|
|
|
|
startButton.onclick = start;
|
|
baselineButton.onclick = buildBaseline;
|
|
},
|
|
start: start,
|
|
buildBaseline: buildBaseline,
|
|
sampleMemory: sampleMemory,
|
|
forceCollectors: forceCollectors,
|
|
prettyTable: prettyTable,
|
|
LEFT: LEFT,
|
|
CENTER: CENTER,
|
|
RIGHT: RIGHT,
|
|
startup: {
|
|
setStartTime: function () {
|
|
startup.startTime["startupTime"] = startup.startTime["vmStartupTime"] = performance.now();
|
|
},
|
|
init: function() {
|
|
if (!storage.running) {
|
|
return;
|
|
}
|
|
|
|
var vmImplKey = "com/sun/midp/main/MIDletSuiteUtils.vmEndStartUp.(I)V";
|
|
var vmOriginalFn = Native[vmImplKey];
|
|
var vmCalled = false;
|
|
Native[vmImplKey] = function() {
|
|
if (!vmCalled) {
|
|
vmCalled = true;
|
|
var now = performance.now();
|
|
startup.stopTimer("vmStartupTime", now);
|
|
startup.startTimer("bgStartupTime", now);
|
|
}
|
|
vmOriginalFn.apply(null, arguments);
|
|
};
|
|
|
|
var bgImplKey = "com/nokia/mid/s40/bg/BGUtils.getFGMIDletClass.()Ljava/lang/String;";
|
|
var bgOriginalFn = Native[bgImplKey];
|
|
Native[bgImplKey] = function() {
|
|
var now = performance.now();
|
|
startup.stopTimer("bgStartupTime", now);
|
|
startup.startTimer("fgStartupTime", now);
|
|
return bgOriginalFn.apply(null, arguments);
|
|
};
|
|
|
|
var fgImplKey = "com/sun/midp/lcdui/DisplayDevice.gainedForeground0.(II)V";
|
|
var fgOriginalFn = Native[fgImplKey];
|
|
Native[fgImplKey] = function() {
|
|
var now = performance.now();
|
|
startup.stopTimer("fgStartupTime", now);
|
|
startup.stopTimer("startupTime", now);
|
|
fgOriginalFn.apply(null, arguments);
|
|
};
|
|
},
|
|
run: startup.run.bind(startup),
|
|
}
|
|
};
|
|
})();
|