Skip to content

Commit fc28b62

Browse files
committed
Import all runs from a benchmark JSON file
When importing a file which is the result of a benchmark run with multiple iterations (which contains a `debugOutput` key), store all the runs in `RunData`. Add static methods to `RunData` to handle both types of JSON, and move the data migration code there. The score computation already correctly computed the arithmetic mean of the runs. The code already correctly built the results table for multiple runs.
1 parent ef533da commit fc28b62

File tree

4 files changed

+179
-82
lines changed

4 files changed

+179
-82
lines changed

MotionMark/resources/debug-runner/debug-runner.js

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -593,13 +593,16 @@ class DebugBenchmarkController extends BenchmarkController {
593593
var reader = new FileReader();
594594
reader.filename = file.name;
595595
reader.onload = (e) => {
596-
var run = JSON.parse(e.target.result);
597-
if (run.debugOutput instanceof Array)
598-
run = run.debugOutput[0];
596+
const data = JSON.parse(e.target.result);
597+
598+
let results;
599+
if (data['debugOutput'] instanceof Array)
600+
results = RunData.resultsDataFromBenchmarkRunnerData(data['debugOutput']);
601+
else
602+
results = RunData.resultsDataFromSingleRunData(data);
599603

600-
this.migrateImportedData(run);
601604
this.ensureRunnerClient([ ], { });
602-
this.runnerClient.scoreCalculator = new ScoreCalculator(new RunData(run.version, run.options, run.data));
605+
this.runnerClient.scoreCalculator = new ScoreCalculator(results);
603606
this.showResults();
604607
};
605608

@@ -608,22 +611,6 @@ class DebugBenchmarkController extends BenchmarkController {
608611
}, false);
609612
}
610613

611-
migrateImportedData(runData)
612-
{
613-
if (!("version" in runData))
614-
runData.version = "1.0";
615-
616-
if (!("frame-rate" in runData.options)) {
617-
runData.options["frame-rate"] = 60;
618-
console.log("No frame-rate data; assuming 60fps")
619-
}
620-
621-
if (!("system-frame-rate" in runData.options)) {
622-
runData.options["system-frame-rate"] = 60;
623-
console.log("No system-frame-rate data; assuming 60fps")
624-
}
625-
}
626-
627614
frameRateDeterminationComplete(targetFrameRate)
628615
{
629616
let frameRateLabelContent = Strings.text.usingFrameRate.replace("%s", targetFrameRate);
@@ -704,7 +691,7 @@ class DebugBenchmarkController extends BenchmarkController {
704691
this.addedKeyEvent = true;
705692
}
706693

707-
var scoreCalculator = this.runnerClient.scoreCalculator;
694+
const scoreCalculator = this.runnerClient.scoreCalculator;
708695
if (scoreCalculator.options["controller"] == "ramp")
709696
Headers.details[3].disabled = true;
710697
else {
@@ -717,10 +704,10 @@ class DebugBenchmarkController extends BenchmarkController {
717704
document.body.classList.add(scoreCalculator.options[Strings.json.configuration]);
718705
}
719706

720-
var score = scoreCalculator.score;
721-
var confidence = ((scoreCalculator.scoreLowerBound / score - 1) * 100).toFixed(2) +
707+
const score = scoreCalculator.score;
708+
const confidence = ((scoreCalculator.scoreLowerBound / score - 1) * 100).toFixed(2) +
722709
"% / +" + ((scoreCalculator.scoreUpperBound / score - 1) * 100).toFixed(2) + "%";
723-
var fps = scoreCalculator._systemFrameRate;
710+
const fps = scoreCalculator._systemFrameRate;
724711
sectionsManager.setSectionVersion("results", scoreCalculator.version);
725712
sectionsManager.setSectionScore("results", score.toFixed(2), confidence, fps);
726713
sectionsManager.populateTable("results-header", Headers.testName, scoreCalculator);

MotionMark/resources/runner/motionmark.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class BenchmarkController {
140140
try {
141141
targetFrameRate = await this.determineFrameRate(progressElement);
142142
} catch (e) {
143-
console.log('Frame rate detection failed ' + e);
143+
console.error('Frame rate detection failed ' + e);
144144
}
145145
this.frameRateDeterminationComplete(targetFrameRate);
146146
}

MotionMark/resources/runner/results.js

Lines changed: 104 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,56 @@ class RunData {
3030
this.options = options;
3131
this.runs = runs;
3232
}
33+
34+
static resultsDataFromSingleRunData(singleRunData)
35+
{
36+
RunData.#migrateImportedData(singleRunData);
37+
38+
if (!singleRunData.data instanceof Array) {
39+
console.error('Imported singleRunData.data is not an array. Bailing');
40+
return null;
41+
}
42+
43+
return new RunData(singleRunData.version, singleRunData.options, singleRunData.data);
44+
}
45+
46+
static resultsDataFromBenchmarkRunnerData(benchmarkData)
47+
{
48+
if (!benchmarkData instanceof Array) {
49+
console.log('Imported benchmarkData is not an array. Bailing');
50+
return null;
51+
}
52+
53+
const runData = [];
54+
for (const run of benchmarkData) {
55+
RunData.#migrateImportedData(run);
56+
if (run.data.length !== 1) {
57+
console.error('Imported benchmarkData has a "data" array with an unexpected number of items. Bailing');
58+
return null;
59+
}
60+
61+
runData.push(run.data[0]);
62+
}
63+
64+
// Version and options data should be these same for each run. Use the first run's information.
65+
return new RunData(benchmarkData[0].version, benchmarkData[0].options, runData);
66+
}
67+
68+
static #migrateImportedData(options)
69+
{
70+
if (!("version" in options))
71+
options.version = "1.0";
72+
73+
if (!("frame-rate" in options)) {
74+
options.options["frame-rate"] = 60;
75+
console.log("No frame-rate data; assuming 60fps")
76+
}
77+
78+
if (!("system-frame-rate" in options)) {
79+
options.options["system-frame-rate"] = 60;
80+
console.log("No system-frame-rate data; assuming 60fps")
81+
}
82+
}
3383
}
3484

3585
class ScoreCalculator {
@@ -109,36 +159,37 @@ class ScoreCalculator {
109159

110160
calculateScore(data)
111161
{
112-
var result = {};
162+
const result = {};
113163
data[Strings.json.result] = result;
114-
var samples = data[Strings.json.samples];
164+
const samples = data[Strings.json.samples];
115165
const desiredFrameLength = 1000 / this._targetFrameRate;
166+
const complexityKey = Strings.json.complexity;
116167

117168
function findRegression(series, profile) {
118-
var minIndex = Math.round(.025 * series.length);
119-
var maxIndex = Math.round(.975 * (series.length - 1));
120-
var minComplexity = series.getFieldInDatum(minIndex, Strings.json.complexity);
121-
var maxComplexity = series.getFieldInDatum(maxIndex, Strings.json.complexity);
169+
const minIndex = Math.round(.025 * series.length);
170+
const maxIndex = Math.round(.975 * (series.length - 1));
171+
const minComplexity = series.getFieldInDatum(minIndex, complexityKey);
172+
const maxComplexity = series.getFieldInDatum(maxIndex, complexityKey);
122173

123174
if (Math.abs(maxComplexity - minComplexity) < 20 && maxIndex - minIndex < 20) {
124175
minIndex = 0;
125176
maxIndex = series.length - 1;
126-
minComplexity = series.getFieldInDatum(minIndex, Strings.json.complexity);
127-
maxComplexity = series.getFieldInDatum(maxIndex, Strings.json.complexity);
177+
minComplexity = series.getFieldInDatum(minIndex, complexityKey);
178+
maxComplexity = series.getFieldInDatum(maxIndex, complexityKey);
128179
}
129180

130-
var frameTypeIndex = series.fieldMap[Strings.json.frameType];
131-
var complexityIndex = series.fieldMap[Strings.json.complexity];
132-
var frameLengthIndex = series.fieldMap[Strings.json.frameLength];
133-
var regressionOptions = { desiredFrameLength: desiredFrameLength };
181+
const frameTypeIndex = series.fieldMap[Strings.json.frameType];
182+
const complexityIndex = series.fieldMap[complexityKey];
183+
const frameLengthIndex = series.fieldMap[Strings.json.frameLength];
184+
const regressionOptions = { desiredFrameLength: desiredFrameLength };
134185
if (profile)
135186
regressionOptions.preferredProfile = profile;
136187

137-
var regressionSamples = series.slice(minIndex, maxIndex + 1);
138-
var animationSamples = regressionSamples.data.filter((sample) => sample[frameTypeIndex] == Strings.json.animationFrameType);
139-
var regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex] ]);
188+
const regressionSamples = series.slice(minIndex, maxIndex + 1);
189+
const animationSamples = regressionSamples.data.filter((sample) => sample[frameTypeIndex] == Strings.json.animationFrameType);
190+
const regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex] ]);
140191

141-
var regression = new Regression(regressionData, minIndex, maxIndex, regressionOptions);
192+
const regression = new Regression(regressionData, minIndex, maxIndex, regressionOptions);
142193
return {
143194
minComplexity: minComplexity,
144195
maxComplexity: maxComplexity,
@@ -148,14 +199,14 @@ class ScoreCalculator {
148199
}
149200

150201
// Convert these samples into SampleData objects if needed
151-
[Strings.json.complexity, Strings.json.controller].forEach(function(seriesName) {
152-
var series = samples[seriesName];
202+
[complexityKey, Strings.json.controller].forEach(function(seriesName) {
203+
const series = samples[seriesName];
153204
if (series && !(series instanceof SampleData))
154205
samples[seriesName] = new SampleData(series.fieldMap, series.data);
155206
});
156207

157-
var isRampController = this._runData.options[Strings.json.controller] == "ramp";
158-
var predominantProfile = "";
208+
const isRampController = this._runData.options[Strings.json.controller] == "ramp";
209+
let predominantProfile = "";
159210
if (isRampController) {
160211
var profiles = {};
161212
data[Strings.json.controller].forEach(function(regression) {
@@ -174,44 +225,44 @@ class ScoreCalculator {
174225
}
175226
}
176227

177-
var regressionResult = findRegression(samples[Strings.json.complexity], predominantProfile);
178-
var calculation = regressionResult.regression;
179-
result[Strings.json.complexity] = {};
180-
result[Strings.json.complexity][Strings.json.regressions.segment1] = [
228+
const regressionResult = findRegression(samples[complexityKey], predominantProfile);
229+
const calculation = regressionResult.regression;
230+
result[complexityKey] = {};
231+
result[complexityKey][Strings.json.regressions.segment1] = [
181232
[regressionResult.minComplexity, calculation.s1 + calculation.t1 * regressionResult.minComplexity],
182233
[calculation.complexity, calculation.s1 + calculation.t1 * calculation.complexity]
183234
];
184-
result[Strings.json.complexity][Strings.json.regressions.segment2] = [
235+
result[complexityKey][Strings.json.regressions.segment2] = [
185236
[calculation.complexity, calculation.s2 + calculation.t2 * calculation.complexity],
186237
[regressionResult.maxComplexity, calculation.s2 + calculation.t2 * regressionResult.maxComplexity]
187238
];
188-
result[Strings.json.complexity][Strings.json.complexity] = calculation.complexity;
189-
result[Strings.json.complexity][Strings.json.measurements.stdev] = Math.sqrt(calculation.error / samples[Strings.json.complexity].length);
239+
result[complexityKey][complexityKey] = calculation.complexity;
240+
result[complexityKey][Strings.json.measurements.stdev] = Math.sqrt(calculation.error / samples[complexityKey].length);
190241

191242
result[Strings.json.fps] = data.targetFPS;
192243

193244
if (isRampController) {
194-
var timeComplexity = new Experiment;
245+
const timeComplexity = new Experiment;
195246
data[Strings.json.controller].forEach(function(regression) {
196-
timeComplexity.sample(regression[Strings.json.complexity]);
247+
timeComplexity.sample(regression[complexityKey]);
197248
});
198249

199-
var experimentResult = {};
250+
const experimentResult = {};
200251
result[Strings.json.controller] = experimentResult;
201252
experimentResult[Strings.json.score] = timeComplexity.mean();
202253
experimentResult[Strings.json.measurements.average] = timeComplexity.mean();
203254
experimentResult[Strings.json.measurements.stdev] = timeComplexity.standardDeviation();
204255
experimentResult[Strings.json.measurements.percent] = timeComplexity.percentage();
205256

206257
const bootstrapIterations = this._runData.options[Strings.json.bootstrapIterations];
207-
var bootstrapResult = Regression.bootstrap(regressionResult.samples.data, bootstrapIterations, function(resampleData) {
208-
var complexityIndex = regressionResult.samples.fieldMap[Strings.json.complexity];
258+
const bootstrapResult = Regression.bootstrap(regressionResult.samples.data, bootstrapIterations, function(resampleData) {
259+
const complexityIndex = regressionResult.samples.fieldMap[complexityKey];
209260
resampleData.sort(function(a, b) {
210261
return a[complexityIndex] - b[complexityIndex];
211262
});
212263

213-
var resample = new SampleData(regressionResult.samples.fieldMap, resampleData);
214-
var bootstrapRegressionResult = findRegression(resample, predominantProfile);
264+
const resample = new SampleData(regressionResult.samples.fieldMap, resampleData);
265+
const bootstrapRegressionResult = findRegression(resample, predominantProfile);
215266
if (bootstrapRegressionResult.regression.t2 < 0) {
216267
// A positive slope means the frame rate decreased with increased complexity (which is the expected
217268
// benavior). OTOH, a negative slope means the framerate increased as the complexity increased. This
@@ -224,24 +275,24 @@ class ScoreCalculator {
224275
return bootstrapRegressionResult.regression.complexity;
225276
}, .8);
226277

227-
result[Strings.json.complexity][Strings.json.bootstrap] = bootstrapResult;
278+
result[complexityKey][Strings.json.bootstrap] = bootstrapResult;
228279
result[Strings.json.score] = bootstrapResult.median;
229280
result[Strings.json.scoreLowerBound] = bootstrapResult.confidenceLow;
230281
result[Strings.json.scoreUpperBound] = bootstrapResult.confidenceHigh;
231282
} else {
232-
var marks = data[Strings.json.marks];
233-
var samplingStartIndex = 0, samplingEndIndex = -1;
283+
const marks = data[Strings.json.marks];
284+
let samplingStartIndex = 0, samplingEndIndex = -1;
234285
if (Strings.json.samplingStartTimeOffset in marks)
235286
samplingStartIndex = marks[Strings.json.samplingStartTimeOffset].index;
236287
if (Strings.json.samplingEndTimeOffset in marks)
237288
samplingEndIndex = marks[Strings.json.samplingEndTimeOffset].index;
238289

239-
var averageComplexity = new Experiment;
240-
var averageFrameLength = new Experiment;
241-
var controllerSamples = samples[Strings.json.controller];
290+
const averageComplexity = new Experiment;
291+
const averageFrameLength = new Experiment;
292+
const controllerSamples = samples[Strings.json.controller];
242293
controllerSamples.forEach(function (sample, i) {
243294
if (i >= samplingStartIndex && (samplingEndIndex == -1 || i < samplingEndIndex)) {
244-
averageComplexity.sample(controllerSamples.getFieldInDatum(sample, Strings.json.complexity));
295+
averageComplexity.sample(controllerSamples.getFieldInDatum(sample, complexityKey));
245296
var smoothedFrameLength = controllerSamples.getFieldInDatum(sample, Strings.json.smoothedFrameLength);
246297
if (smoothedFrameLength && smoothedFrameLength != -1)
247298
averageFrameLength.sample(smoothedFrameLength);
@@ -346,14 +397,14 @@ class ResultsTable {
346397

347398
_addHeader()
348399
{
349-
var thead = Utilities.createElement("thead", {}, this.element);
350-
var row = Utilities.createElement("tr", {}, thead);
400+
const thead = Utilities.createElement("thead", {}, this.element);
401+
const row = Utilities.createElement("tr", {}, thead);
351402

352403
this._headers.forEach(function (header) {
353404
if (header.disabled)
354405
return;
355406

356-
var th = Utilities.createElement("th", {}, row);
407+
const th = Utilities.createElement("th", {}, row);
357408
if (header.title != Strings.text.graph)
358409
th.innerHTML = header.title;
359410
if (header.children)
@@ -368,18 +419,18 @@ class ResultsTable {
368419

369420
_addEmptyRow()
370421
{
371-
var row = Utilities.createElement("tr", {}, this.tbody);
422+
const row = Utilities.createElement("tr", {}, this.tbody);
372423
this._flattenedHeaders.forEach(function (header) {
373424
return Utilities.createElement("td", { class: "suites-separator" }, row);
374425
});
375426
}
376427

377428
_addTest(testName, testResult, options)
378429
{
379-
var row = Utilities.createElement("tr", {}, this.tbody);
430+
const row = Utilities.createElement("tr", {}, this.tbody);
380431

381432
this._flattenedHeaders.forEach(function (header) {
382-
var td = Utilities.createElement("td", {}, row);
433+
const td = Utilities.createElement("td", {}, row);
383434
if (header.text == Strings.text.testName) {
384435
td.textContent = testName;
385436
} else if (typeof header.text == "string") {
@@ -394,12 +445,12 @@ class ResultsTable {
394445

395446
_addIteration(iterationResult, iterationData, options)
396447
{
397-
var testsResults = iterationResult[Strings.json.results.tests];
398-
for (var suiteName in testsResults) {
448+
const testsResults = iterationResult[Strings.json.results.tests];
449+
for (const suiteName in testsResults) {
399450
this._addEmptyRow();
400-
var suiteResult = testsResults[suiteName];
401-
var suiteData = iterationData[suiteName];
402-
for (var testName in suiteResult)
451+
const suiteResult = testsResults[suiteName];
452+
const suiteData = iterationData[suiteName];
453+
for (let testName in suiteResult)
403454
this._addTest(testName, suiteResult[testName], options, suiteData[testName]);
404455
}
405456
}
@@ -410,7 +461,7 @@ class ResultsTable {
410461
this._addHeader();
411462
this._addBody();
412463

413-
var iterationsResults = scoreCalculator.results;
464+
const iterationsResults = scoreCalculator.results;
414465
iterationsResults.forEach(function(iterationResult, index) {
415466
this._addIteration(iterationResult, scoreCalculator.data[index], scoreCalculator.options);
416467
}, this);

0 commit comments

Comments
 (0)