mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-25 17:09:50 -05:00
Add benchmark suite, run and report on multiple benchmark projects
This commit is contained in:
parent
b02d7e8cd8
commit
c6f4d89371
4 changed files with 469 additions and 0 deletions
|
@ -348,11 +348,21 @@ class ProfilerRun {
|
||||||
run () {
|
run () {
|
||||||
loadProject();
|
loadProject();
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'BENCH_MESSAGE_LOADING'
|
||||||
|
}, '*');
|
||||||
|
|
||||||
this.vm.on('workspaceUpdate', () => {
|
this.vm.on('workspaceUpdate', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'BENCH_MESSAGE_WARMING_UP'
|
||||||
|
}, '*');
|
||||||
this.vm.greenFlag();
|
this.vm.greenFlag();
|
||||||
}, 100);
|
}, 100);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'BENCH_MESSAGE_ACTIVE'
|
||||||
|
}, '*');
|
||||||
this.vm.runtime.profiler = this.profiler;
|
this.vm.runtime.profiler = this.profiler;
|
||||||
}, 100 + this.warmUpTime);
|
}, 100 + this.warmUpTime);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -362,6 +372,12 @@ class ProfilerRun {
|
||||||
|
|
||||||
this.frameTable.render();
|
this.frameTable.render();
|
||||||
this.opcodeTable.render();
|
this.opcodeTable.render();
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'BENCH_MESSAGE_COMPLETE',
|
||||||
|
frames: this.frames.frames,
|
||||||
|
opcodes: this.opcodes.opcodes
|
||||||
|
}, '*');
|
||||||
}, 100 + this.warmUpTime + this.maxRecordedTime);
|
}, 100 + this.warmUpTime + this.maxRecordedTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
45
src/playground/suite.css
Normal file
45
src/playground/suite.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-controls {
|
||||||
|
position: absolute;
|
||||||
|
width: 30em;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 30em;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
padding: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bench-frame-owner {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 30em);
|
||||||
|
height: 100%;
|
||||||
|
left: 30em;
|
||||||
|
right: 100%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-project {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-warm-up {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-recording {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-status {
|
||||||
|
float: right;
|
||||||
|
}
|
23
src/playground/suite.html
Normal file
23
src/playground/suite.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Scratch VM Benchmark Suite</title>
|
||||||
|
<link rel="stylesheet" href="./suite.css" type="text/css" media="screen">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="runner-controls">
|
||||||
|
<h2>Scratch VM Benchmark Suite</h2>
|
||||||
|
<div class="suite-results">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bench-frame-owner">
|
||||||
|
<iframe src="" class="bench-frame" width="100%" height="100%"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BenchRunner -->
|
||||||
|
<script src="./suite.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
385
src/playground/suite.js
Normal file
385
src/playground/suite.js
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
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 BENCH_MESSAGE_TYPE = {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
startBench (args) {
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||||
|
.then(() => {
|
||||||
|
this.frame.contentWindow.location.assign('about:blank');
|
||||||
|
})
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||||
|
.then(() => {
|
||||||
|
this.frame.contentWindow.location.assign(
|
||||||
|
`/benchmark/#${args.projectId},${args.warmUpTime},${args.recordingTime}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BENCH_STATUS = {
|
||||||
|
INACTIVE: 'BENCH_STATUS_INACTIVE',
|
||||||
|
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.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.STARTING]: 'Starting',
|
||||||
|
[BENCH_STATUS.LOADING]: 'Loading',
|
||||||
|
[BENCH_STATUS.WARMING_UP]: 'Warming Up',
|
||||||
|
[BENCH_STATUS.ACTIVE]: 'Active',
|
||||||
|
[BENCH_STATUS.COMPLETE]: 'Complete'
|
||||||
|
};
|
||||||
|
|
||||||
|
class BenchResultView {
|
||||||
|
constructor ({result}) {
|
||||||
|
this.result = result;
|
||||||
|
this.dom = document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
update (result) {
|
||||||
|
soon().then(() => this.render(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
render (newResult = this.result) {
|
||||||
|
const blockFunctionFrame = (newResult.frames ? newResult.frames : [])
|
||||||
|
.find(frame => frame.name === 'blockFunction');
|
||||||
|
const stepThreadsInnerFrame = (newResult.frames ? newResult.frames : [])
|
||||||
|
.find(frame => frame.name === 'Sequencer.stepThreads#inner');
|
||||||
|
|
||||||
|
const blocksPerSecond = blockFunctionFrame ?
|
||||||
|
(blockFunctionFrame.executions / (stepThreadsInnerFrame.totalTime / 1000)) | 0 :
|
||||||
|
0;
|
||||||
|
|
||||||
|
this.dom.innerHTML = `
|
||||||
|
<div class="fixture-project">
|
||||||
|
<label>Project ID</label> <a
|
||||||
|
href="/playground/#${newResult.fixture.projectId}"
|
||||||
|
>${newResult.fixture.projectId}</a>
|
||||||
|
</div>
|
||||||
|
(<span class="fixture-warm-up">${newResult.fixture.warmUpTime / 1000}s</span>
|
||||||
|
<span class="fixture-recording">${newResult.fixture.recordingTime / 1000}s</span>)
|
||||||
|
<div class="result-status">
|
||||||
|
${blockFunctionFrame ? `${blocksPerSecond} blocks/s` : viewNames[newResult.status]}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.result = newResult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BenchSuiteResultView {
|
||||||
|
constructor ({runner}) {
|
||||||
|
this.runner = runner;
|
||||||
|
this.suite = runner.suite;
|
||||||
|
this.views = {};
|
||||||
|
this.dom = document.createElement('div');
|
||||||
|
|
||||||
|
const {suite} = runner;
|
||||||
|
for (const fixture of suite.fixtures) {
|
||||||
|
this.views[fixture.id] = new BenchResultView({result: new BenchResult({fixture})});
|
||||||
|
this.dom.appendChild(this.views[fixture.id].render().dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.on('result', result => {
|
||||||
|
this.views[result.fixture.id].update(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
const suite = new BenchSuite();
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 130041250,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 2000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 130041250,
|
||||||
|
warmUpTime: 4000,
|
||||||
|
recordingTime: 6000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 14844969,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 2000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 14844969,
|
||||||
|
warmUpTime: 1000,
|
||||||
|
recordingTime: 6000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 173918262,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 173918262,
|
||||||
|
warmUpTime: 5000,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 155128646,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 155128646,
|
||||||
|
warmUpTime: 5000,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 89811578,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 89811578,
|
||||||
|
warmUpTime: 5000,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 139193539,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 139193539,
|
||||||
|
warmUpTime: 5000,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
const frame = document.getElementsByTagName('iframe')[0];
|
||||||
|
const runner = new BenchRunner({frame, suite});
|
||||||
|
const resultsView = new BenchSuiteResultView({runner});
|
||||||
|
|
||||||
|
document.getElementsByClassName('suite-results')[0].appendChild(resultsView.dom);
|
||||||
|
|
||||||
|
runner.run();
|
||||||
|
};
|
Loading…
Reference in a new issue