const soon = (() => {
let _soon;
return () => {
if (!_soon) {
_soon = Promise.resolve()
.then(() => {
_soon = null;
});
}
return _soon;
};
})();
class Emitter {
constructor () {
Object.defineProperty(this, '_listeners', {
value: {},
enumerable: false
});
}
on (name, listener, context) {
if (!this._listeners[name]) {
this._listeners[name] = [];
}
this._listeners[name].push(listener, context);
}
off (name, listener, context) {
if (this._listeners[name]) {
if (listener) {
for (let i = 0; i < this._listeners[name].length; i += 2) {
if (
this._listeners[name][i] === listener &&
this._listeners[name][i + 1] === context) {
this._listeners[name].splice(i, 2);
i -= 2;
}
}
} else {
for (let i = 0; i < this._listeners[name].length; i += 2) {
if (this._listeners[name][i + 1] === context) {
this._listeners[name].splice(i, 2);
i -= 2;
}
}
}
}
}
emit (name, ...args) {
if (this._listeners[name]) {
for (let i = 0; i < this._listeners[name].length; i += 2) {
this._listeners[name][i].call(this._listeners[name][i + 1] || this, ...args);
}
}
}
}
class BenchFrameStream extends Emitter {
constructor (frame) {
super();
this.frame = frame;
window.addEventListener('message', message => {
this.emit('message', message.data);
});
}
send (message) {
this.frame.send(message);
}
}
const benchmarkUrlArgs = args => (
[
args.projectId,
args.warmUpTime,
args.recordingTime
].join(',')
);
const BENCH_MESSAGE_TYPE = {
INACTIVE: 'BENCH_MESSAGE_INACTIVE',
LOAD: 'BENCH_MESSAGE_LOAD',
LOADING: 'BENCH_MESSAGE_LOADING',
WARMING_UP: 'BENCH_MESSAGE_WARMING_UP',
ACTIVE: 'BENCH_MESSAGE_ACTIVE',
COMPLETE: 'BENCH_MESSAGE_COMPLETE'
};
class BenchUtil {
constructor (frame) {
this.frame = frame;
this.benchStream = new BenchFrameStream(frame);
}
setFrameLocation (url) {
this.frame.contentWindow.location.assign(url);
}
startBench (args) {
this.benchArgs = args;
this.setFrameLocation(`index.html#${benchmarkUrlArgs(args)}`);
}
pauseBench () {
new Promise(resolve => setTimeout(resolve, 1000))
.then(() => {
this.benchStream.emit('message', {
type: BENCH_MESSAGE_TYPE.INACTIVE
});
});
}
resumeBench () {
this.startBench(this.benchArgs);
}
renderResults (results) {
this.setFrameLocation(
`index.html#view/${btoa(JSON.stringify(results))}`
);
}
}
const BENCH_STATUS = {
INACTIVE: 'BENCH_STATUS_INACTIVE',
RESUME: 'BENCH_STATUS_RESUME',
STARTING: 'BENCH_STATUS_STARTING',
LOADING: 'BENCH_STATUS_LOADING',
WARMING_UP: 'BENCH_STATUS_WARMING_UP',
ACTIVE: 'BENCH_STATUS_ACTIVE',
COMPLETE: 'BENCH_STATUS_COMPLETE'
};
class BenchResult {
constructor ({fixture, status = BENCH_STATUS.INACTIVE, frames = null, opcodes = null}) {
this.fixture = fixture;
this.status = status;
this.frames = frames;
this.opcodes = opcodes;
}
}
class BenchFixture extends Emitter {
constructor ({
projectId,
warmUpTime = 4000,
recordingTime = 6000
}) {
super();
this.projectId = projectId;
this.warmUpTime = warmUpTime;
this.recordingTime = recordingTime;
}
get id () {
return `${this.projectId}-${this.warmUpTime}-${this.recordingTime}`;
}
run (util) {
return new Promise(resolve => {
util.benchStream.on('message', message => {
const result = {
fixture: this,
status: BENCH_STATUS.STARTING,
frames: null,
opcodes: null
};
if (message.type === BENCH_MESSAGE_TYPE.INACTIVE) {
result.status = BENCH_STATUS.RESUME;
} else if (message.type === BENCH_MESSAGE_TYPE.LOADING) {
result.status = BENCH_STATUS.LOADING;
} else if (message.type === BENCH_MESSAGE_TYPE.WARMING_UP) {
result.status = BENCH_STATUS.WARMING_UP;
} else if (message.type === BENCH_MESSAGE_TYPE.ACTIVE) {
result.status = BENCH_STATUS.ACTIVE;
} else if (message.type === BENCH_MESSAGE_TYPE.COMPLETE) {
result.status = BENCH_STATUS.COMPLETE;
result.frames = message.frames;
result.opcodes = message.opcodes;
resolve(new BenchResult(result));
util.benchStream.off('message', null, this);
}
this.emit('result', new BenchResult(result));
}, this);
util.startBench(this);
});
}
}
class BenchSuiteResult extends Emitter {
constructor ({suite, results = []}) {
super();
this.suite = suite;
this.results = results;
if (suite) {
suite.on('result', result => {
if (result.status === BENCH_STATUS.COMPLETE) {
this.results.push(results);
this.emit('add', this);
}
});
}
}
}
class BenchSuite extends Emitter {
constructor (fixtures = []) {
super();
this.fixtures = fixtures;
}
add (fixture) {
this.fixtures.push(fixture);
}
run (util) {
return new Promise(resolve => {
const fixtures = this.fixtures.slice();
const results = [];
const push = result => {
result.fixture.off('result', null, this);
results.push(result);
};
const emitResult = this.emit.bind(this, 'result');
const pop = () => {
const fixture = fixtures.shift();
if (fixture) {
fixture.on('result', emitResult, this);
fixture.run(util)
.then(push)
.then(pop);
} else {
resolve(new BenchSuiteResult({suite: this, results}));
}
};
pop();
});
}
}
class BenchRunner extends Emitter {
constructor ({frame, suite}) {
super();
this.frame = frame;
this.suite = suite;
this.util = new BenchUtil(frame);
}
run () {
return this.suite.run(this.util);
}
}
const viewNames = {
[BENCH_STATUS.INACTIVE]: 'Inactive',
[BENCH_STATUS.RESUME]: 'Resume',
[BENCH_STATUS.STARTING]: 'Starting',
[BENCH_STATUS.LOADING]: 'Loading',
[BENCH_STATUS.WARMING_UP]: 'Warming Up',
[BENCH_STATUS.ACTIVE]: 'Active',
[BENCH_STATUS.COMPLETE]: 'Complete'
};
class BenchResultView {
constructor ({result, benchUtil}) {
this.result = result;
this.compare = null;
this.benchUtil = benchUtil;
this.dom = document.createElement('div');
}
update (result) {
soon().then(() => this.render(result));
}
resume () {
this.benchUtil.resumeBench();
}
setFrameLocation (loc) {
this.benchUtil.pauseBench();
this.benchUtil.setFrameLocation(loc);
}
act (ev) {
if (
ev.type === 'click' &&
ev.button === 0 &&
!(ev.altKey || ev.ctrlKey || ev.shiftKey || ev.metaKey)
) {
let target = ev.target;
while (target && target.tagName.toLowerCase() !== 'a') {
target = target.parentElement;
}
if (target && target.tagName.toLowerCase() === 'a') {
if (target.href) {
this.setFrameLocation(target.href);
ev.preventDefault();
}
} else if (ev.currentTarget.classList.contains('resume')) {
this.resume();
}
}
}
render (newResult = this.result, compareResult = this.compare) {
const newResultFrames = (newResult.frames ? newResult.frames : []);
const blockFunctionFrame = newResultFrames
.find(frame => frame.name === 'blockFunction');
const stepThreadsInnerFrame = newResultFrames
.find(frame => frame.name === 'Sequencer.stepThreads#inner');
const blocksPerSecond = blockFunctionFrame ?
(blockFunctionFrame.executions /
(stepThreadsInnerFrame.totalTime / 1000)) | 0 :
0;
const stepsPerSecond = stepThreadsInnerFrame ?
(stepThreadsInnerFrame.executions /
(stepThreadsInnerFrame.totalTime / 1000)) | 0 :
0;
const compareResultFrames = (
compareResult && compareResult.frames ?
compareResult.frames :
[]
);
const blockFunctionCompareFrame = compareResultFrames
.find(frame => frame.name === 'blockFunction');
const stepThreadsInnerCompareFrame = compareResultFrames
.find(frame => frame.name === 'Sequencer.stepThreads#inner');
const compareBlocksPerSecond = blockFunctionCompareFrame ?
(blockFunctionCompareFrame.executions /
(stepThreadsInnerCompareFrame.totalTime / 1000)) | 0 :
0;
const compareStepsPerSecond = stepThreadsInnerCompareFrame ?
(stepThreadsInnerCompareFrame.executions /
(stepThreadsInnerCompareFrame.totalTime / 1000)) | 0 :
0;
const statusName = viewNames[newResult.status];
this.dom.className = `result-view ${
viewNames[newResult.status].toLowerCase()
}`;
this.dom.onclick = this.act.bind(this);
let url = `index.html#${benchmarkUrlArgs(newResult.fixture)}`;
if (newResult.status === BENCH_STATUS.COMPLETE) {
url = `index.html#view/${btoa(JSON.stringify(newResult))}`;
}
let compareUrl = url;
if (compareResult && compareResult) {
compareUrl =
`index.html#view/${btoa(JSON.stringify(compareResult))}`;
}
let compareHTML = '';
if (stepThreadsInnerFrame && stepThreadsInnerCompareFrame) {
compareHTML = `