mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-22 20:19:05 -04:00
Merge pull request #901 from mzgoddard/benchmark-prototype
Benchmark prototype
This commit is contained in:
commit
cc1923f982
6 changed files with 862 additions and 17 deletions
|
@ -11,16 +11,42 @@ p {
|
||||||
left: 450px;
|
left: 450px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.share {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.share label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.share[href] {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.render .profile-tables {
|
||||||
|
position: static;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.render .description {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.render .run-push {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#scratch-stage {
|
#scratch-stage {
|
||||||
border: 5px solid black;
|
border: 5px solid black;
|
||||||
display: block;
|
display: block;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
.render #scratch-stage {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.loading label, .profile-count label{
|
.loading label, .profile-count label{
|
||||||
width: 15em;
|
width: 15em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
.render .loading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.profile-tables table {
|
.profile-tables table {
|
||||||
margin: 30px 0 30px 0px;
|
margin: 30px 0 30px 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,20 @@ document.querySelector('.run')
|
||||||
location.reload();
|
location.reload();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
const setShareLink = function (json) {
|
||||||
|
document.querySelector('.share')
|
||||||
|
.href = `#view/${btoa(JSON.stringify(json))}`;
|
||||||
|
document.querySelectorAll('.share')[1]
|
||||||
|
.href = `suite.html`;
|
||||||
|
};
|
||||||
|
|
||||||
const loadProject = function () {
|
const loadProject = function () {
|
||||||
let id = location.hash.substring(1);
|
let id = location.hash.substring(1).split(',')[0];
|
||||||
if (id.length < 1 || !isFinite(id)) {
|
if (id.length < 1 || !isFinite(id)) {
|
||||||
id = projectInput.value;
|
id = projectInput.value;
|
||||||
}
|
}
|
||||||
Scratch.vm.downloadProjectId(id);
|
Scratch.vm.downloadProjectId(id);
|
||||||
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,10 +134,16 @@ class StatView {
|
||||||
}
|
}
|
||||||
|
|
||||||
cell = document.createElement('td');
|
cell = document.createElement('td');
|
||||||
|
// Truncate selfTime. Value past the microsecond are floating point
|
||||||
|
// noise.
|
||||||
|
this.selfTime = Math.floor(this.selfTime * 1000) / 1000;
|
||||||
cell.innerText = (this.selfTime / 1000).toPrecision(3);
|
cell.innerText = (this.selfTime / 1000).toPrecision(3);
|
||||||
row.appendChild(cell);
|
row.appendChild(cell);
|
||||||
|
|
||||||
cell = document.createElement('td');
|
cell = document.createElement('td');
|
||||||
|
// Truncate totalTime. Value past the microsecond are floating point
|
||||||
|
// noise.
|
||||||
|
this.totalTime = Math.floor(this.totalTime * 1000) / 1000;
|
||||||
cell.innerText = (this.totalTime / 1000).toPrecision(3);
|
cell.innerText = (this.totalTime / 1000).toPrecision(3);
|
||||||
row.appendChild(cell);
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
@ -221,6 +235,13 @@ const frameOrder = [
|
||||||
'Runtime._step'
|
'Runtime._step'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const trackSlowFrames = [
|
||||||
|
'Sequencer.stepThreads',
|
||||||
|
'Sequencer.stepThreads#inner',
|
||||||
|
'Sequencer.stepThread',
|
||||||
|
'execute'
|
||||||
|
];
|
||||||
|
|
||||||
class FramesTable extends StatTable {
|
class FramesTable extends StatTable {
|
||||||
constructor (options) {
|
constructor (options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -241,12 +262,7 @@ class FramesTable extends StatTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
isSlow (key, frame) {
|
isSlow (key, frame) {
|
||||||
return ([
|
return (trackSlowFrames.indexOf(key) > 0 &&
|
||||||
'Sequencer.stepThreads',
|
|
||||||
'Sequencer.stepThreads#inner',
|
|
||||||
'Sequencer.stepThread',
|
|
||||||
'execute'
|
|
||||||
].indexOf(key) > 0 &&
|
|
||||||
frame.selfTime / frame.totalTime > SLOW);
|
frame.selfTime / frame.totalTime > SLOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +326,7 @@ class ProfilerRun {
|
||||||
dom: document.getElementsByClassName('profile-count-group')[0],
|
dom: document.getElementsByClassName('profile-count-group')[0],
|
||||||
|
|
||||||
runningStats,
|
runningStats,
|
||||||
maxRecordedTime: 6000
|
maxRecordedTime
|
||||||
});
|
});
|
||||||
|
|
||||||
const frames = this.frames = new Frames(profiler);
|
const frames = this.frames = new Frames(profiler);
|
||||||
|
@ -346,13 +362,23 @@ class ProfilerRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
run () {
|
run () {
|
||||||
loadProject();
|
this.projectId = 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,12 +388,55 @@ 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
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
setShareLink({
|
||||||
|
fixture: {
|
||||||
|
projectId: this.projectId,
|
||||||
|
warmUpTime: this.warmUpTime,
|
||||||
|
recordingTime: this.maxRecordedTime
|
||||||
|
},
|
||||||
|
frames: this.frames.frames,
|
||||||
|
opcodes: this.opcodes.opcodes
|
||||||
|
});
|
||||||
}, 100 + this.warmUpTime + this.maxRecordedTime);
|
}, 100 + this.warmUpTime + this.maxRecordedTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render (json) {
|
||||||
|
const {fixture} = json;
|
||||||
|
document.querySelector('[type=text]').value = [
|
||||||
|
fixture.projectId,
|
||||||
|
fixture.warmUpTime,
|
||||||
|
fixture.recordingTime
|
||||||
|
].join(',');
|
||||||
|
|
||||||
|
this.frames.frames = json.frames.map(
|
||||||
|
frame => Object.assign(new StatView(), frame, {
|
||||||
|
name: this.profiler.nameById(this.profiler.idByName(frame.name))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.opcodes.opcodes = {};
|
||||||
|
Object.entries(json.opcodes).forEach(([opcode, data]) => {
|
||||||
|
this.opcodes.opcodes[opcode] = Object.assign(new StatView(), data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.frameTable.render();
|
||||||
|
this.opcodeTable.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function () {
|
/**
|
||||||
|
* Run the benchmark with given parameters in the location's hash field or
|
||||||
|
* using defaults.
|
||||||
|
*/
|
||||||
|
const runBenchmark = function () {
|
||||||
// Lots of global variables to make debugging easier
|
// Lots of global variables to make debugging easier
|
||||||
// Instantiate the VM.
|
// Instantiate the VM.
|
||||||
const vm = new window.VirtualMachine();
|
const vm = new window.VirtualMachine();
|
||||||
|
@ -388,10 +457,21 @@ window.onload = function () {
|
||||||
.innerText = progress.complete;
|
.innerText = progress.complete;
|
||||||
}).on(storage);
|
}).on(storage);
|
||||||
|
|
||||||
|
let warmUpTime = 4000;
|
||||||
|
let maxRecordedTime = 6000;
|
||||||
|
|
||||||
|
if (location.hash) {
|
||||||
|
const split = location.hash.substring(1).split(',');
|
||||||
|
if (split[1] && split[1].length > 0) {
|
||||||
|
warmUpTime = Number(split[1]);
|
||||||
|
}
|
||||||
|
maxRecordedTime = Number(split[2] || '0') || 6000;
|
||||||
|
}
|
||||||
|
|
||||||
new ProfilerRun({
|
new ProfilerRun({
|
||||||
vm,
|
vm,
|
||||||
warmUpTime: 4000,
|
warmUpTime,
|
||||||
maxRecordedTime: 6000
|
maxRecordedTime
|
||||||
}).run();
|
}).run();
|
||||||
|
|
||||||
// Instantiate the renderer and connect it to the VM.
|
// Instantiate the renderer and connect it to the VM.
|
||||||
|
@ -466,3 +546,29 @@ window.onload = function () {
|
||||||
// Run threads
|
// Run threads
|
||||||
vm.start();
|
vm.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render previously run benchmark data.
|
||||||
|
* @param {object} json data from a previous benchmark run.
|
||||||
|
*/
|
||||||
|
const renderBenchmarkData = function (json) {
|
||||||
|
const vm = new window.VirtualMachine();
|
||||||
|
new ProfilerRun({vm}).render(json);
|
||||||
|
setShareLink(json);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
if (location.hash.substring(1).startsWith('view')) {
|
||||||
|
document.body.className = 'render';
|
||||||
|
const data = location.hash.substring(6);
|
||||||
|
const frozen = atob(data);
|
||||||
|
const json = JSON.parse(frozen);
|
||||||
|
renderBenchmarkData(json);
|
||||||
|
} else {
|
||||||
|
runBenchmark();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onhashchange = function () {
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
|
|
@ -8,19 +8,21 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Scratch VM Benchmark</h2>
|
<h2>Scratch VM Benchmark</h2>
|
||||||
<p>
|
<p class="description">
|
||||||
Welcome to the scratch-vm benchmark. This tool helps you profile a scratch
|
Welcome to the scratch-vm benchmark. This tool helps you profile a scratch
|
||||||
project. When you load the page, it:
|
project. When you load the page, it:
|
||||||
<ol>
|
<ol class="description">
|
||||||
<li>loads the default project and enables turbo mode
|
<li>loads the default project and enables turbo mode
|
||||||
<li>runs the project for 4 seconds to warm up
|
<li>runs the project for 4 seconds to warm up
|
||||||
<li>profiles for 6 seconds
|
<li>profiles for 6 seconds
|
||||||
<li>stops and reports
|
<li>stops and reports
|
||||||
</ol>
|
</ol>
|
||||||
</p>
|
</p>
|
||||||
<input type="text" value="119615668">
|
<div class="run-form">
|
||||||
<button class="run">run</button>
|
<input type="text" value="119615668">
|
||||||
<p>
|
<button class="run">run</button>
|
||||||
|
</div>
|
||||||
|
<p class="run-push">
|
||||||
<i>Try a different project, like `130041250`</i>
|
<i>Try a different project, like `130041250`</i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -43,6 +45,14 @@
|
||||||
<label>Blocks executed:</label>
|
<label>Blocks executed:</label>
|
||||||
<span class="profile-count-value profile-count-blocks-executed">...</span>
|
<span class="profile-count-value profile-count-blocks-executed">...</span>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="share"><div class="profile-count">
|
||||||
|
<label>Share this report</label>
|
||||||
|
</div></a>
|
||||||
|
<a class="share" target="_parent">
|
||||||
|
<div class="profile-count">
|
||||||
|
<label>Run the full suite</label>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="profile-tables">
|
<div class="profile-tables">
|
||||||
|
|
78
src/playground/suite.css
Normal file
78
src/playground/suite.css
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-controls {
|
||||||
|
position: absolute;
|
||||||
|
width: 30em;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 30em;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
padding: 1em;
|
||||||
|
padding-top: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bench-frame-owner {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 30em);
|
||||||
|
height: 100%;
|
||||||
|
left: 30em;
|
||||||
|
right: 100%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-view {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-project {
|
||||||
|
display: inline-block;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-warm-up {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixture-recording {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-view.resume {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-status {
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compare-file {
|
||||||
|
cursor: pointer;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 0;
|
||||||
|
}
|
25
src/playground/suite.html
Normal file
25
src/playground/suite.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!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 id="bench_frame" name="bench_frame" src="" class="bench-frame"
|
||||||
|
width="100%" height="100%"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BenchRunner -->
|
||||||
|
<script src="./suite.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
600
src/playground/suite.js
Normal file
600
src/playground/suite.js
Normal file
|
@ -0,0 +1,600 @@
|
||||||
|
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 = `<a href="${compareUrl}" target="_blank">
|
||||||
|
<div class="result-status">
|
||||||
|
<div>${compareStepsPerSecond}</div>
|
||||||
|
<div>${compareBlocksPerSecond}</div>
|
||||||
|
</div>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
this.dom.innerHTML = `
|
||||||
|
<div class="fixture-project">
|
||||||
|
<a href="${url}" target="bench_frame"
|
||||||
|
>${newResult.fixture.projectId}</a>
|
||||||
|
</div>
|
||||||
|
<div class="result-status">
|
||||||
|
<div>${stepThreadsInnerFrame ? `steps/s` : ''}</div>
|
||||||
|
<div>${blockFunctionFrame ? `blocks/s` : statusName}</div>
|
||||||
|
</div>
|
||||||
|
<a href="${stepThreadsInnerFrame ? url : ''}" target="_blank">
|
||||||
|
<div class="result-status">
|
||||||
|
<div>${stepThreadsInnerFrame ? `${stepsPerSecond}` : ''}</div>
|
||||||
|
<div>${blockFunctionFrame ? `${blocksPerSecond}` : ''}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
${compareHTML}
|
||||||
|
<div class="">
|
||||||
|
Run for ${newResult.fixture.recordingTime / 1000} seconds after
|
||||||
|
${newResult.fixture.warmUpTime / 1000} seconds
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.result = newResult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BenchSuiteResultView {
|
||||||
|
constructor ({runner}) {
|
||||||
|
const {suite, util} = runner;
|
||||||
|
|
||||||
|
this.runner = runner;
|
||||||
|
this.suite = suite;
|
||||||
|
this.views = {};
|
||||||
|
this.dom = document.createElement('div');
|
||||||
|
|
||||||
|
for (const fixture of suite.fixtures) {
|
||||||
|
this.views[fixture.id] = new BenchResultView({
|
||||||
|
result: new BenchResult({fixture}),
|
||||||
|
benchUtil: util
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.on('result', result => {
|
||||||
|
this.views[result.fixture.id].update(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
this.dom.innerHTML = `<div class="legend">
|
||||||
|
<span>Project ID</span>
|
||||||
|
<div class="result-status">
|
||||||
|
<div>steps per second</div>
|
||||||
|
<div>blocks per second</div>
|
||||||
|
</div>
|
||||||
|
<div>Description</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="legend">
|
||||||
|
<span> </span>
|
||||||
|
<div class="result-status">
|
||||||
|
<div><a href="#" onclick="window.download(this)">
|
||||||
|
Save Reports
|
||||||
|
</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="result-status">
|
||||||
|
<a href="#"><label for="compare-file">Compare Reports<input
|
||||||
|
id="compare-file" type="file"
|
||||||
|
class="compare-file"
|
||||||
|
accept="application/json"
|
||||||
|
onchange="window.upload(this)" />
|
||||||
|
</label></a>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
for (const fixture of this.suite.fixtures) {
|
||||||
|
this.dom.appendChild(this.views[fixture.id].render().dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let suite;
|
||||||
|
let suiteView;
|
||||||
|
|
||||||
|
window.upload = function (_this) {
|
||||||
|
if (!_this.files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
const report = JSON.parse(reader.result);
|
||||||
|
Object.values(suiteView.views)
|
||||||
|
.forEach(view => {
|
||||||
|
const sameFixture = report.results.find(result => (
|
||||||
|
result.fixture.projectId ===
|
||||||
|
view.result.fixture.projectId &&
|
||||||
|
result.fixture.warmUpTime ===
|
||||||
|
view.result.fixture.warmUpTime &&
|
||||||
|
result.fixture.recordingTime ===
|
||||||
|
view.result.fixture.recordingTime
|
||||||
|
));
|
||||||
|
|
||||||
|
if (sameFixture) {
|
||||||
|
if (
|
||||||
|
view.result && view.result.frames &&
|
||||||
|
view.result.frames.length > 0
|
||||||
|
) {
|
||||||
|
view.render(view.result, sameFixture);
|
||||||
|
} else {
|
||||||
|
view.compare = sameFixture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsText(_this.files[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.download = function (_this) {
|
||||||
|
const blob = new Blob([JSON.stringify({
|
||||||
|
meta: {
|
||||||
|
source: 'Scratch VM Benchmark Suite',
|
||||||
|
version: 1
|
||||||
|
},
|
||||||
|
results: Object.values(suiteView.views)
|
||||||
|
.map(view => view.result)
|
||||||
|
.filter(view => view.status === BENCH_STATUS.COMPLETE)
|
||||||
|
})], {type: 'application/json'});
|
||||||
|
|
||||||
|
_this.download = 'scratch-vm-benchmark.json';
|
||||||
|
_this.href = URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
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
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 187694931,
|
||||||
|
warmUpTime: 0,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
suite.add(new BenchFixture({
|
||||||
|
projectId: 187694931,
|
||||||
|
warmUpTime: 5000,
|
||||||
|
recordingTime: 5000
|
||||||
|
}));
|
||||||
|
|
||||||
|
const frame = document.getElementsByTagName('iframe')[0];
|
||||||
|
const runner = new BenchRunner({frame, suite});
|
||||||
|
const resultsView = suiteView = new BenchSuiteResultView({runner}).render();
|
||||||
|
|
||||||
|
document.getElementsByClassName('suite-results')[0]
|
||||||
|
.appendChild(resultsView.dom);
|
||||||
|
|
||||||
|
runner.run();
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue