From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../resources/debug-runner/animometer.css | 774 +++++++++++++++++++++ .../resources/debug-runner/animometer.js | 703 +++++++++++++++++++ .../MotionMark/resources/debug-runner/graph.js | 615 ++++++++++++++++ .../MotionMark/resources/debug-runner/tests.js | 345 +++++++++ .../MotionMark/resources/extensions.js | 670 ++++++++++++++++++ .../MotionMark/resources/runner/animometer.css | 520 ++++++++++++++ .../MotionMark/resources/runner/animometer.js | 626 +++++++++++++++++ .../resources/runner/benchmark-runner.js | 179 +++++ .../MotionMark/resources/runner/crystal.svg | 92 +++ .../MotionMark/resources/runner/lines.svg | 26 + .../MotionMark/resources/runner/logo.svg | 26 + .../MotionMark/resources/runner/tests.js | 81 +++ .../MotionMark/resources/statistics.js | 397 +++++++++++ .../MotionMark/resources/strings.js | 51 ++ 14 files changed, 5105 insertions(+) create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.css create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/graph.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/tests.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/extensions.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.css create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/benchmark-runner.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/crystal.svg create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/lines.svg create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/logo.svg create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/runner/tests.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/statistics.js create mode 100644 third_party/webkit/PerformanceTests/MotionMark/resources/strings.js (limited to 'third_party/webkit/PerformanceTests/MotionMark/resources') diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.css b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.css new file mode 100644 index 0000000000..7fbf31ee7d --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.css @@ -0,0 +1,774 @@ +body { + font-size: initial; +} + +body.showing-intro, +body.showing-results, +body.showing-test-graph { + background-color: rgb(96, 96, 96); + background-image: initial; + background-repeat: initial; + background-size: initial; + animation: initial; + will-change: initial; + + color: rgb(235, 235, 235); +} + +section .body { + margin-left: 0; + max-width: initial; + transform: none; +} + +h1 { + font-size: 3em; + margin: 1.5em 0 .5em; + text-align: center; +} + +button { + transform: none !important; + min-width: initial; + transition: none; + animation: none; + will-change: initial; + + display: block; + font-size: 1.5em; + border: 2px solid rgb(235, 235, 235); + color: rgb(235, 235, 235); + background: transparent; + border-radius: 10px; + padding: .5em 2em; +} + +button:hover { + background-color: rgba(255, 255, 255, .1); + cursor: pointer; +} + +button:active { + color: inherit; + background-color: rgba(255, 255, 255, .2); +} + +button:disabled { + border-color: rgba(235, 235, 235, .5); + color: rgba(235, 235, 235, .5); +} + +@media screen and (max-device-width: 414px), + screen and (max-device-height: 414px) and (orientation: landscape) { + h1 { + font-size: 2.5em; + } + + section { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 0 5px; + } +} + +/* -------------------------------------------------------------------------- */ +/* Tree */ +/* -------------------------------------------------------------------------- */ + +.tree { + padding: 0; + list-style-type: none; +} + +.tree .expand-button { + position: absolute; + clip: rect(0, 0, 0, 0); +} + +.tree .expand-button ~ ul { + display: none; +} + +.tree .expand-button:checked ~ ul { + display: block; +} + +.tree ul { + list-style-type:none; +} + +.tree li { + position: relative; + padding: 0 0 1em 1em; +} + +.tree ul li { + list-style:none; + padding: 1em 0 0 0em; +} + +.tree > li:last-child { + padding-bottom: 0; +} + +.tree-label { + position: relative; + display: inline-block; +} + +label.tree-label { + cursor: pointer; +} + +.tree > li > label.tree-label:before { + position: relative; + z-index: 1; + float: left; + margin: 0 0 0 -2em; + width: 1em; + height: 1em; + content: '\25BA'; + text-align: center; + line-height: 2.5em; + font-size: .5em; +} + +.tree > li > :checked ~ label.tree-label:before { + content: '\25BC'; +} + +.tree .link { + cursor: pointer; + color: #999; + font-style: italic; + margin-left: 2em; +} + +@media screen and (max-device-width: 414px), + screen and (max-device-height: 414px) and (orientation: landscape) { + .tree { + padding-left: 1em; + } + .tree > li > label.tree-label:before { + font-size: 1em; + margin-left: -1.75em; + line-height: 1em; + } +} + +/* -------------------------------------------------------------------------- */ +/* Intro Section */ +/* -------------------------------------------------------------------------- */ + +#intro { + padding: 0; + opacity: initial; + transition: none; +} + +#intro .body > p { + padding: 1em 0; + margin: 0 auto; + text-align: center; +} + +#intro .start-benchmark { + padding: 10vh 0; + text-align: center; +} + +#intro .start-benchmark p { + color: hsl(11, 72%, 50%); + margin-bottom: 1em; + -apple-trailing-word: -apple-partially-balanced; +} + +#intro .start-benchmark button { + margin: 0 auto; +} + + +@media screen and (max-device-width: 414px), + screen and (max-device-height: 414px) and (orientation: landscape) { + #intro.selected { + display: flex; + align-items: center; + justify-content: flex-start; + flex-flow: column; + } + + #intro p { + padding-left: 20px; + padding-right: 20px; + font-size: 1.5em; + } +} + +#intro h2 { + font-size: 1.2em; +} + +#intro .body > div:first-of-type { + width: 100%; + margin: 2em 0 0; + flex-direction: row; + display: flex; + align-content: flex-start; +} + +#suites { + padding-left: 15vw; + padding-right: 3em; + flex: 1 1 30%; +} + +#options { + flex: 10 1 auto; +} + +#intro input[type="number"] { + width: 50px; +} + +#suites input[type="number"] { + display: none; + float: right; +} + +#suites input[type="number"].selected { + display: inline; + margin: 0; +} + +#suites ul ul { + font-size: .8em; + margin: 0; + padding: 0 0 0 1em; +} + +#suites > div { + margin: 3em 0; +} + +#drop-target { + font-size: 1em; + border-radius: 10px; + padding: .5em 2em; + border: 2px solid rgb(235, 235, 235); + color: rgb(235, 235, 235); +} + +#drop-target:hover { + background-color: rgba(255, 255, 255, .1); + cursor: pointer; +} + +#options ul { + margin: 0; + padding: 0; + list-style: none; +} + +#options h3 { + font-size: 1em; + font-weight: inherit; + margin: 0 0 .3em 0; + padding: 0; +} + +#options > form > ul > li { + padding: 0 0 1em 0; +} + +#options ul ul { + padding: 0; +} + +#options li { + padding: .1em 0; +} + +#intro > p { + padding: 0 5px 1em; + font-size: 1em; +} + +#intro .start-benchmark { + padding: 0 0 10vh; + margin-top: 0; +} + +#intro .start-benchmark p { + color: hsl(11, 100%, 66%); +} + +@media screen and (max-device-width: 414px), + screen and (max-device-height: 414px) and (orientation: landscape) { + #intro .body > div:first-of-type { + flex-direction: column; + } + + #suites, + #options { + padding: 0 5px; + margin: 0; + flex: 0 0 auto; + } +} + +/* -------------------------------------------------------------------------- */ +/* Running Section */ +/* -------------------------------------------------------------------------- */ + +#running-test { + display: flex; + align-items: center; + justify-content: center; +} + +#progress { + display: none; +} + +.display-progress-bar #progress { + display: block; + position: fixed; + top: 0; + left: 0; + height: 6px; + width: 100%; + background-color: rgb(128, 128, 128); +} + +.display-progress-bar #progress-completed { + position: absolute; + top: 0; + left: 0; + height: 6px; + width: 0; + background-color: rgb(235, 96, 32); +} + +body.showing-test-container.tiles-big { + overflow: hidden; +} + +body.showing-test-container.tiles-classic { + width: 3000px; + height: 3000px; + overflow: scroll; +} + +/* -------------------------------------------------------------------------- */ +/* Results Section */ +/* -------------------------------------------------------------------------- */ + +#results { + text-align: center; +} + +#results h1, #test-graph h1 { + font-size: 2em; +} + +#results button.small-button { + border: 1px solid rgba(235, 235, 235, .9); + color: rgba(235, 235, 235, .9); + border-radius: 2px; + padding: 1px 4px; + margin: 0 0 0 1em; + font-size: 9px; +} + +#results button.small-button:active { + background-color: rgba(235, 235, 235, .2); + color: inherit; +} + +#results .score, +#test-graph .score { + font-size: 3em; + font-weight: bold; + margin: 0; +} + +#results .confidence, +#test-graph .confidence { + margin-top: 0; + margin-bottom: 1em; + font-size: 1.5em; + font-weight: 400; + text-indent: inherit; + color: inherit; +} + +#results-tables { + direction: rtl; + + display: flex; + + align-items: center; + justify-content: center; + + margin: 3em 0; +} + +#results .table-container > div { + margin-left: 0; +} + +#results #results-score { + float: initial; +} + +#results #results-header { + width: initial; + position: initial; +} + +#results table { + direction: ltr; + min-width: initial; +} + +#results table td.suites-separator { + padding: .5em 0; +} + +#results table tr:nth-child(even) { + background-color: transparent; +} + +#results th { + padding: .5em 0; +} + +#results tr td { + padding: .25em 0; +} + +#results-header td, #results-header th { + text-align: left; +} +#results-header tr td { + padding-right: 1em; +} +#results-score td, #results-score th { + text-align: right; +} +#results .body > button { + margin: 1.5em auto .5em; +} +#results footer { + padding-bottom: 10vh; +} + +@media screen and (max-device-width: 414px), + screen and (max-device-height: 414px) and (orientation: landscape) { + #results.selected { + padding: 0 20px; + } +} + +#overlay { + background: rgba(0, 0, 10, .8); +} + +@supports (-webkit-backdrop-filter: blur(10px)) { + #overlay { + background: rgba(0, 0, 10, .4); + } +} + +#overlay > div div { + border: 1px solid rgb(241, 241, 241); +} + +#overlay button { + margin: 2em auto; + border-color: rgb(241, 241, 241); + color: rgb(241, 241, 241); +} + +#overlay button:hover { + background-color: rgba(255, 255, 255, .1); +} + +#overlay button:active { + background-color: rgba(255, 255, 255, .2); +} + +#results-data .average { + padding-left: 1em; + text-align: right; +} + +#results-data .stdev { + text-align: left; + padding-left: .25em; +} + +#results-data .left { + text-align: left; +} + +#results-data .right { + text-align: right; +} + +#results-data .pad-left { + padding-left: 1em; +} + +#results-data .pad-right { + padding-right: .25em; +} + +#results-data .small { + font-size: .8em; +} + +#results-tables td.noisy-results { + color: rgb(255, 104, 104); +} + +#results-tables div { + direction: ltr; + display: flex; + flex-direction: row; +} + +#test-graph { + flex: 1 0 calc(100% - 40px); +} + +#test-graph h1 { + margin-bottom: 0; +} + +#test-graph header { + position: relative; + text-align:center; +} + +#test-graph header button { + position: absolute; + top: 0; + left: 0; + border-width: 1px; + font-size: 1em; + padding: .5em 1em; +} + +#test-graph .score, #test-graph .confidence { + margin: 0; +} + +#test-graph nav { + position: absolute; + top: 1.5em; + right: 0; + font-size: .7em; + width: 28em; +} + +#test-graph nav ul { + margin: 0 30px 1em 0; + padding: 0; + list-style: none; +} + +#test-graph nav li { + padding: .1em 0; +} + +#test-graph nav li > span { + float: right; +} + +#test-graph nav.hide-data span { + display: none; +} + +/* -------------------------------------------------------------------------- */ +/* Graph Section */ +/* -------------------------------------------------------------------------- */ + +#test-graph-data { + z-index: 1; + font: 10px sans-serif; + color: rgb(235, 235, 235); +} + +#test-graph-data > svg { + fill: none; + overflow: visible; +} + +.axis path, +.axis line { + fill: none; + stroke: #999999; + shape-rendering: crispEdges; +} + +.axis text { + fill: #999; +} + +.yLeft.axis text { + fill: #7add49; +} +.yLeft.axis path, +.yLeft.axis line { + stroke: #7add49; +} +.yRight.axis text { + fill: #fa4925; +} +.yRight.axis path, +.yRight.axis line { + stroke: #fa4925; +} + +.axis.complexity .tick line { + stroke: rgba(200, 200, 200, .6); + stroke-width: 2px; +} + +.axis.complexity .domain, +.axis.complexity text { + stroke: transparent; + fill: transparent; +} + +.marker line { + stroke: #5493D6; +} + +.marker text { + fill: #999; +} + +.mean.complexity line { + stroke: hsla(100, 69%, 58%, .8); + stroke-width: 2px; +} + +.mean.complexity polygon { + fill: hsla(100, 69%, 58%, .05); +} + +.target-fps { + stroke: rgba(250, 73, 37, .4); + stroke-width: 1px; + stroke-dasharray: 10, 10; +} + +.mean.fps line { + stroke: hsla(10, 96%, 56%, .8); + stroke-width: 2px; +} + +.mean.fps polygon { + fill: hsla(10, 96%, 56%, .1); +} + +#regressions line { + stroke: rgba(200, 200, 200, .8); + stroke-width: 2px; +} + +#regressions circle { + fill: rgba(200, 200, 200, .8); +} + +.cursor line { + stroke: rgb(250, 250, 250); + stroke-width: 1px; +} + +.cursor circle, +.cursor text { + fill: rgb(250, 250, 250); +} + +#complexity path { + stroke: rgba(122, 221, 73, .7); + stroke-width: 2px; +} + +#complexity circle { + fill: rgb(122, 221, 73); +} + +#filteredFPS path { + stroke: hsla(30, 96%, 56%, .7); + stroke-width: 1px; +} + +#filteredFPS circle { + fill: hsl(30, 96%, 56%); +} + +#rawFPS path { + stroke: rgba(250, 73, 37, .7); + stroke-width: 1px; +} + +#rawFPS circle { + fill: rgb(250, 73, 37); +} + +#complexity-graph .regression line { + stroke: rgba(253, 253, 253, .8); + stroke-width: 2px; +} + +#complexity-graph .regression circle { + fill: rgba(253, 253, 253, .8); +} + +#complexity-graph .regression polygon { + fill: rgba(253, 253, 253, .05); +} + +#complexity-graph .raw.series line { + stroke: hsla(30, 96%, 56%, .3); + stroke-width: 1px; +} + +#complexity-graph .raw.regression line { + stroke: rgba(30, 96%, 86%, .6); +} + +#complexity-graph .raw.regression polygon { + stroke: rgba(30, 96%, 86%, .05); +} + +#complexity-graph .average.series circle { + fill: hsl(170, 96%, 56%); +} + +#complexity-graph .average.series line { + stroke: hsla(170, 96%, 56%, .2); + stroke-width: 2px; +} + +#complexity-graph .bootstrap .bar { + fill: hsla(260, 56%, 66%, .4); +} + +#complexity-graph .bootstrap .median line { + stroke: hsla(300, 56%, 66%, .8); + stroke-width: 2px; +} + +#complexity-graph .bootstrap .median circle { + fill: hsla(300, 56%, 66%, .8); +} + +#complexity-graph .bootstrap .median polygon { + fill: hsla(300, 56%, 66%, .05); +} diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.js b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.js new file mode 100644 index 0000000000..072cdcc762 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/animometer.js @@ -0,0 +1,703 @@ +ProgressBar = Utilities.createClass( + function(element, ranges) + { + this._element = element; + this._ranges = ranges; + this._currentRange = 0; + this._updateElement(); + }, { + + _updateElement: function() + { + this._element.style.width = (this._currentRange * (100 / this._ranges)) + "%"; + }, + + incrementRange: function() + { + ++this._currentRange; + this._updateElement(); + } +}); + +DeveloperResultsTable = Utilities.createSubclass(ResultsTable, + function(element, headers) + { + ResultsTable.call(this, element, headers); + }, { + + _addGraphButton: function(td, testName, testResult, testData) + { + var button = Utilities.createElement("button", { class: "small-button" }, td); + button.textContent = Strings.text.graph + "…"; + button.testName = testName; + button.testResult = testResult; + button.testData = testData; + + button.addEventListener("click", function(e) { + benchmarkController.showTestGraph(e.target.testName, e.target.testResult, e.target.testData); + }); + }, + + _isNoisyMeasurement: function(jsonExperiment, data, measurement, options) + { + const percentThreshold = 10; + const averageThreshold = 2; + + if (measurement == Strings.json.measurements.percent) + return data[Strings.json.measurements.percent] >= percentThreshold; + + if (jsonExperiment == Strings.json.frameLength && measurement == Strings.json.measurements.average) + return Math.abs(data[Strings.json.measurements.average] - options["frame-rate"]) >= averageThreshold; + + return false; + }, + + _addTest: function(testName, testResult, options, testData) + { + var row = Utilities.createElement("tr", {}, this.element); + + var isNoisy = false; + [Strings.json.complexity, Strings.json.frameLength].forEach(function (experiment) { + var data = testResult[experiment]; + for (var measurement in data) { + if (this._isNoisyMeasurement(experiment, data, measurement, options)) + isNoisy = true; + } + }, this); + + this._flattenedHeaders.forEach(function (header) { + var className = ""; + if (header.className) { + if (typeof header.className == "function") + className = header.className(testResult, options); + else + className = header.className; + } + + if (header.text == Strings.text.testName) { + if (isNoisy) + className += " noisy-results"; + var td = Utilities.createElement("td", { class: className }, row); + td.textContent = testName; + return; + } + + var td = Utilities.createElement("td", { class: className }, row); + if (header.title == Strings.text.graph) { + this._addGraphButton(td, testName, testResult, testData); + } else if (!("text" in header)) { + td.textContent = testResult[header.title]; + } else if (typeof header.text == "string") { + var data = testResult[header.text]; + if (typeof data == "number") + data = data.toFixed(2); + td.textContent = data; + } else + td.textContent = header.text(testResult); + }, this); + } +}); + +Utilities.extendObject(window.benchmarkRunnerClient, { + testsCount: null, + progressBar: null, + + initialize: function(suites, options) + { + this.testsCount = this.iterationCount * suites.reduce(function (count, suite) { return count + suite.tests.length; }, 0); + this.options = options; + }, + + willStartFirstIteration: function() + { + this.results = new ResultsDashboard(this.options); + this.progressBar = new ProgressBar(document.getElementById("progress-completed"), this.testsCount); + }, + + didRunTest: function(testData) + { + this.progressBar.incrementRange(); + this.results.calculateScore(testData); + } +}); + +Utilities.extendObject(window.sectionsManager, { + setSectionHeader: function(sectionIdentifier, title) + { + document.querySelector("#" + sectionIdentifier + " h1").textContent = title; + }, + + populateTable: function(tableIdentifier, headers, dashboard) + { + var table = new DeveloperResultsTable(document.getElementById(tableIdentifier), headers); + table.showIterations(dashboard); + } +}); + +window.optionsManager = +{ + valueForOption: function(name) + { + var formElement = document.forms["benchmark-options"].elements[name]; + if (formElement.type == "checkbox") + return formElement.checked; + else if (formElement.constructor === HTMLCollection) { + for (var i = 0; i < formElement.length; ++i) { + var radio = formElement[i]; + if (radio.checked) + return formElement.value; + } + return null; + } + return formElement.value; + }, + + updateUIFromLocalStorage: function() + { + var formElements = document.forms["benchmark-options"].elements; + + for (var i = 0; i < formElements.length; ++i) { + var formElement = formElements[i]; + var name = formElement.id || formElement.name; + var type = formElement.type; + + var value = localStorage.getItem(name); + if (value === null) + continue; + + if (type == "number") + formElements[name].value = +value; + else if (type == "checkbox") + formElements[name].checked = value == "true"; + else if (type == "radio") + formElements[name].value = value; + } + }, + + updateLocalStorageFromUI: function() + { + var formElements = document.forms["benchmark-options"].elements; + var options = {}; + + for (var i = 0; i < formElements.length; ++i) { + var formElement = formElements[i]; + var name = formElement.id || formElement.name; + var type = formElement.type; + + if (type == "number") + options[name] = +formElement.value; + else if (type == "checkbox") + options[name] = formElement.checked; + else if (type == "radio") { + var radios = formElements[name]; + if (radios.constructor === HTMLCollection) { + for (var j = 0; j < radios.length; ++j) { + var radio = radios[j]; + if (radio.checked) { + options[name] = radio.value; + break; + } + } + } else + options[name] = formElements[name].value; + } + + try { + localStorage.setItem(name, options[name]); + } catch (e) {} + } + + return options; + }, + + updateDisplay: function() + { + document.body.classList.remove("display-minimal"); + document.body.classList.remove("display-progress-bar"); + + document.body.classList.add("display-" + optionsManager.valueForOption("display")); + }, + + updateTiles: function() + { + document.body.classList.remove("tiles-big"); + document.body.classList.remove("tiles-classic"); + + document.body.classList.add("tiles-" + optionsManager.valueForOption("tiles")); + } +}; + +window.suitesManager = +{ + _treeElement: function() + { + return document.querySelector("#suites > .tree"); + }, + + _suitesElements: function() + { + return document.querySelectorAll("#suites > ul > li"); + }, + + _checkboxElement: function(element) + { + return element.querySelector("input[type='checkbox']:not(.expand-button)"); + }, + + _editElement: function(element) + { + return element.querySelector("input[type='number']"); + }, + + _editsElements: function() + { + return document.querySelectorAll("#suites input[type='number']"); + }, + + _localStorageNameForTest: function(suiteName, testName) + { + return suiteName + "/" + testName; + }, + + _updateSuiteCheckboxState: function(suiteCheckbox) + { + var numberEnabledTests = 0; + suiteCheckbox.testsElements.forEach(function(testElement) { + var testCheckbox = this._checkboxElement(testElement); + if (testCheckbox.checked) + ++numberEnabledTests; + }, this); + suiteCheckbox.checked = numberEnabledTests > 0; + suiteCheckbox.indeterminate = numberEnabledTests > 0 && numberEnabledTests < suiteCheckbox.testsElements.length; + }, + + isAtLeastOneTestSelected: function() + { + var suitesElements = this._suitesElements(); + + for (var i = 0; i < suitesElements.length; ++i) { + var suiteElement = suitesElements[i]; + var suiteCheckbox = this._checkboxElement(suiteElement); + + if (suiteCheckbox.checked) + return true; + } + + return false; + }, + + _onChangeSuiteCheckbox: function(event) + { + var selected = event.target.checked; + event.target.testsElements.forEach(function(testElement) { + var testCheckbox = this._checkboxElement(testElement); + testCheckbox.checked = selected; + }, this); + benchmarkController.updateStartButtonState(); + }, + + _onChangeTestCheckbox: function(suiteCheckbox) + { + this._updateSuiteCheckboxState(suiteCheckbox); + benchmarkController.updateStartButtonState(); + }, + + _createSuiteElement: function(treeElement, suite, id) + { + var suiteElement = Utilities.createElement("li", {}, treeElement); + var expand = Utilities.createElement("input", { type: "checkbox", class: "expand-button", id: id }, suiteElement); + var label = Utilities.createElement("label", { class: "tree-label", for: id }, suiteElement); + + var suiteCheckbox = Utilities.createElement("input", { type: "checkbox" }, label); + suiteCheckbox.suite = suite; + suiteCheckbox.onchange = this._onChangeSuiteCheckbox.bind(this); + suiteCheckbox.testsElements = []; + + label.appendChild(document.createTextNode(" " + suite.name)); + return suiteElement; + }, + + _createTestElement: function(listElement, test, suiteCheckbox) + { + var testElement = Utilities.createElement("li", {}, listElement); + var span = Utilities.createElement("label", { class: "tree-label" }, testElement); + + var testCheckbox = Utilities.createElement("input", { type: "checkbox" }, span); + testCheckbox.test = test; + testCheckbox.onchange = function(event) { + this._onChangeTestCheckbox(event.target.suiteCheckbox); + }.bind(this); + testCheckbox.suiteCheckbox = suiteCheckbox; + + suiteCheckbox.testsElements.push(testElement); + span.appendChild(document.createTextNode(" " + test.name + " ")); + + testElement.appendChild(document.createTextNode(" ")); + var link = Utilities.createElement("span", {}, testElement); + link.classList.add("link"); + link.textContent = "link"; + link.suiteName = Utilities.stripNonASCIICharacters(suiteCheckbox.suite.name); + link.testName = test.name; + link.onclick = function(event) { + var element = event.target; + var title = "Link to run “" + element.testName + "” with current options:"; + var url = location.href.split(/[?#]/)[0]; + var options = optionsManager.updateLocalStorageFromUI(); + Utilities.extendObject(options, { + "suite-name": element.suiteName, + "test-name": Utilities.stripNonASCIICharacters(element.testName) + }); + var complexity = suitesManager._editElement(element.parentNode).value; + if (complexity) + options.complexity = complexity; + prompt(title, url + Utilities.convertObjectToQueryString(options)); + }; + + var complexity = Utilities.createElement("input", { type: "number" }, testElement); + complexity.relatedCheckbox = testCheckbox; + complexity.oninput = function(event) { + var relatedCheckbox = event.target.relatedCheckbox; + relatedCheckbox.checked = true; + this._onChangeTestCheckbox(relatedCheckbox.suiteCheckbox); + }.bind(this); + return testElement; + }, + + createElements: function() + { + var treeElement = this._treeElement(); + + Suites.forEach(function(suite, index) { + var suiteElement = this._createSuiteElement(treeElement, suite, "suite-" + index); + var listElement = Utilities.createElement("ul", {}, suiteElement); + var suiteCheckbox = this._checkboxElement(suiteElement); + + suite.tests.forEach(function(test) { + this._createTestElement(listElement, test, suiteCheckbox); + }, this); + }, this); + }, + + updateEditsElementsState: function() + { + var editsElements = this._editsElements(); + var showComplexityInputs = ["fixed", "step"].indexOf(optionsManager.valueForOption("controller")) != -1; + + for (var i = 0; i < editsElements.length; ++i) { + var editElement = editsElements[i]; + if (showComplexityInputs) + editElement.classList.add("selected"); + else + editElement.classList.remove("selected"); + } + }, + + updateUIFromLocalStorage: function() + { + var suitesElements = this._suitesElements(); + + for (var i = 0; i < suitesElements.length; ++i) { + var suiteElement = suitesElements[i]; + var suiteCheckbox = this._checkboxElement(suiteElement); + var suite = suiteCheckbox.suite; + + suiteCheckbox.testsElements.forEach(function(testElement) { + var testCheckbox = this._checkboxElement(testElement); + var testEdit = this._editElement(testElement); + var test = testCheckbox.test; + + var str = localStorage.getItem(this._localStorageNameForTest(suite.name, test.name)); + if (str === null) + return; + + var value = JSON.parse(str); + testCheckbox.checked = value.checked; + testEdit.value = value.complexity; + }, this); + + this._updateSuiteCheckboxState(suiteCheckbox); + } + + benchmarkController.updateStartButtonState(); + }, + + updateLocalStorageFromUI: function() + { + var suitesElements = this._suitesElements(); + var suites = []; + + for (var i = 0; i < suitesElements.length; ++i) { + var suiteElement = suitesElements[i]; + var suiteCheckbox = this._checkboxElement(suiteElement); + var suite = suiteCheckbox.suite; + + var tests = []; + suiteCheckbox.testsElements.forEach(function(testElement) { + var testCheckbox = this._checkboxElement(testElement); + var testEdit = this._editElement(testElement); + var test = testCheckbox.test; + + if (testCheckbox.checked) { + test.complexity = testEdit.value; + tests.push(test); + } + + var value = { checked: testCheckbox.checked, complexity: testEdit.value }; + try { + localStorage.setItem(this._localStorageNameForTest(suite.name, test.name), JSON.stringify(value)); + } catch (e) {} + }, this); + + if (tests.length) + suites.push(new Suite(suiteCheckbox.suite.name, tests)); + } + + return suites; + }, + + suitesFromQueryString: function(suiteName, testName, oskey=null) + { + var suites = []; + var suiteRegExp = new RegExp(suiteName, "i"); + var testRegExp = new RegExp(testName, "i"); + + for (var i = 0; i < Suites.length; ++i) { + var suite = Suites[i]; + if (!Utilities.stripNonASCIICharacters(suite.name).match(suiteRegExp)) + continue; + + var test; + for (var j = 0; j < suite.tests.length; ++j) { + suiteTest = suite.tests[j]; + // MOZILLA: Run all the tests in a given suite + if (typeof(testName) === "undefined") { + let complexity = {"HTMLsuite": { + "CSSbouncingcircles": {"win": 322, "linux64": 322, "osx": 218}, + "CSSbouncingclippedrects": {"win": 520, "linux64": 520, "osx": 75}, + "CSSbouncinggradientcircles": {"win": 402, "linux64": 402, "osx": 97}, + "CSSbouncingblendcircles": {"win": 171, "linux64": 171, "osx": 254}, + "CSSbouncingfiltercircles": {"win": 189, "linux64": 189, "osx": 189}, + "CSSbouncingSVGimages": {"win": 329, "linux64": 329, "osx": 392}, + "CSSbouncingtaggedimages": {"win": 255, "linux64": 255, "osx": 351}, + "Leaves20": {"win": 262, "linux64": 262, "osx": 191}, + "Focus20": {"win": 15, "linux64": 15, "osx": 18}, + "DOMparticlesSVGmasks": {"win": 390, "linux64": 390, "osx": 54}, + "CompositedTransforms": {"win": 400, "linux64": 400, "osx": 75} + }, "Animometer": { + "Multiply": {"win": 391, "linux64": 391, "osx": 193}, + "CanvasArcs": {"win": 1287, "linux64": 1287, "osx": 575}, + "Leaves": {"win": 550, "linux64": 550, "osx": 271}, + "Paths": {"win": 4070, "linux64": 4070, "osx": 2024}, + "CanvasLines": {"win": 4692, "linux64": 4692, "osx": 10932}, + "Focus": {"win": 44, "linux64": 44, "osx": 32}, + "Images": {"win": 293, "linux64": 293, "osx": 188}, + "Design": {"win": 60, "linux64": 60, "osx": 17}, + "Suits": {"win": 210, "linux64": 210, "osx": 145} + } + }; + if (oskey == null) { + oskey = "linux64"; + } + suiteTest.complexity = complexity[suiteName][Utilities.stripNonASCIICharacters(suiteTest.name)][oskey]; + suites.push(new Suite(suiteName, [suiteTest])); + continue; + } + + if (Utilities.stripNonASCIICharacters(suiteTest.name).match(testRegExp)) { + test = suiteTest; + break; + } + } + + if (!test) + continue; + + suites.push(new Suite(suiteName, [test])); + }; + + return suites; + }, + + updateLocalStorageFromJSON: function(results) + { + for (var suiteName in results[Strings.json.results.tests]) { + var suiteResults = results[Strings.json.results.tests][suiteName]; + for (var testName in suiteResults) { + var testResults = suiteResults[testName]; + var data = testResults[Strings.json.controller]; + var complexity = Math.round(data[Strings.json.measurements.average]); + + var value = { checked: true, complexity: complexity }; + try { + localStorage.setItem(this._localStorageNameForTest(suiteName, testName), JSON.stringify(value)); + } catch (e) {} + } + } + } +} + +Utilities.extendObject(window.benchmarkController, { + initialize: function() + { + document.forms["benchmark-options"].addEventListener("change", benchmarkController.onBenchmarkOptionsChanged, true); + document.forms["graph-type"].addEventListener("change", benchmarkController.onGraphTypeChanged, true); + document.forms["time-graph-options"].addEventListener("change", benchmarkController.onTimeGraphOptionsChanged, true); + document.forms["complexity-graph-options"].addEventListener("change", benchmarkController.onComplexityGraphOptionsChanged, true); + optionsManager.updateUIFromLocalStorage(); + optionsManager.updateDisplay(); + optionsManager.updateTiles(); + + if (benchmarkController.startBenchmarkImmediatelyIfEncoded()) + return; + + benchmarkController.addOrientationListenerIfNecessary(); + suitesManager.createElements(); + suitesManager.updateUIFromLocalStorage(); + suitesManager.updateEditsElementsState(); + + var dropTarget = document.getElementById("drop-target"); + function stopEvent(e) { + e.stopPropagation(); + e.preventDefault(); + } + dropTarget.addEventListener("dragenter", stopEvent, false); + dropTarget.addEventListener("dragover", stopEvent, false); + dropTarget.addEventListener("dragleave", stopEvent, false); + dropTarget.addEventListener("drop", function (e) { + e.stopPropagation(); + e.preventDefault(); + + if (!e.dataTransfer.files.length) + return; + + var file = e.dataTransfer.files[0]; + + var reader = new FileReader(); + reader.filename = file.name; + reader.onload = function(e) { + var run = JSON.parse(e.target.result); + if (run.debugOutput instanceof Array) + run = run.debugOutput[0]; + benchmarkRunnerClient.results = new ResultsDashboard(run.options, run.data); + benchmarkController.showResults(); + }; + + reader.readAsText(file); + document.title = "File: " + reader.filename; + }, false); + }, + + updateStartButtonState: function() + { + var startButton = document.getElementById("run-benchmark"); + if ("isInLandscapeOrientation" in this && !this.isInLandscapeOrientation) { + startButton.disabled = true; + return; + } + startButton.disabled = !suitesManager.isAtLeastOneTestSelected(); + }, + + onBenchmarkOptionsChanged: function(event) + { + switch (event.target.name) { + case "controller": + suitesManager.updateEditsElementsState(); + break; + case "display": + optionsManager.updateDisplay(); + break; + case "tiles": + optionsManager.updateTiles(); + break; + } + }, + + startBenchmark: function() + { + benchmarkController.determineCanvasSize(); + benchmarkController.options = optionsManager.updateLocalStorageFromUI(); + benchmarkController.suites = suitesManager.updateLocalStorageFromUI(); + this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test"); + }, + + startBenchmarkImmediatelyIfEncoded: function() + { + benchmarkController.options = Utilities.convertQueryStringToObject(location.search); + if (!benchmarkController.options) + return false; + + this.raptor = benchmarkController.options["raptor"]; + benchmarkController.suites = suitesManager.suitesFromQueryString(benchmarkController.options["suite-name"], + benchmarkController.options["test-name"], + benchmarkController.options["oskey"]); + if (!benchmarkController.suites.length) + return false; + + setTimeout(function() { + this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test"); + }.bind(this), 0); + return true; + }, + + restartBenchmark: function() + { + this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test"); + }, + + showResults: function() + { + if (!this.addedKeyEvent) { + document.addEventListener("keypress", this.handleKeyPress, false); + this.addedKeyEvent = true; + } + + var dashboard = benchmarkRunnerClient.results; + if (["ramp", "ramp30"].indexOf(dashboard.options["controller"]) != -1) + Headers.details[3].disabled = true; + else { + Headers.details[1].disabled = true; + Headers.details[4].disabled = true; + } + + if (dashboard.options[Strings.json.configuration]) { + document.body.classList.remove("small", "medium", "large"); + document.body.classList.add(dashboard.options[Strings.json.configuration]); + } + + var score = dashboard.score; + var item = dashboard._results['iterationsResults'][0]; + var fullNames = new Array; + var values = new Array; + for (var suite in item['testsResults']) { + for (var subtest in item['testsResults'][suite.toString()]) { + fullNames.push(suite.toString() + "-" + subtest.toString().replace(/ /g, '_')); + values.push(item['testsResults'][suite.toString()][subtest.toString()]['frameLength']['average']); + } + } + if (typeof tpRecordTime !== "undefined") { + tpRecordTime(values.join(','), 0, fullNames.join(',')); + } + if (this.raptor) { + _data = ['raptor-benchmark', 'motionmark', item['testsResults']]; + window.postMessage(_data, '*'); + window.sessionStorage.setItem('benchmark_results', JSON.stringify(_data)); + } + + var confidence = ((dashboard.scoreLowerBound / score - 1) * 100).toFixed(2) + + "% / +" + ((dashboard.scoreUpperBound / score - 1) * 100).toFixed(2) + "%"; + sectionsManager.setSectionScore("results", score.toFixed(2), confidence); + sectionsManager.populateTable("results-header", Headers.testName, dashboard); + sectionsManager.populateTable("results-score", Headers.score, dashboard); + sectionsManager.populateTable("results-data", Headers.details, dashboard); + sectionsManager.showSection("results", true); + + suitesManager.updateLocalStorageFromJSON(dashboard.results[0]); + }, + + showTestGraph: function(testName, testResult, testData) + { + sectionsManager.setSectionHeader("test-graph", testName); + sectionsManager.showSection("test-graph", true); + this.updateGraphData(testResult, testData, benchmarkRunnerClient.results.options); + } +}); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/graph.js b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/graph.js new file mode 100644 index 0000000000..4803936219 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/graph.js @@ -0,0 +1,615 @@ +Utilities.extendObject(window.benchmarkController, { + updateGraphData: function(testResult, testData, options) + { + var element = document.getElementById("test-graph-data"); + element.innerHTML = ""; + element._testResult = testResult; + element._options = options; + + var margins = new Insets(30, 30, 30, 40); + var size = Point.elementClientSize(element); + size.y = window.innerHeight - element.offsetTop; + size = size.subtract(margins.size); + + // Convert from compact JSON output to propertied data + var samplesWithProperties = {}; + [Strings.json.controller, Strings.json.complexity, Strings.json.complexityAverage].forEach(function(seriesName) { + var series = testData[Strings.json.samples][seriesName]; + samplesWithProperties[seriesName] = series.toArray(); + }) + + this.createTimeGraph(testResult, samplesWithProperties[Strings.json.controller], testData[Strings.json.marks], testData[Strings.json.controller], options, margins, size); + this.onTimeGraphOptionsChanged(); + + this._showOrHideNodes(true, "form[name=graph-type]"); + document.forms["graph-type"].elements["type"] = "complexity"; + this.createComplexityGraph(testResult, testData[Strings.json.controller], samplesWithProperties, options, margins, size); + this.onComplexityGraphOptionsChanged(); + + this.onGraphTypeChanged(); + }, + + _addRegressionLine: function(parent, xScale, yScale, points, range, isAlongYAxis) + { + var polygon = []; + var line = [] + var xRange = isAlongYAxis ? range : 0; + var yRange = isAlongYAxis ? 0 : range; + for (var i = 0; i < points.length; ++i) { + var point = points[i]; + var x; + if (xRange instanceof Array) + x = xRange[0]; + else + x = point[0] + xRange; + polygon.push(xScale(x), yScale(point[1] + yRange)); + line.push(xScale(point[0]), yScale(point[1])); + } + for (var i = points.length - 1; i >= 0; --i) { + var point = points[i]; + var x; + if (xRange instanceof Array) + x = xRange[1]; + else + x = point[0] - xRange; + polygon.push(xScale(x), yScale(point[1] - yRange)); + } + parent.append("polygon") + .attr("points", polygon.join(",")); + parent.append("line") + .attr("x1", line[0]) + .attr("y1", line[1]) + .attr("x2", line[2]) + .attr("y2", line[3]); + }, + + _addRegression: function(data, svg, xScale, yScale) + { + svg.append("circle") + .attr("cx", xScale(data.segment1[1][0])) + .attr("cy", yScale(data.segment1[1][1])) + .attr("r", 5); + this._addRegressionLine(svg, xScale, yScale, data.segment1, data.stdev); + this._addRegressionLine(svg, xScale, yScale, data.segment2, data.stdev); + }, + + createComplexityGraph: function(result, timeRegressions, data, options, margins, size) + { + var svg = d3.select("#test-graph-data").append("svg") + .attr("id", "complexity-graph") + .attr("class", "hidden") + .attr("width", size.width + margins.left + margins.right) + .attr("height", size.height + margins.top + margins.bottom) + .append("g") + .attr("transform", "translate(" + margins.left + "," + margins.top + ")"); + + var timeSamples = data[Strings.json.controller]; + + var xMin = 100000, xMax = 0; + if (timeRegressions) { + timeRegressions.forEach(function(regression) { + for (var i = regression.startIndex; i <= regression.endIndex; ++i) { + xMin = Math.min(xMin, timeSamples[i].complexity); + xMax = Math.max(xMax, timeSamples[i].complexity); + } + }); + } else { + xMin = d3.min(timeSamples, function(s) { return s.complexity; }); + xMax = d3.max(timeSamples, function(s) { return s.complexity; }); + } + + var xScale = d3.scale.linear() + .range([0, size.width]) + .domain([xMin, xMax]); + var yScale = d3.scale.linear() + .range([size.height, 0]) + .domain([1000/20, 1000/60]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + var yAxis = d3.svg.axis() + .scale(yScale) + .tickValues([1000/20, 1000/25, 1000/30, 1000/35, 1000/40, 1000/45, 1000/50, 1000/55, 1000/60]) + .tickFormat(function(d) { return (1000 / d).toFixed(0); }) + .orient("left"); + + // x-axis + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + size.height + ")") + .call(xAxis); + + // y-axis + svg.append("g") + .attr("class", "y axis") + .call(yAxis); + + // time result + var mean = svg.append("g") + .attr("class", "mean complexity"); + var timeResult = result[Strings.json.controller]; + var yMin = yScale.domain()[0], yMax = yScale.domain()[1]; + this._addRegressionLine(mean, xScale, yScale, [[timeResult.average, yMin], [timeResult.average, yMax]], timeResult.stdev, true); + + // regression + this._addRegression(result[Strings.json.complexity], svg.append("g").attr("class", "regression raw"), xScale, yScale); + this._addRegression(result[Strings.json.complexityAverage], svg.append("g").attr("class", "regression average"), xScale, yScale); + + var bootstrapResult = result[Strings.json.complexity][Strings.json.bootstrap]; + if (bootstrapResult) { + var histogram = d3.layout.histogram() + .bins(xScale.ticks(100))(bootstrapResult.data); + var yBootstrapScale = d3.scale.linear() + .range([size.height/2, 0]) + .domain([0, d3.max(histogram, function(d) { return d.y; })]); + group = svg.append("g").attr("class", "bootstrap"); + var bar = group.selectAll(".bar") + .data(histogram) + .enter().append("g") + .attr("class", "bar") + .attr("transform", function(d) { return "translate(" + xScale(d.x) + "," + yBootstrapScale(d.y) + ")"; }); + bar.append("rect") + .attr("x", 1) + .attr("y", size.height/2) + .attr("width", xScale(histogram[1].x) - xScale(histogram[0].x) - 1) + .attr("height", function(d) { return size.height/2 - yBootstrapScale(d.y); }); + group = group.append("g").attr("class", "median"); + this._addRegressionLine(group, xScale, yScale, [[bootstrapResult.median, yMin], [bootstrapResult.median, yMax]], [bootstrapResult.confidenceLow, bootstrapResult.confidenceHigh], true); + group.append("circle") + .attr("cx", xScale(bootstrapResult.median)) + .attr("cy", yScale(1000/60)) + .attr("r", 5); + } + + // series + group = svg.append("g") + .attr("class", "series raw") + .selectAll("line") + .data(data[Strings.json.complexity]) + .enter(); + group.append("line") + .attr("x1", function(d) { return xScale(d.complexity) - 3; }) + .attr("x2", function(d) { return xScale(d.complexity) + 3; }) + .attr("y1", function(d) { return yScale(d.frameLength) - 3; }) + .attr("y2", function(d) { return yScale(d.frameLength) + 3; }); + group.append("line") + .attr("x1", function(d) { return xScale(d.complexity) - 3; }) + .attr("x2", function(d) { return xScale(d.complexity) + 3; }) + .attr("y1", function(d) { return yScale(d.frameLength) + 3; }) + .attr("y2", function(d) { return yScale(d.frameLength) - 3; }); + + group = svg.append("g") + .attr("class", "series average") + .selectAll("circle") + .data(data[Strings.json.complexityAverage]) + .enter(); + group.append("circle") + .attr("cx", function(d) { return xScale(d.complexity); }) + .attr("cy", function(d) { return yScale(d.frameLength); }) + .attr("r", 3) + group.append("line") + .attr("x1", function(d) { return xScale(d.complexity); }) + .attr("x2", function(d) { return xScale(d.complexity); }) + .attr("y1", function(d) { return yScale(d.frameLength - d.stdev); }) + .attr("y2", function(d) { return yScale(d.frameLength + d.stdev); }); + + // Cursor + var cursorGroup = svg.append("g").attr("class", "cursor hidden"); + cursorGroup.append("line") + .attr("class", "x") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", yScale(yAxis.scale().domain()[0]) + 10) + .attr("y2", yScale(yAxis.scale().domain()[1])); + cursorGroup.append("line") + .attr("class", "y") + .attr("x1", xScale(xAxis.scale().domain()[0]) - 10) + .attr("x2", xScale(xAxis.scale().domain()[1])) + .attr("y1", 0) + .attr("y2", 0) + cursorGroup.append("text") + .attr("class", "label x") + .attr("x", 0) + .attr("y", yScale(yAxis.scale().domain()[0]) + 15) + .attr("baseline-shift", "-100%") + .attr("text-anchor", "middle"); + cursorGroup.append("text") + .attr("class", "label y") + .attr("x", xScale(xAxis.scale().domain()[0]) - 15) + .attr("y", 0) + .attr("baseline-shift", "-30%") + .attr("text-anchor", "end"); + // Area to handle mouse events + var area = svg.append("rect") + .attr("fill", "transparent") + .attr("x", 0) + .attr("y", 0) + .attr("width", size.width) + .attr("height", size.height); + + area.on("mouseover", function() { + document.querySelector("#complexity-graph .cursor").classList.remove("hidden"); + }).on("mouseout", function() { + document.querySelector("#complexity-graph .cursor").classList.add("hidden"); + }).on("mousemove", function() { + var location = d3.mouse(this); + var location_domain = [xScale.invert(location[0]), yScale.invert(location[1])]; + cursorGroup.select("line.x") + .attr("x1", location[0]) + .attr("x2", location[0]); + cursorGroup.select("text.x") + .attr("x", location[0]) + .text(location_domain[0].toFixed(1)); + cursorGroup.select("line.y") + .attr("y1", location[1]) + .attr("y2", location[1]); + cursorGroup.select("text.y") + .attr("y", location[1]) + .text((1000 / location_domain[1]).toFixed(1)); + }); + }, + + createTimeGraph: function(result, samples, marks, regressions, options, margins, size) + { + var svg = d3.select("#test-graph-data").append("svg") + .attr("id", "time-graph") + .attr("width", size.width + margins.left + margins.right) + .attr("height", size.height + margins.top + margins.bottom) + .append("g") + .attr("transform", "translate(" + margins.left + "," + margins.top + ")"); + + // Axis scales + var x = d3.scale.linear() + .range([0, size.width]) + .domain([ + Math.min(d3.min(samples, function(s) { return s.time; }), 0), + d3.max(samples, function(s) { return s.time; })]); + var complexityMax = d3.max(samples, function(s) { + if (s.time > 0) + return s.complexity; + return 0; + }); + + var yLeft = d3.scale.linear() + .range([size.height, 0]) + .domain([0, complexityMax]); + var yRight = d3.scale.linear() + .range([size.height, 0]) + .domain([1000/20, 1000/60]); + + // Axes + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom") + .tickFormat(function(d) { return (d/1000).toFixed(0); }); + var yAxisLeft = d3.svg.axis() + .scale(yLeft) + .orient("left"); + var yAxisRight = d3.svg.axis() + .scale(yRight) + .tickValues([1000/20, 1000/25, 1000/30, 1000/35, 1000/40, 1000/45, 1000/50, 1000/55, 1000/60]) + .tickFormat(function(d) { return (1000/d).toFixed(0); }) + .orient("right"); + + // x-axis + svg.append("g") + .attr("class", "x axis") + .attr("fill", "rgb(235, 235, 235)") + .attr("transform", "translate(0," + size.height + ")") + .call(xAxis) + .append("text") + .attr("class", "label") + .attr("x", size.width) + .attr("y", -6) + .attr("fill", "rgb(235, 235, 235)") + .style("text-anchor", "end") + .text("time"); + + // yLeft-axis + svg.append("g") + .attr("class", "yLeft axis") + .attr("fill", "#7ADD49") + .call(yAxisLeft) + .append("text") + .attr("class", "label") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("fill", "#7ADD49") + .attr("dy", ".71em") + .style("text-anchor", "end") + .text(Strings.text.complexity); + + // yRight-axis + svg.append("g") + .attr("class", "yRight axis") + .attr("fill", "#FA4925") + .attr("transform", "translate(" + size.width + ", 0)") + .call(yAxisRight) + .append("text") + .attr("class", "label") + .attr("x", 9) + .attr("y", -20) + .attr("fill", "#FA4925") + .attr("dy", ".71em") + .style("text-anchor", "start") + .text(Strings.text.frameRate); + + // marks + var yMin = yRight(yAxisRight.scale().domain()[0]); + var yMax = yRight(yAxisRight.scale().domain()[1]); + for (var markName in marks) { + var mark = marks[markName]; + var xLocation = x(mark.time); + + var markerGroup = svg.append("g") + .attr("class", "marker") + .attr("transform", "translate(" + xLocation + ", 0)"); + markerGroup.append("text") + .attr("transform", "translate(10, " + (yMin - 10) + ") rotate(-90)") + .style("text-anchor", "start") + .text(markName) + markerGroup.append("line") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", yMin) + .attr("y2", yMax); + } + + if (Strings.json.controller in result) { + var complexity = result[Strings.json.controller]; + var regression = svg.append("g") + .attr("class", "complexity mean"); + this._addRegressionLine(regression, x, yLeft, [[samples[0].time, complexity.average], [samples[samples.length - 1].time, complexity.average]], complexity.stdev); + } + if (Strings.json.frameLength in result) { + var frameLength = result[Strings.json.frameLength]; + var regression = svg.append("g") + .attr("class", "fps mean"); + this._addRegressionLine(regression, x, yRight, [[samples[0].time, 1000/frameLength.average], [samples[samples.length - 1].time, 1000/frameLength.average]], frameLength.stdev); + } + + // right-target + if (options["controller"] == "adaptive") { + var targetFrameLength = 1000 / options["frame-rate"]; + svg.append("line") + .attr("x1", x(0)) + .attr("x2", size.width) + .attr("y1", yRight(targetFrameLength)) + .attr("y2", yRight(targetFrameLength)) + .attr("class", "target-fps marker"); + } + + // Cursor + var cursorGroup = svg.append("g").attr("class", "cursor"); + cursorGroup.append("line") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", yMin) + .attr("y2", yMin); + + // Data + var allData = samples; + var filteredData = samples.filter(function (sample) { + return "smoothedFrameLength" in sample; + }); + + function addData(name, data, yCoordinateCallback, pointRadius, omitLine) { + var svgGroup = svg.append("g").attr("id", name); + if (!omitLine) { + svgGroup.append("path") + .datum(data) + .attr("d", d3.svg.line() + .x(function(d) { return x(d.time); }) + .y(yCoordinateCallback)); + } + svgGroup.selectAll("circle") + .data(data) + .enter() + .append("circle") + .attr("cx", function(d) { return x(d.time); }) + .attr("cy", yCoordinateCallback) + .attr("r", pointRadius); + + cursorGroup.append("circle") + .attr("class", name) + .attr("r", pointRadius + 2); + } + + addData("complexity", allData, function(d) { return yLeft(d.complexity); }, 2); + addData("rawFPS", allData, function(d) { return yRight(d.frameLength); }, 1); + addData("filteredFPS", filteredData, function(d) { return yRight(d.smoothedFrameLength); }, 2); + + // regressions + var regressionGroup = svg.append("g") + .attr("id", "regressions"); + if (regressions) { + var complexities = []; + regressions.forEach(function (regression) { + if (!isNaN(regression.segment1[0][1]) && !isNaN(regression.segment1[1][1])) { + regressionGroup.append("line") + .attr("x1", x(regression.segment1[0][0])) + .attr("x2", x(regression.segment1[1][0])) + .attr("y1", yRight(regression.segment1[0][1])) + .attr("y2", yRight(regression.segment1[1][1])); + } + if (!isNaN(regression.segment2[0][1]) && !isNaN(regression.segment2[1][1])) { + regressionGroup.append("line") + .attr("x1", x(regression.segment2[0][0])) + .attr("x2", x(regression.segment2[1][0])) + .attr("y1", yRight(regression.segment2[0][1])) + .attr("y2", yRight(regression.segment2[1][1])); + } + // inflection point + regressionGroup.append("circle") + .attr("cx", x(regression.segment1[1][0])) + .attr("cy", yLeft(regression.complexity)) + .attr("r", 5); + complexities.push(regression.complexity); + }); + if (complexities.length) { + var yLeftComplexities = d3.svg.axis() + .scale(yLeft) + .tickValues(complexities) + .tickSize(10) + .orient("left"); + svg.append("g") + .attr("class", "complexity yLeft axis") + .call(yLeftComplexities); + } + } + + // Area to handle mouse events + var area = svg.append("rect") + .attr("fill", "transparent") + .attr("x", 0) + .attr("y", 0) + .attr("width", size.width) + .attr("height", size.height); + + var timeBisect = d3.bisector(function(d) { return d.time; }).right; + var statsToHighlight = ["complexity", "rawFPS", "filteredFPS"]; + area.on("mouseover", function() { + document.querySelector("#time-graph .cursor").classList.remove("hidden"); + document.querySelector("#test-graph nav").classList.remove("hide-data"); + }).on("mouseout", function() { + document.querySelector("#time-graph .cursor").classList.add("hidden"); + document.querySelector("#test-graph nav").classList.add("hide-data"); + }).on("mousemove", function() { + var form = document.forms["time-graph-options"].elements; + + var mx_domain = x.invert(d3.mouse(this)[0]); + var index = Math.min(timeBisect(allData, mx_domain), allData.length - 1); + var data = allData[index]; + var cursor_x = x(data.time); + var cursor_y = yAxisRight.scale().domain()[1]; + var ys = [yRight(yAxisRight.scale().domain()[0]), yRight(yAxisRight.scale().domain()[1])]; + + document.querySelector("#test-graph nav .time").textContent = (data.time / 1000).toFixed(4) + "s (" + index + ")"; + statsToHighlight.forEach(function(name) { + var element = document.querySelector("#test-graph nav ." + name); + var content = ""; + var data_y = null; + switch (name) { + case "complexity": + content = data.complexity; + data_y = yLeft(data.complexity); + break; + case "rawFPS": + content = (1000/data.frameLength).toFixed(2); + data_y = yRight(data.frameLength); + break; + case "filteredFPS": + if ("smoothedFrameLength" in data) { + content = (1000/data.smoothedFrameLength).toFixed(2); + data_y = yRight(data.smoothedFrameLength); + } + break; + } + + element.textContent = content; + + if (form[name].checked && data_y !== null) { + ys.push(data_y); + cursorGroup.select("." + name) + .attr("cx", cursor_x) + .attr("cy", data_y); + document.querySelector("#time-graph .cursor ." + name).classList.remove("hidden"); + } else + document.querySelector("#time-graph .cursor ." + name).classList.add("hidden"); + }); + + if (form["rawFPS"].checked) + cursor_y = Math.max(cursor_y, data.frameLength); + cursorGroup.select("line") + .attr("x1", cursor_x) + .attr("x2", cursor_x) + .attr("y1", Math.min.apply(null, ys)) + .attr("y2", Math.max.apply(null, ys)); + + }); + }, + + _showOrHideNodes: function(isShown, selector) { + var nodeList = document.querySelectorAll(selector); + if (isShown) { + for (var i = 0; i < nodeList.length; ++i) + nodeList[i].classList.remove("hidden"); + } else { + for (var i = 0; i < nodeList.length; ++i) + nodeList[i].classList.add("hidden"); + } + }, + + onComplexityGraphOptionsChanged: function() { + var form = document.forms["complexity-graph-options"].elements; + benchmarkController._showOrHideNodes(form["series-raw"].checked, "#complexity-graph .series.raw"); + benchmarkController._showOrHideNodes(form["series-average"].checked, "#complexity-graph .series.average"); + benchmarkController._showOrHideNodes(form["regression-time-score"].checked, "#complexity-graph .mean.complexity"); + benchmarkController._showOrHideNodes(form["bootstrap-score"].checked, "#complexity-graph .bootstrap"); + benchmarkController._showOrHideNodes(form["complexity-regression-aggregate-raw"].checked, "#complexity-graph .regression.raw"); + benchmarkController._showOrHideNodes(form["complexity-regression-aggregate-average"].checked, "#complexity-graph .regression.average"); + }, + + onTimeGraphOptionsChanged: function() { + var form = document.forms["time-graph-options"].elements; + benchmarkController._showOrHideNodes(form["markers"].checked, ".marker"); + benchmarkController._showOrHideNodes(form["averages"].checked, "#test-graph-data .mean"); + benchmarkController._showOrHideNodes(form["complexity"].checked, "#complexity"); + benchmarkController._showOrHideNodes(form["rawFPS"].checked, "#rawFPS"); + benchmarkController._showOrHideNodes(form["filteredFPS"].checked, "#filteredFPS"); + benchmarkController._showOrHideNodes(form["regressions"].checked, "#regressions"); + }, + + onGraphTypeChanged: function() { + var form = document.forms["graph-type"].elements; + var testResult = document.getElementById("test-graph-data")._testResult; + var isTimeSelected = form["graph-type"].value == "time"; + + benchmarkController._showOrHideNodes(isTimeSelected, "#time-graph"); + benchmarkController._showOrHideNodes(isTimeSelected, "form[name=time-graph-options]"); + benchmarkController._showOrHideNodes(!isTimeSelected, "#complexity-graph"); + benchmarkController._showOrHideNodes(!isTimeSelected, "form[name=complexity-graph-options]"); + + var score = "", mean = ""; + if (isTimeSelected) { + score = testResult[Strings.json.score].toFixed(2); + + var regression = testResult[Strings.json.controller]; + mean = [ + "mean: ", + regression.average.toFixed(2), + " ± ", + regression.stdev.toFixed(2), + " (", + regression.percent.toFixed(2), + "%)"]; + if (regression.concern) { + mean = mean.concat([ + ", worst 5%: ", + regression.concern.toFixed(2)]); + } + mean = mean.join(""); + } else { + var complexityRegression = testResult[Strings.json.complexity]; + var complexityAverageRegression = testResult[Strings.json.complexityAverage]; + + document.getElementById("complexity-regression-aggregate-raw").textContent = complexityRegression.complexity.toFixed(2) + ", ±" + complexityRegression.stdev.toFixed(2) + "ms"; + document.getElementById("complexity-regression-aggregate-average").textContent = complexityAverageRegression.complexity.toFixed(2) + ", ±" + complexityAverageRegression.stdev.toFixed(2) + "ms"; + + var bootstrap = complexityRegression[Strings.json.bootstrap]; + if (bootstrap) { + score = bootstrap.median.toFixed(2); + mean = [ + (bootstrap.confidencePercentage * 100).toFixed(0), + "% CI: ", + bootstrap.confidenceLow.toFixed(2), + "–", + bootstrap.confidenceHigh.toFixed(2) + ].join(""); + } + } + + sectionsManager.setSectionScore("test-graph", score, mean); + } +}); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/tests.js b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/tests.js new file mode 100644 index 0000000000..accaa3c3df --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/debug-runner/tests.js @@ -0,0 +1,345 @@ +Utilities.extendObject(Strings.text, { + samples: "Samples", + complexity: "Time Complexity", + frameRate: "FPS", + confidenceInterval: "80% Confidence Interval", + mergedRawComplexity: "Raw Complexity", + graph: "Graph" +}); + + +Utilities.extendObject(Headers, { + details: [ + { + title: Strings.text.graph + }, + { + title: Strings.text.confidenceInterval, + children: + [ + { + text: function(data) { + return data[Strings.json.complexity][Strings.json.bootstrap].confidenceLow.toFixed(2); + }, + className: "right pad-left pad-right" + }, + { + text: function(data) { + return " - " + data[Strings.json.complexity][Strings.json.bootstrap].confidenceHigh.toFixed(2); + }, + className: "left" + }, + { + text: function(data) { + var bootstrap = data[Strings.json.complexity][Strings.json.bootstrap]; + return (100 * (bootstrap.confidenceLow / bootstrap.median - 1)).toFixed(2) + "%"; + }, + className: "left pad-left small" + }, + { + text: function(data) { + var bootstrap = data[Strings.json.complexity][Strings.json.bootstrap]; + return "+" + (100 * (bootstrap.confidenceHigh / bootstrap.median - 1)).toFixed(2) + "%"; + }, + className: "left pad-left small" + } + ] + }, + { + title: Strings.text.complexity, + children: + [ + { + text: function(data) { + return data[Strings.json.controller][Strings.json.measurements.average].toFixed(2); + }, + className: "average" + }, + { + text: function(data) { + return [ + "± ", + data[Strings.json.controller][Strings.json.measurements.percent].toFixed(2), + "%" + ].join(""); + }, + className: function(data) { + var className = "stdev"; + + if (data[Strings.json.controller][Strings.json.measurements.percent] >= 10) + className += " noisy-results"; + return className; + } + } + ] + }, + { + title: Strings.text.frameRate, + children: + [ + { + text: function(data) { + return data[Strings.json.frameLength][Strings.json.measurements.average].toFixed(2); + }, + className: function(data, options) { + var className = "average"; + if (Math.abs(data[Strings.json.frameLength][Strings.json.measurements.average] - options["frame-rate"]) >= 2) + className += " noisy-results"; + return className; + } + }, + { + text: function(data) { + var frameRateData = data[Strings.json.frameLength]; + return [ + "± ", + frameRateData[Strings.json.measurements.percent].toFixed(2), + "%" + ].join(""); + }, + className: function(data) { + var className = "stdev"; + + if (data[Strings.json.frameLength][Strings.json.measurements.percent] >= 10) + className += " noisy-results"; + return className; + } + } + ] + }, + { + title: Strings.text.mergedRawComplexity, + children: + [ + { + text: function(data) { + return data[Strings.json.complexity][Strings.json.complexity].toFixed(2); + }, + className: "average" + }, + { + text: function(data) { + return [ + "± ", + data[Strings.json.complexity][Strings.json.measurements.stdev].toFixed(2), + "ms" + ].join(""); + }, + className: "stdev" + } + ] + } + ] +}) + +/////////// +// Suites + +Suites.push(new Suite("HTML suite", + [ + { + url: "bouncing-particles/bouncing-css-shapes.html?particleWidth=12&particleHeight=12&shape=circle", + name: "CSS bouncing circles" + }, + { + url: "bouncing-particles/bouncing-css-shapes.html?particleWidth=40&particleHeight=40&shape=rect&clip=star", + name: "CSS bouncing clipped rects" + }, + { + url: "bouncing-particles/bouncing-css-shapes.html?particleWidth=50&particleHeight=50&shape=circle&fill=gradient", + name: "CSS bouncing gradient circles" + }, + { + url: "bouncing-particles/bouncing-css-shapes.html?particleWidth=80&particleHeight=80&shape=circle&blend", + name: "CSS bouncing blend circles" + }, + { + url: "bouncing-particles/bouncing-css-shapes.html?particleWidth=80&particleHeight=80&shape=circle&filter", + name: "CSS bouncing filter circles" + }, + { + url: "bouncing-particles/bouncing-css-images.html?particleWidth=80&particleHeight=80&imageSrc=../resources/yin-yang.svg", + name: "CSS bouncing SVG images" + }, + { + url: "bouncing-particles/bouncing-tagged-images.html?particleWidth=100&particleHeight=100", + name: "CSS bouncing tagged images" + }, + { + url: "dom/leaves.html", + name: "Leaves 2.0" + }, + { + url: "dom/focus.html", + name: "Focus 2.0" + }, + { + url: "dom/particles.html", + name: "DOM particles, SVG masks" + }, + { + url: "dom/compositing-transforms.html?particleWidth=50&particleHeight=50&filters=yes&imageSrc=../resources/yin-yang.svg", + name: "Composited Transforms" + } + ] +)); + +Suites.push(new Suite("Canvas suite", + [ + { + url: "bouncing-particles/bouncing-canvas-shapes.html?particleWidth=40&particleHeight=40&shape=rect&clip=star", + name: "canvas bouncing clipped rects" + }, + { + url: "bouncing-particles/bouncing-canvas-shapes.html?particleWidth=50&particleHeight=50&shape=circle&fill=gradient", + name: "canvas bouncing gradient circles" + }, + { + url: "bouncing-particles/bouncing-canvas-images.html?particleWidth=80&particleHeight=80&imageSrc=../resources/yin-yang.svg", + name: "canvas bouncing SVG images" + }, + { + url: "bouncing-particles/bouncing-canvas-images.html?particleWidth=80&particleHeight=80&imageSrc=../resources/yin-yang.png", + name: "canvas bouncing PNG images" + }, + { + url: "simple/simple-canvas-paths.html?pathType=strokes", + name: "Stroke shapes" + }, + { + url: "simple/simple-canvas-paths.html?pathType=fills", + name: "Fill shapes" + }, + { + url: "simple/tiled-canvas-image.html", + name: "Canvas put/get image data" + }, + ] +)); + +Suites.push(new Suite("SVG suite", + [ + { + url: "bouncing-particles/bouncing-svg-shapes.html?particleWidth=12&particleHeight=12&shape=circle", + name: "SVG bouncing circles", + }, + { + url: "bouncing-particles/bouncing-svg-shapes.html?particleWidth=40&particleHeight=40&shape=rect&clip=star", + name: "SVG bouncing clipped rects", + }, + { + url: "bouncing-particles/bouncing-svg-shapes.html?particleWidth=50&particleHeight=50&shape=circle&fill=gradient", + name: "SVG bouncing gradient circles" + }, + { + url: "bouncing-particles/bouncing-svg-images.html?particleWidth=80&particleHeight=80&imageSrc=../resources/yin-yang.svg", + name: "SVG bouncing SVG images" + }, + { + url: "bouncing-particles/bouncing-svg-images.html?particleWidth=80&particleHeight=80&imageSrc=../resources/yin-yang.png", + name: "SVG bouncing PNG images" + }, + ] +)); + +Suites.push(new Suite("3D Graphics", + [ + { + url: "3d/webgl.html", + name: "WebGL" + }, + ] +)); + +Suites.push(new Suite("Basic canvas path suite", + [ + { + url: "simple/simple-canvas-paths.html?pathType=line&lineCap=butt", + name: "Canvas line segments, butt caps" + }, + { + url: "simple/simple-canvas-paths.html?pathType=line&lineCap=round", + name: "Canvas line segments, round caps" + }, + { + url: "simple/simple-canvas-paths.html?pathType=line&lineCap=square", + name: "Canvas line segments, square caps" + }, + { + url: "simple/simple-canvas-paths.html?pathType=linePath&lineJoin=bevel", + name: "Canvas line path, bevel join" + }, + { + url: "simple/simple-canvas-paths.html?pathType=linePath&lineJoin=round", + name: "Canvas line path, round join" + }, + { + url: "simple/simple-canvas-paths.html?pathType=linePath&lineJoin=miter", + name: "Canvas line path, miter join" + }, + { + url: "simple/simple-canvas-paths.html?pathType=linePath&lineDash=1", + name: "Canvas line path with dash pattern" + }, + { + url: "simple/simple-canvas-paths.html?pathType=quadratic", + name: "Canvas quadratic segments" + }, + { + url: "simple/simple-canvas-paths.html?pathType=quadraticPath", + name: "Canvas quadratic path" + }, + { + url: "simple/simple-canvas-paths.html?pathType=bezier", + name: "Canvas bezier segments" + }, + { + url: "simple/simple-canvas-paths.html?pathType=bezierPath", + name: "Canvas bezier path" + }, + { + url: "simple/simple-canvas-paths.html?&pathType=arcTo", + name: "Canvas arcTo segments" + }, + { + url: "simple/simple-canvas-paths.html?pathType=arc", + name: "Canvas arc segments" + }, + { + url: "simple/simple-canvas-paths.html?pathType=rect", + name: "Canvas rects" + }, + { + url: "simple/simple-canvas-paths.html?pathType=ellipse", + name: "Canvas ellipses" + }, + { + url: "simple/simple-canvas-paths.html?pathType=lineFill", + name: "Canvas line path, fill" + }, + { + url: "simple/simple-canvas-paths.html?pathType=quadraticFill", + name: "Canvas quadratic path, fill" + }, + { + url: "simple/simple-canvas-paths.html?pathType=bezierFill", + name: "Canvas bezier path, fill" + }, + { + url: "simple/simple-canvas-paths.html?&pathType=arcToFill", + name: "Canvas arcTo segments, fill" + }, + { + url: "simple/simple-canvas-paths.html?pathType=arcFill", + name: "Canvas arc segments, fill" + }, + { + url: "simple/simple-canvas-paths.html?pathType=rectFill", + name: "Canvas rects, fill" + }, + { + url: "simple/simple-canvas-paths.html?pathType=ellipseFill", + name: "Canvas ellipses, fill" + } + ] +)); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/extensions.js b/third_party/webkit/PerformanceTests/MotionMark/resources/extensions.js new file mode 100644 index 0000000000..fb9d500877 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/extensions.js @@ -0,0 +1,670 @@ +Utilities = +{ + _parse: function(str, sep) + { + var output = {}; + str.split(sep).forEach(function(part) { + var item = part.split("="); + var value = decodeURIComponent(item[1]); + if (value[0] == "'" ) + output[item[0]] = value.substr(1, value.length - 2); + else + output[item[0]] = value; + }); + return output; + }, + + parseParameters: function() + { + return this._parse(window.location.search.substr(1), "&"); + }, + + parseArguments: function(str) + { + return this._parse(str, " "); + }, + + extendObject: function(obj1, obj2) + { + for (var attrname in obj2) + obj1[attrname] = obj2[attrname]; + return obj1; + }, + + copyObject: function(obj) + { + return this.extendObject({}, obj); + }, + + mergeObjects: function(obj1, obj2) + { + return this.extendObject(this.copyObject(obj1), obj2); + }, + + createClass: function(classConstructor, classMethods) + { + classConstructor.prototype = classMethods; + return classConstructor; + }, + + createSubclass: function(superclass, classConstructor, classMethods) + { + classConstructor.prototype = Object.create(superclass.prototype); + classConstructor.prototype.constructor = classConstructor; + if (classMethods) + Utilities.extendObject(classConstructor.prototype, classMethods); + return classConstructor; + }, + + createElement: function(name, attrs, parentElement) + { + var element = document.createElement(name); + + for (var key in attrs) + element.setAttribute(key, attrs[key]); + + parentElement.appendChild(element); + return element; + }, + + createSVGElement: function(name, attrs, xlinkAttrs, parentElement) + { + const svgNamespace = "http://www.w3.org/2000/svg"; + const xlinkNamespace = "http://www.w3.org/1999/xlink"; + + var element = document.createElementNS(svgNamespace, name); + + for (var key in attrs) + element.setAttribute(key, attrs[key]); + + for (var key in xlinkAttrs) + element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]); + + parentElement.appendChild(element); + return element; + }, + + browserPrefix: function() + { + // Get the HTML element's CSSStyleDeclaration + var styles = window.getComputedStyle(document.documentElement, ''); + + // Convert the styles list to an array + var stylesArray = Array.prototype.slice.call(styles); + + // Concatenate all the styles in one big string + var stylesString = stylesArray.join(''); + + // Search the styles string for a known prefix type, settle on Opera if none is found. + var prefixes = stylesString.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']); + + // prefixes has two elements; e.g. for webkit it has ['-webkit-', 'webkit']; + var prefix = prefixes[1]; + + // Have 'O' before 'Moz' in the string so it is matched first. + var dom = ('WebKit|O|Moz|MS').match(new RegExp(prefix, 'i'))[0]; + + // Return all the required prefixes. + return { + dom: dom, + lowercase: prefix, + css: '-' + prefix + '-', + js: prefix[0].toUpperCase() + prefix.substr(1) + }; + }, + + setElementPrefixedProperty: function(element, property, value) + { + element.style[property] = element.style[this.browserPrefix().js + property[0].toUpperCase() + property.substr(1)] = value; + }, + + stripNonASCIICharacters: function(inputString) + { + return inputString.replace(/[ .,]/g, ''); + }, + + convertObjectToQueryString: function(object) + { + var queryString = []; + for (var property in object) { + if (object.hasOwnProperty(property)) + queryString.push(encodeURIComponent(property) + "=" + encodeURIComponent(object[property])); + } + return "?" + queryString.join("&"); + }, + + convertQueryStringToObject: function(queryString) + { + queryString = queryString.substring(1); + if (!queryString) + return null; + + var object = {}; + queryString.split("&").forEach(function(parameter) { + var components = parameter.split("="); + object[components[0]] = components[1]; + }); + return object; + }, + + progressValue: function(value, min, max) + { + return (value - min) / (max - min); + }, + + lerp: function(value, min, max) + { + return min + (max - min) * value; + }, + + toFixedNumber: function(number, precision) + { + if (number.toFixed) + return Number(number.toFixed(precision)); + return number; + } +}; + +Array.prototype.swap = function(i, j) +{ + var t = this[i]; + this[i] = this[j]; + this[j] = t; + return this; +} + +if (!Array.prototype.fill) { + Array.prototype.fill = function(value) { + if (this == null) + throw new TypeError('Array.prototype.fill called on null or undefined'); + + var object = Object(this); + var len = parseInt(object.length, 10); + var start = arguments[1]; + var relativeStart = parseInt(start, 10) || 0; + var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len); + var end = arguments[2]; + var relativeEnd = end === undefined ? len : (parseInt(end) || 0) ; + var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len); + + for (; k < final; k++) + object[k] = value; + + return object; + }; +} + +if (!Array.prototype.find) { + Array.prototype.find = function(predicate) { + if (this == null) + throw new TypeError('Array.prototype.find called on null or undefined'); + if (typeof predicate !== 'function') + throw new TypeError('predicate must be a function'); + + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) + return value; + } + return undefined; + }; +} + +Array.prototype.shuffle = function() +{ + for (var index = this.length - 1; index >= 0; --index) { + var randomIndex = Math.floor(Math.random() * (index + 1)); + this.swap(index, randomIndex); + } + return this; +} + +Point = Utilities.createClass( + function(x, y) + { + this.x = x; + this.y = y; + }, { + + // Used when the point object is used as a size object. + get width() + { + return this.x; + }, + + // Used when the point object is used as a size object. + get height() + { + return this.y; + }, + + // Used when the point object is used as a size object. + get center() + { + return new Point(this.x / 2, this.y / 2); + }, + + str: function() + { + return "x = " + this.x + ", y = " + this.y; + }, + + add: function(other) + { + if(isNaN(other.x)) + return new Point(this.x + other, this.y + other); + return new Point(this.x + other.x, this.y + other.y); + }, + + subtract: function(other) + { + if(isNaN(other.x)) + return new Point(this.x - other, this.y - other); + return new Point(this.x - other.x, this.y - other.y); + }, + + multiply: function(other) + { + if(isNaN(other.x)) + return new Point(this.x * other, this.y * other); + return new Point(this.x * other.x, this.y * other.y); + }, + + move: function(angle, velocity, timeDelta) + { + return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000))); + }, + + length: function() { + return Math.sqrt( this.x * this.x + this.y * this.y ); + }, + + normalize: function() { + var l = Math.sqrt( this.x * this.x + this.y * this.y ); + this.x /= l; + this.y /= l; + return this; + } +}); + +Utilities.extendObject(Point, { + zero: new Point(0, 0), + + pointOnCircle: function(angle, radius) + { + return new Point(radius * Math.cos(angle), radius * Math.sin(angle)); + }, + + pointOnEllipse: function(angle, radiuses) + { + return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle)); + }, + + elementClientSize: function(element) + { + var rect = element.getBoundingClientRect(); + return new Point(rect.width, rect.height); + } +}); + +Insets = Utilities.createClass( + function(top, right, bottom, left) + { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + }, { + + get width() + { + return this.left + this.right; + }, + + get height() + { + return this.top + this.bottom; + }, + + get size() + { + return new Point(this.width, this.height); + } +}); + +Insets.elementPadding = function(element) +{ + var styles = window.getComputedStyle(element); + return new Insets( + parseFloat(styles.paddingTop), + parseFloat(styles.paddingRight), + parseFloat(styles.paddingBottom), + parseFloat(styles.paddingTop)); +} + +UnitBezier = Utilities.createClass( + function(point1, point2) + { + // First and last points in the Bézier curve are assumed to be (0,0) and (!,1) + this._c = point1.multiply(3); + this._b = point2.subtract(point1).multiply(3).subtract(this._c); + this._a = new Point(1, 1).subtract(this._c).subtract(this._b); + }, { + + epsilon: 1e-5, + derivativeEpsilon: 1e-6, + + solve: function(x) + { + return this.sampleY(this.solveForT(x)); + }, + + sampleX: function(t) + { + return ((this._a.x * t + this._b.x) * t + this._c.x) * t; + }, + + sampleY: function(t) + { + return ((this._a.y * t + this._b.y) * t + this._c.y) * t; + }, + + sampleDerivativeX: function(t) + { + return(3 * this._a.x * t + 2 * this._b.x) * t + this._c.x; + }, + + solveForT: function(x) + { + var t0, t1, t2, x2, d2, i; + + for (t2 = x, i = 0; i < 8; ++i) { + x2 = this.sampleX(t2) - x; + if (Math.abs(x2) < this.epsilon) + return t2; + d2 = this.sampleDerivativeX(t2); + if (Math.abs(d2) < this.derivativeEpsilon) + break; + t2 = t2 - x2 / d2; + } + + t0 = 0; + t1 = 1; + t2 = x; + + if (t2 < t0) + return t0; + if (t2 > t1) + return t1; + + while (t0 < t1) { + x2 = this.sampleX(t2); + if (Math.abs(x2 - x) < this.epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 - t0) * .5 + t0; + } + + return t2; + } +}); + +SimplePromise = Utilities.createClass( + function() + { + this._chainedPromise = null; + this._callback = null; + }, { + + then: function (callback) + { + if (this._callback) + throw "SimplePromise doesn't support multiple calls to then"; + + this._callback = callback; + this._chainedPromise = new SimplePromise; + + if (this._resolved) + this.resolve(this._resolvedValue); + + return this._chainedPromise; + }, + + resolve: function (value) + { + if (!this._callback) { + this._resolved = true; + this._resolvedValue = value; + return; + } + + var result = this._callback(value); + if (result instanceof SimplePromise) { + var chainedPromise = this._chainedPromise; + result.then(function (result) { chainedPromise.resolve(result); }); + } else + this._chainedPromise.resolve(result); + } +}); + +var Heap = Utilities.createClass( + function(maxSize, compare) + { + this._maxSize = maxSize; + this._compare = compare; + this._size = 0; + this._values = new Array(this._maxSize); + }, { + + // This is a binary heap represented in an array. The root element is stored + // in the first element in the array. The root is followed by its two children. + // Then its four grandchildren and so on. So every level in the binary heap is + // doubled in the following level. Here is an example of the node indices and + // how they are related to their parents and children. + // =========================================================================== + // 0 1 2 3 4 5 6 + // PARENT -1 0 0 1 1 2 2 + // LEFT 1 3 5 7 9 11 13 + // RIGHT 2 4 6 8 10 12 14 + // =========================================================================== + _parentIndex: function(i) + { + return i > 0 ? Math.floor((i - 1) / 2) : -1; + }, + + _leftIndex: function(i) + { + var leftIndex = i * 2 + 1; + return leftIndex < this._size ? leftIndex : -1; + }, + + _rightIndex: function(i) + { + var rightIndex = i * 2 + 2; + return rightIndex < this._size ? rightIndex : -1; + }, + + // Return the child index that may violate the heap property at index i. + _childIndex: function(i) + { + var left = this._leftIndex(i); + var right = this._rightIndex(i); + + if (left != -1 && right != -1) + return this._compare(this._values[left], this._values[right]) > 0 ? left : right; + + return left != -1 ? left : right; + }, + + init: function() + { + this._size = 0; + }, + + top: function() + { + return this._size ? this._values[0] : NaN; + }, + + push: function(value) + { + if (this._size == this._maxSize) { + // If size is bounded and the new value can be a parent of the top() + // if the size were unbounded, just ignore the new value. + if (this._compare(value, this.top()) > 0) + return; + this.pop(); + } + this._values[this._size++] = value; + this._bubble(this._size - 1); + }, + + pop: function() + { + if (!this._size) + return NaN; + + this._values[0] = this._values[--this._size]; + this._sink(0); + }, + + _bubble: function(i) + { + // Fix the heap property at index i given that parent is the only node that + // may violate the heap property. + for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) { + if (this._compare(this._values[pi], this._values[i]) > 0) + break; + + this._values.swap(pi, i); + } + }, + + _sink: function(i) + { + // Fix the heap property at index i given that each of the left and the right + // sub-trees satisfies the heap property. + for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) { + if (this._compare(this._values[i], this._values[ci]) > 0) + break; + + this._values.swap(ci, i); + } + }, + + str: function() + { + var out = "Heap[" + this._size + "] = ["; + for (var i = 0; i < this._size; ++i) { + out += this._values[i]; + if (i < this._size - 1) + out += ", "; + } + return out + "]"; + }, + + values: function(size) { + // Return the last "size" heap elements values. + var values = this._values.slice(0, this._size); + return values.sort(this._compare).slice(0, Math.min(size, this._size)); + } +}); + +Utilities.extendObject(Heap, { + createMinHeap: function(maxSize) + { + return new Heap(maxSize, function(a, b) { return b - a; }); + }, + + createMaxHeap: function(maxSize) { + return new Heap(maxSize, function(a, b) { return a - b; }); + } +}); + +var SampleData = Utilities.createClass( + function(fieldMap, data) + { + this.fieldMap = fieldMap || {}; + this.data = data || []; + }, { + + get length() + { + return this.data.length; + }, + + addField: function(name, index) + { + this.fieldMap[name] = index; + }, + + push: function(datum) + { + this.data.push(datum); + }, + + sort: function(sortFunction) + { + this.data.sort(sortFunction); + }, + + slice: function(begin, end) + { + return new SampleData(this.fieldMap, this.data.slice(begin, end)); + }, + + forEach: function(iterationFunction) + { + this.data.forEach(iterationFunction); + }, + + createDatum: function() + { + return []; + }, + + getFieldInDatum: function(datum, fieldName) + { + if (typeof datum === 'number') + datum = this.data[datum]; + return datum[this.fieldMap[fieldName]]; + }, + + setFieldInDatum: function(datum, fieldName, value) + { + if (typeof datum === 'number') + datum = this.data[datum]; + return datum[this.fieldMap[fieldName]] = value; + }, + + at: function(index) + { + return this.data[index]; + }, + + toArray: function() + { + var array = []; + + this.data.forEach(function(datum) { + var newDatum = {}; + array.push(newDatum); + + for (var fieldName in this.fieldMap) { + var value = this.getFieldInDatum(datum, fieldName); + if (value !== null && value !== undefined) + newDatum[fieldName] = value; + } + }, this); + + return array; + } +}); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.css b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.css new file mode 100644 index 0000000000..86da6bea1c --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.css @@ -0,0 +1,520 @@ +/* Outer harness */ + +html, +body { + min-height: 100%; +} + +body { + background-color: hsl(0, 0%, 95%); + + font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif; + font-size: 15px; + + cursor: default; + + -webkit-user-select: none; +} + +body.showing-intro, +body.showing-results { + background-color: hsl(35, 100%, 100%); + background-image: url(crystal.svg), url(lines.svg); + background-size: auto 225%, auto 100%; + background-repeat: no-repeat; + + animation: background-fade 1s ease-in 1s, background-color 60s linear infinite 2s; + animation-play-state: paused; + + will-change: background-color; +} + +body.showing-test-container { + overflow: hidden; +} + +body.images-loaded { + animation-play-state: running; +} + +@media screen and (min-width: 667px) { + body { + font-size: 24px; + } + + body.showing-intro, + body.showing-results { + background-size: 200% 100%, 150% auto; + } +} + +@media screen and (min-width: 1025px) { + body.showing-intro, + body.showing-results { + background-size: 150% 100%, 150% auto; + } +} + +::selection { + background-color: black; + color: white; +} + +.hidden { + display: none; +} + +section { + display: none; +} + +section.selected { + display: block; +} + +svg.logo { + width: 350px; + height: 88px; + + max-width: 100%; + + padding-bottom: 1em; + + color: hsl(35, 100%, 50%); + + animation: foreground-color 60s linear infinite 2s; + animation-play-state: paused; + + will-change: color; +} + +body.images-loaded svg.logo { + animation-play-state: running; +} + +@media screen and (min-width: 667px) { + svg.logo { + width: 525px; + height: 130px; + } +} + +section .body { + margin: 0 1em; + max-width: 350px; +} + +section .body p { + margin: 1em 0; + line-height: 1.5em; + + -webkit-user-select: text; + cursor: text; +} + +@media screen and (min-width: 667px) { + section .body { + margin-left: 2.5em; + max-width: 500px; + transform: skewX(-10deg); + } + + section button { + transform: none; + } +} + +button { + background-color: hsl(35, 100%, 50%); + color: white; + + padding: 0.25em; + margin: 1.5em -0.25em 0 0; + + min-width: 50%; + + border: none; + + font-family: inherit; + font-size: inherit; + + transform: skewX(-10deg); + + transition: 100ms filter ease-in-out; + + animation: background-color 60s linear infinite 2s; + animation-play-state: paused; + + will-change: background-color; +} + +body.images-loaded button { + animation-play-state: running; +} + +button:hover { + filter: brightness(115%); +} + +button:active { + filter: brightness(130%); +} + +button:disabled { + opacity: 0.5; + filter: none !important; +} + +@media print { + button { + display: none; + } +} + +.portrait-orientation-check { + display: none; +} + +@media screen and (max-device-width: 1025px) and (orientation: portrait) { + .portrait-orientation-check { + display: block; + } +} + +@media screen and (max-device-width: 1025px) and (orientation: portrait) { + .landscape-orientation-check { + /* This keeps the button color animation in sync with page, while display: none does not. */ + visibility: hidden; + } +} + +@keyframes background-fade { + 100% { + background-color: hsl(35, 100%, 50%); + } +} + +@keyframes background-color { + 0%, 10% { + background-color: hsl(35, 100%, 50%); + } + + 12%, 20% { + background-color: hsl(75, 100%, 30%); + } + + 22%, 30% { + background-color: hsl(115, 100%, 30%); + } + + 32%, 40% { + background-color: hsl(155, 100%, 30%); + } + + 42%, 50% { + background-color: hsl(195, 100%, 30%); + } + + 52%, 60% { + background-color: hsl(235, 100%, 30%); + } + + 62%, 70% { + background-color: hsl(275, 100%, 30%); + } + + 72%, 80% { + background-color: hsl(315, 100%, 30%); + } + + 82%, 90% { + background-color: hsl(355, 100%, 30%); + } + + 92%, 100% { + background-color: hsl(395, 100%, 50%); + } +} + +@keyframes foreground-color { + 0%, 10% { + color: hsl(35, 100%, 50%); + } + + 12%, 20% { + color: hsl(75, 100%, 30%); + } + + 22%, 30% { + color: hsl(115, 100%, 30%); + } + + 32%, 40% { + color: hsl(155, 100%, 30%); + } + + 42%, 50% { + color: hsl(195, 100%, 30%); + } + + 52%, 60% { + color: hsl(235, 100%, 30%); + } + + 62%, 70% { + color: hsl(275, 100%, 30%); + } + + 72%, 80% { + color: hsl(315, 100%, 30%); + } + + 82%, 90% { + color: hsl(355, 100%, 30%); + } + + 92%, 100% { + color: hsl(395, 100%, 50%); + } +} + +/* Intro section, About page */ + +#intro, #about { + padding: 2em; +} + +#intro { + opacity: 0; + transition: opacity 500ms ease-in; +} + +body.images-loaded #intro { + opacity: 1; +} + +#about .body { + transform: none; + margin: 0; + max-width: initial; +} + +#about li { + line-height: 1.5em; +} + +#about button { + padding: .75em 2em; + margin: 1.5em auto 0; + min-width: initial; + transform: skewX(-10deg); +} + +@media screen and (min-width: 667px) { + #about .body { + font-size: .7em; + margin: 1em; + } + + #about ol, #about ul { + padding-left: 3em; + } +} + + +#intro a, #about a, +#intro a:visited, #about a:visited { + color: black; +} + +/* Running test section */ + +.frame-container { + position: absolute; + + top: 50%; + left: 50%; +} + +.frame-container > iframe { + width: 100%; + height: 100%; + + border: 0; + margin: 0; +} + +body.small .frame-container { + width: 568px; + height: 320px; + margin-left: -284px; + margin-top: -160px; +} + +body.medium .frame-container { + width: 900px; + height: 600px; + margin-left: -450px; + margin-top: -300px; +} + +body.large .frame-container { + width: 1600px; + height: 800px; + margin-left: -800px; + margin-top: -400px; +} + +/* Results section */ + +#results { + padding: 2em; +} + +#results .body { + -webkit-user-select: text; +} + +#results .score-container { + padding-bottom: 2em; +} + +#results .table-container { + position: relative; +} + +#results .table-container > div { + margin-left: 40%; +} + +#results .score { + font-size: 5em; + font-weight: bold; + font-style: italic; + line-height: 1; + margin: 0; +} + +#results .confidence { + font-size: 2em; + font-style: italic; + line-height: 1; + margin: 0; + text-indent: 1.75em; + color: hsl(0, 0%, 40%); + padding-bottom: .3em; +} + +#results table { + border-spacing: 0; + margin: 0; + padding: 0; + min-width: 25%; +} + +#results table td, +#results table th { + padding: 0.25em; +} + +#results table td.suites-separator { + padding: 0; +} + +#results table tr:nth-child(even) { + background-color: hsla(0, 0%, 0%, 0.05); +} + +#results #results-header { + top: 0; + left: 0; + width: 40%; + position: absolute; +} + +#results #results-score { + float: left; +} + +#results #results-data span { + font-size: .75em; + color: hsl(0, 0%, 40%); +} + +#results #results-header td, +#results #results-header th { + text-align: right; + padding-right: 1em !important; + padding-left: 0.5em !important; +} + +#results #results-score td, +#results #results-score th { + text-align: left; + padding-right: 0.5em !important; +} + +#results #results-score td { + cursor: text; +} + +@media screen and (min-width: 667px) { + #results .score, + #results .confidence { + font-style: normal; + } +} + +.detail span { + display: none; +} + +body.small .detail .small, +body.medium .detail .medium, +body.large .detail .large { + display: initial; +} + +#overlay { + position: fixed; + + top: 0; + left: 0; + bottom: 0; + right: 0; + + background: hsla(0, 0%, 100%, 0.9); +} + +@supports (-webkit-backdrop-filter: blur(10px)) { + #overlay { + background: hsla(0, 0%, 100%, 0.7); + -webkit-backdrop-filter: blur(20px); + } +} + +#overlay > div { + position: absolute; + + width: 500px; + height: 500px; + + margin-top: -250px; + margin-left: -250px; + + top: 50%; + left: 50%; +} + +#overlay > div div { + overflow: scroll; + + font-size: 12px; + -webkit-user-select: text; + cursor: text; + + max-height: 250px; + + border: 1px solid hsla(0, 0%, 0%, 0.1); + padding: 1em; +} diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.js b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.js new file mode 100644 index 0000000000..65e8c5450d --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/animometer.js @@ -0,0 +1,626 @@ +ResultsDashboard = Utilities.createClass( + function(options, testData) + { + this._iterationsSamplers = []; + this._options = options; + this._results = null; + if (testData) { + this._iterationsSamplers = testData; + this._processData(); + } + }, { + + push: function(suitesSamplers) + { + this._iterationsSamplers.push(suitesSamplers); + }, + + _processData: function() + { + this._results = {}; + this._results[Strings.json.results.iterations] = []; + + var iterationsScores = []; + this._iterationsSamplers.forEach(function(iteration, index) { + var testsScores = []; + var testsLowerBoundScores = []; + var testsUpperBoundScores = []; + + var result = {}; + this._results[Strings.json.results.iterations][index] = result; + + var suitesResult = {}; + result[Strings.json.results.tests] = suitesResult; + + for (var suiteName in iteration) { + var suiteData = iteration[suiteName]; + + var suiteResult = {}; + suitesResult[suiteName] = suiteResult; + + for (var testName in suiteData) { + if (!suiteData[testName][Strings.json.result]) + this.calculateScore(suiteData[testName]); + + suiteResult[testName] = suiteData[testName][Strings.json.result]; + delete suiteData[testName][Strings.json.result]; + + testsScores.push(suiteResult[testName][Strings.json.score]); + testsLowerBoundScores.push(suiteResult[testName][Strings.json.scoreLowerBound]); + testsUpperBoundScores.push(suiteResult[testName][Strings.json.scoreUpperBound]); + } + } + + result[Strings.json.score] = Statistics.geometricMean(testsScores); + result[Strings.json.scoreLowerBound] = Statistics.geometricMean(testsLowerBoundScores); + result[Strings.json.scoreUpperBound] = Statistics.geometricMean(testsUpperBoundScores); + iterationsScores.push(result[Strings.json.score]); + }, this); + + this._results[Strings.json.score] = Statistics.sampleMean(iterationsScores.length, iterationsScores.reduce(function(a, b) { return a + b; })); + this._results[Strings.json.scoreLowerBound] = this._results[Strings.json.results.iterations][0][Strings.json.scoreLowerBound]; + this._results[Strings.json.scoreUpperBound] = this._results[Strings.json.results.iterations][0][Strings.json.scoreUpperBound]; + }, + + calculateScore: function(data) + { + var result = {}; + data[Strings.json.result] = result; + var samples = data[Strings.json.samples]; + + var desiredFrameLength = 1000/60; + if (this._options["controller"] == "ramp30") + desiredFrameLength = 1000/30; + + function findRegression(series, profile) { + var minIndex = Math.round(.025 * series.length); + var maxIndex = Math.round(.975 * (series.length - 1)); + var minComplexity = series.getFieldInDatum(minIndex, Strings.json.complexity); + var maxComplexity = series.getFieldInDatum(maxIndex, Strings.json.complexity); + + if (Math.abs(maxComplexity - minComplexity) < 20 && maxIndex - minIndex < 20) { + minIndex = 0; + maxIndex = series.length - 1; + minComplexity = series.getFieldInDatum(minIndex, Strings.json.complexity); + maxComplexity = series.getFieldInDatum(maxIndex, Strings.json.complexity); + } + + var complexityIndex = series.fieldMap[Strings.json.complexity]; + var frameLengthIndex = series.fieldMap[Strings.json.frameLength]; + var regressionOptions = { desiredFrameLength: desiredFrameLength }; + if (profile) + regressionOptions.preferredProfile = profile; + return { + minComplexity: minComplexity, + maxComplexity: maxComplexity, + samples: series.slice(minIndex, maxIndex + 1), + regression: new Regression( + series.data, + function (data, i) { return data[i][complexityIndex]; }, + function (data, i) { return data[i][frameLengthIndex]; }, + minIndex, maxIndex, regressionOptions) + }; + } + + var complexitySamples; + // Convert these samples into SampleData objects if needed + [Strings.json.complexity, Strings.json.complexityAverage, Strings.json.controller].forEach(function(seriesName) { + var series = samples[seriesName]; + if (series && !(series instanceof SampleData)) + samples[seriesName] = new SampleData(series.fieldMap, series.data); + }); + + var isRampController = ["ramp", "ramp30"].indexOf(this._options["controller"]) != -1; + var predominantProfile = ""; + if (isRampController) { + var profiles = {}; + data[Strings.json.controller].forEach(function(regression) { + if (regression[Strings.json.regressions.profile]) { + var profile = regression[Strings.json.regressions.profile]; + profiles[profile] = (profiles[profile] || 0) + 1; + } + }); + + var maxProfileCount = 0; + for (var profile in profiles) { + if (profiles[profile] > maxProfileCount) { + predominantProfile = profile; + maxProfileCount = profiles[profile]; + } + } + } + + [Strings.json.complexity, Strings.json.complexityAverage].forEach(function(seriesName) { + if (!(seriesName in samples)) + return; + + var regression = {}; + result[seriesName] = regression; + var regressionResult = findRegression(samples[seriesName], predominantProfile); + if (seriesName == Strings.json.complexity) + complexitySamples = regressionResult.samples; + var calculation = regressionResult.regression; + regression[Strings.json.regressions.segment1] = [ + [regressionResult.minComplexity, calculation.s1 + calculation.t1 * regressionResult.minComplexity], + [calculation.complexity, calculation.s1 + calculation.t1 * calculation.complexity] + ]; + regression[Strings.json.regressions.segment2] = [ + [calculation.complexity, calculation.s2 + calculation.t2 * calculation.complexity], + [regressionResult.maxComplexity, calculation.s2 + calculation.t2 * regressionResult.maxComplexity] + ]; + regression[Strings.json.complexity] = calculation.complexity; + regression[Strings.json.measurements.stdev] = Math.sqrt(calculation.error / samples[seriesName].length); + }); + + if (isRampController) { + var timeComplexity = new Experiment; + data[Strings.json.controller].forEach(function(regression) { + timeComplexity.sample(regression[Strings.json.complexity]); + }); + + var experimentResult = {}; + result[Strings.json.controller] = experimentResult; + experimentResult[Strings.json.score] = timeComplexity.mean(); + experimentResult[Strings.json.measurements.average] = timeComplexity.mean(); + experimentResult[Strings.json.measurements.stdev] = timeComplexity.standardDeviation(); + experimentResult[Strings.json.measurements.percent] = timeComplexity.percentage(); + + const bootstrapIterations = 2500; + var bootstrapResult = Regression.bootstrap(complexitySamples.data, bootstrapIterations, function(resampleData) { + var complexityIndex = complexitySamples.fieldMap[Strings.json.complexity]; + resampleData.sort(function(a, b) { + return a[complexityIndex] - b[complexityIndex]; + }); + + var resample = new SampleData(complexitySamples.fieldMap, resampleData); + var regressionResult = findRegression(resample, predominantProfile); + return regressionResult.regression.complexity; + }, .8); + + result[Strings.json.complexity][Strings.json.bootstrap] = bootstrapResult; + result[Strings.json.score] = bootstrapResult.median; + result[Strings.json.scoreLowerBound] = bootstrapResult.confidenceLow; + result[Strings.json.scoreUpperBound] = bootstrapResult.confidenceHigh; + } else { + var marks = data[Strings.json.marks]; + var samplingStartIndex = 0, samplingEndIndex = -1; + if (Strings.json.samplingStartTimeOffset in marks) + samplingStartIndex = marks[Strings.json.samplingStartTimeOffset].index; + if (Strings.json.samplingEndTimeOffset in marks) + samplingEndIndex = marks[Strings.json.samplingEndTimeOffset].index; + + var averageComplexity = new Experiment; + var averageFrameLength = new Experiment; + var controllerSamples = samples[Strings.json.controller]; + controllerSamples.forEach(function (sample, i) { + if (i >= samplingStartIndex && (samplingEndIndex == -1 || i < samplingEndIndex)) { + averageComplexity.sample(controllerSamples.getFieldInDatum(sample, Strings.json.complexity)); + var smoothedFrameLength = controllerSamples.getFieldInDatum(sample, Strings.json.smoothedFrameLength); + if (smoothedFrameLength && smoothedFrameLength != -1) + averageFrameLength.sample(smoothedFrameLength); + } + }); + + var experimentResult = {}; + result[Strings.json.controller] = experimentResult; + experimentResult[Strings.json.measurements.average] = averageComplexity.mean(); + experimentResult[Strings.json.measurements.concern] = averageComplexity.concern(Experiment.defaults.CONCERN); + experimentResult[Strings.json.measurements.stdev] = averageComplexity.standardDeviation(); + experimentResult[Strings.json.measurements.percent] = averageComplexity.percentage(); + + experimentResult = {}; + result[Strings.json.frameLength] = experimentResult; + experimentResult[Strings.json.measurements.average] = 1000 / averageFrameLength.mean(); + experimentResult[Strings.json.measurements.concern] = averageFrameLength.concern(Experiment.defaults.CONCERN); + experimentResult[Strings.json.measurements.stdev] = averageFrameLength.standardDeviation(); + experimentResult[Strings.json.measurements.percent] = averageFrameLength.percentage(); + + result[Strings.json.score] = averageComplexity.score(Experiment.defaults.CONCERN); + result[Strings.json.scoreLowerBound] = result[Strings.json.score] - averageFrameLength.standardDeviation(); + result[Strings.json.scoreUpperBound] = result[Strings.json.score] + averageFrameLength.standardDeviation(); + } + }, + + get data() + { + return this._iterationsSamplers; + }, + + get results() + { + if (this._results) + return this._results[Strings.json.results.iterations]; + this._processData(); + return this._results[Strings.json.results.iterations]; + }, + + get options() + { + return this._options; + }, + + _getResultsProperty: function(property) + { + if (this._results) + return this._results[property]; + this._processData(); + return this._results[property]; + }, + + get score() + { + return this._getResultsProperty(Strings.json.score); + }, + + get scoreLowerBound() + { + return this._getResultsProperty(Strings.json.scoreLowerBound); + }, + + get scoreUpperBound() + { + return this._getResultsProperty(Strings.json.scoreUpperBound); + } +}); + +ResultsTable = Utilities.createClass( + function(element, headers) + { + this.element = element; + this._headers = headers; + + this._flattenedHeaders = []; + this._headers.forEach(function(header) { + if (header.disabled) + return; + + if (header.children) + this._flattenedHeaders = this._flattenedHeaders.concat(header.children); + else + this._flattenedHeaders.push(header); + }, this); + + this._flattenedHeaders = this._flattenedHeaders.filter(function (header) { + return !header.disabled; + }); + + this.clear(); + }, { + + clear: function() + { + this.element.textContent = ""; + }, + + _addHeader: function() + { + var thead = Utilities.createElement("thead", {}, this.element); + var row = Utilities.createElement("tr", {}, thead); + + this._headers.forEach(function (header) { + if (header.disabled) + return; + + var th = Utilities.createElement("th", {}, row); + if (header.title != Strings.text.graph) + th.innerHTML = header.title; + if (header.children) + th.colSpan = header.children.length; + }); + }, + + _addBody: function() + { + this.tbody = Utilities.createElement("tbody", {}, this.element); + }, + + _addEmptyRow: function() + { + var row = Utilities.createElement("tr", {}, this.tbody); + this._flattenedHeaders.forEach(function (header) { + return Utilities.createElement("td", { class: "suites-separator" }, row); + }); + }, + + _addTest: function(testName, testResult, options) + { + var row = Utilities.createElement("tr", {}, this.tbody); + + this._flattenedHeaders.forEach(function (header) { + var td = Utilities.createElement("td", {}, row); + if (header.text == Strings.text.testName) { + td.textContent = testName; + } else if (typeof header.text == "string") { + var data = testResult[header.text]; + if (typeof data == "number") + data = data.toFixed(2); + td.innerHTML = data; + } else + td.innerHTML = header.text(testResult); + }, this); + }, + + _addIteration: function(iterationResult, iterationData, options) + { + var testsResults = iterationResult[Strings.json.results.tests]; + for (var suiteName in testsResults) { + this._addEmptyRow(); + var suiteResult = testsResults[suiteName]; + var suiteData = iterationData[suiteName]; + for (var testName in suiteResult) + this._addTest(testName, suiteResult[testName], options, suiteData[testName]); + } + }, + + showIterations: function(dashboard) + { + this.clear(); + this._addHeader(); + this._addBody(); + + var iterationsResults = dashboard.results; + iterationsResults.forEach(function(iterationResult, index) { + this._addIteration(iterationResult, dashboard.data[index], dashboard.options); + }, this); + } +}); + +window.benchmarkRunnerClient = { + iterationCount: 1, + options: null, + results: null, + + initialize: function(suites, options) + { + this.options = options; + }, + + willStartFirstIteration: function() + { + this.results = new ResultsDashboard(this.options); + }, + + didRunSuites: function(suitesSamplers) + { + this.results.push(suitesSamplers); + }, + + didRunTest: function(testData) + { + this.results.calculateScore(testData); + }, + + didFinishLastIteration: function() + { + benchmarkController.showResults(); + } +}; + +window.sectionsManager = +{ + showSection: function(sectionIdentifier, pushState) + { + var sections = document.querySelectorAll("main > section"); + for (var i = 0; i < sections.length; ++i) { + document.body.classList.remove("showing-" + sections[i].id); + } + document.body.classList.add("showing-" + sectionIdentifier); + + var currentSectionElement = document.querySelector("section.selected"); + console.assert(currentSectionElement); + + var newSectionElement = document.getElementById(sectionIdentifier); + console.assert(newSectionElement); + + currentSectionElement.classList.remove("selected"); + newSectionElement.classList.add("selected"); + + if (pushState) + history.pushState({section: sectionIdentifier}, document.title); + }, + + setSectionScore: function(sectionIdentifier, score, confidence) + { + document.querySelector("#" + sectionIdentifier + " .score").textContent = score; + if (confidence) + document.querySelector("#" + sectionIdentifier + " .confidence").textContent = confidence; + }, + + populateTable: function(tableIdentifier, headers, dashboard) + { + var table = new ResultsTable(document.getElementById(tableIdentifier), headers); + table.showIterations(dashboard); + } +}; + +window.benchmarkController = { + initialize: function() + { + benchmarkController.addOrientationListenerIfNecessary(); + }, + + determineCanvasSize: function() { + var match = window.matchMedia("(max-device-width: 760px)"); + if (match.matches) { + document.body.classList.add("small"); + return; + } + + match = window.matchMedia("(max-device-width: 1600px)"); + if (match.matches) { + document.body.classList.add("medium"); + return; + } + + match = window.matchMedia("(max-width: 1600px)"); + if (match.matches) { + document.body.classList.add("medium"); + return; + } + + document.body.classList.add("large"); + }, + + addOrientationListenerIfNecessary: function() { + if (!("orientation" in window)) + return; + + this.orientationQuery = window.matchMedia("(orientation: landscape)"); + this._orientationChanged(this.orientationQuery); + this.orientationQuery.addListener(this._orientationChanged); + }, + + _orientationChanged: function(match) + { + benchmarkController.isInLandscapeOrientation = match.matches; + if (match.matches) + document.querySelector(".start-benchmark p").classList.add("hidden"); + else + document.querySelector(".start-benchmark p").classList.remove("hidden"); + benchmarkController.updateStartButtonState(); + }, + + updateStartButtonState: function() + { + document.getElementById("run-benchmark").disabled = !this.isInLandscapeOrientation; + }, + + _startBenchmark: function(suites, options, frameContainerID) + { + benchmarkController.determineCanvasSize(); + + var configuration = document.body.className.match(/small|medium|large/); + if (configuration) + options[Strings.json.configuration] = configuration[0]; + + benchmarkRunnerClient.initialize(suites, options); + var frameContainer = document.getElementById(frameContainerID); + var runner = new BenchmarkRunner(suites, frameContainer, benchmarkRunnerClient); + runner.runMultipleIterations(); + + sectionsManager.showSection("test-container"); + }, + + startBenchmark: function() + { + var options = { + "test-interval": 30, + "display": "minimal", + "tiles": "big", + "controller": "ramp", + "kalman-process-error": 1, + "kalman-measurement-error": 4, + "time-measurement": "performance" + }; + this._startBenchmark(Suites, options, "test-container"); + }, + + showResults: function() + { + if (!this.addedKeyEvent) { + document.addEventListener("keypress", this.handleKeyPress, false); + this.addedKeyEvent = true; + } + + var dashboard = benchmarkRunnerClient.results; + var score = dashboard.score; + var confidence = "±" + (Statistics.largestDeviationPercentage(dashboard.scoreLowerBound, score, dashboard.scoreUpperBound) * 100).toFixed(2) + "%"; + sectionsManager.setSectionScore("results", score.toFixed(2), confidence); + sectionsManager.populateTable("results-header", Headers.testName, dashboard); + sectionsManager.populateTable("results-score", Headers.score, dashboard); + sectionsManager.populateTable("results-data", Headers.details, dashboard); + sectionsManager.showSection("results", true); + }, + + handleKeyPress: function(event) + { + switch (event.charCode) + { + case 27: // esc + benchmarkController.hideDebugInfo(); + break; + case 106: // j + benchmarkController.showDebugInfo(); + break; + case 115: // s + benchmarkController.selectResults(event.target); + break; + } + }, + + hideDebugInfo: function() + { + var overlay = document.getElementById("overlay"); + if (!overlay) + return; + document.body.removeChild(overlay); + }, + + showDebugInfo: function() + { + if (document.getElementById("overlay")) + return; + + var overlay = Utilities.createElement("div", { + id: "overlay" + }, document.body); + var container = Utilities.createElement("div", {}, overlay); + + var header = Utilities.createElement("h3", {}, container); + header.textContent = "Debug Output"; + + var data = Utilities.createElement("div", {}, container); + data.textContent = "Please wait..."; + setTimeout(function() { + var output = { + options: benchmarkRunnerClient.results.options, + data: benchmarkRunnerClient.results.data + }; + data.textContent = JSON.stringify(output, function(key, value) { + if (typeof value === 'number') + return Utilities.toFixedNumber(value, 3); + return value; + }, 1); + }, 0); + data.onclick = function() { + var selection = window.getSelection(); + selection.removeAllRanges(); + var range = document.createRange(); + range.selectNode(data); + selection.addRange(range); + }; + + var button = Utilities.createElement("button", {}, container); + button.textContent = "Done"; + button.onclick = function() { + benchmarkController.hideDebugInfo(); + }; + }, + + selectResults: function(target) + { + target.selectRange = ((target.selectRange || 0) + 1) % 3; + + var selection = window.getSelection(); + selection.removeAllRanges(); + var range = document.createRange(); + switch (target.selectRange) { + case 0: { + range.selectNode(document.getElementById("results-score")); + break; + } + case 1: { + range.setStart(document.querySelector("#results .score"), 0); + range.setEndAfter(document.querySelector("#results-score"), 0); + break; + } + case 2: { + range.selectNodeContents(document.querySelector("#results .score")); + break; + } + } + selection.addRange(range); + } +}; + +window.addEventListener("load", function() { benchmarkController.initialize(); }); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/benchmark-runner.js b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/benchmark-runner.js new file mode 100644 index 0000000000..1aa630356c --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/benchmark-runner.js @@ -0,0 +1,179 @@ +BenchmarkRunnerState = Utilities.createClass( + function(suites) + { + this._suites = suites; + this._suiteIndex = -1; + this._testIndex = 0; + this.next(); + }, { + + currentSuite: function() + { + return this._suites[this._suiteIndex]; + }, + + currentTest: function() + { + var suite = this.currentSuite(); + return suite ? suite.tests[this._testIndex] : null; + }, + + isFirstTest: function() + { + return !this._testIndex; + }, + + next: function() + { + this._testIndex++; + + var suite = this._suites[this._suiteIndex]; + if (suite && this._testIndex < suite.tests.length) + return; + + this._testIndex = 0; + do { + this._suiteIndex++; + } while (this._suiteIndex < this._suites.length && this._suites[this._suiteIndex].disabled); + }, + + prepareCurrentTest: function(runner, frame) + { + var test = this.currentTest(); + var promise = new SimplePromise; + + frame.onload = function() { + promise.resolve(); + }; + + frame.src = "tests/" + test.url; + return promise; + } +}); + +BenchmarkRunner = Utilities.createClass( + function(suites, frameContainer, client) + { + this._suites = suites; + this._client = client; + this._frameContainer = frameContainer; + }, { + + _appendFrame: function() + { + var frame = document.createElement("iframe"); + frame.setAttribute("scrolling", "no"); + + this._frameContainer.insertBefore(frame, this._frameContainer.firstChild); + this._frame = frame; + return frame; + }, + + _removeFrame: function() + { + if (this._frame) { + this._frame.parentNode.removeChild(this._frame); + this._frame = null; + } + }, + + _runBenchmarkAndRecordResults: function(state) + { + var promise = new SimplePromise; + var suite = state.currentSuite(); + var test = state.currentTest(); + + if (this._client && this._client.willRunTest) + this._client.willRunTest(suite, test); + + var contentWindow = this._frame.contentWindow; + var self = this; + + var options = { complexity: test.complexity }; + Utilities.extendObject(options, this._client.options); + Utilities.extendObject(options, contentWindow.Utilities.parseParameters()); + + var benchmark = new contentWindow.benchmarkClass(options); + document.body.style.backgroundColor = benchmark.backgroundColor(); + benchmark.run().then(function(testData) { + var suiteResults = self._suitesResults[suite.name] || {}; + suiteResults[test.name] = testData; + self._suitesResults[suite.name] = suiteResults; + + if (self._client && self._client.didRunTest) + self._client.didRunTest(testData); + + state.next(); + if (state.currentSuite() != suite) + self._removeFrame(); + promise.resolve(state); + }); + + return promise; + }, + + step: function(state) + { + if (!state) { + state = new BenchmarkRunnerState(this._suites); + this._suitesResults = {}; + } + + var suite = state.currentSuite(); + if (!suite) { + this._finalize(); + var promise = new SimplePromise; + promise.resolve(); + return promise; + } + + if (state.isFirstTest()) { + this._appendFrame(); + } + + return state.prepareCurrentTest(this, this._frame).then(function(prepareReturnValue) { + return this._runBenchmarkAndRecordResults(state); + }.bind(this)); + }, + + runAllSteps: function(startingState) + { + var nextCallee = this.runAllSteps.bind(this); + this.step(startingState).then(function(nextState) { + if (nextState) + nextCallee(nextState); + }); + }, + + runMultipleIterations: function() + { + var self = this; + var currentIteration = 0; + + this._runNextIteration = function() { + currentIteration++; + if (currentIteration < self._client.iterationCount) + self.runAllSteps(); + else if (this._client && this._client.didFinishLastIteration) { + document.body.style.backgroundColor = ""; + self._client.didFinishLastIteration(); + } + } + + if (this._client && this._client.willStartFirstIteration) + this._client.willStartFirstIteration(); + + this.runAllSteps(); + }, + + _finalize: function() + { + this._removeFrame(); + + if (this._client && this._client.didRunSuites) + this._client.didRunSuites(this._suitesResults); + + if (this._runNextIteration) + this._runNextIteration(); + } +}); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/crystal.svg b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/crystal.svg new file mode 100644 index 0000000000..0090df6c0c --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/crystal.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/lines.svg b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/lines.svg new file mode 100644 index 0000000000..83458b8095 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/lines.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/logo.svg b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/logo.svg new file mode 100644 index 0000000000..a7d0eaf375 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/logo.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/runner/tests.js b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/tests.js new file mode 100644 index 0000000000..aa938c5189 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/runner/tests.js @@ -0,0 +1,81 @@ +var Headers = { + testName: [ + { + title: "" + Strings.text.testName + "", + text: Strings.text.testName + } + ], + score: [ + { + title: Strings.text.score, + text: Strings.json.score + } + ], + details: [ + { + title: " ", + text: function(data) { + var bootstrap = data[Strings.json.complexity][Strings.json.bootstrap]; + return "±" + (Statistics.largestDeviationPercentage(bootstrap.confidenceLow, bootstrap.median, bootstrap.confidenceHigh) * 100).toFixed(2) + "%"; + } + } + ] +}; + +var Suite = function(name, tests) { + this.name = name; + this.tests = tests; +}; + +var Suites = []; + +Suites.push(new Suite("Animometer", + [ + { + url: "master/multiply.html", + name: "Multiply" + }, + { + url: "master/canvas-stage.html?pathType=arcs", + name: "Canvas Arcs" + }, + { + url: "master/leaves.html", + name: "Leaves" + }, + { + url: "master/canvas-stage.html?pathType=linePath", + name: "Paths" + }, + { + url: "master/canvas-stage.html?pathType=line&lineCap=square", + name: "Canvas Lines" + }, + { + url: "master/focus.html", + name: "Focus" + }, + { + url: "master/image-data.html", + name: "Images" + }, + { + url: "master/text.html", + name: "Design" + }, + { + url: "master/svg-particles.html", + name: "Suits" + }, + ] +)); + +function suiteFromName(name) +{ + return Suites.find(function(suite) { return suite.name == name; }); +} + +function testFromName(suite, name) +{ + return suite.tests.find(function(test) { return test.name == name; }); +} diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/statistics.js b/third_party/webkit/PerformanceTests/MotionMark/resources/statistics.js new file mode 100644 index 0000000000..9322a797b6 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/statistics.js @@ -0,0 +1,397 @@ +Pseudo = +{ + initialRandomSeed: 49734321, + randomSeed: 49734321, + + resetRandomSeed: function() + { + Pseudo.randomSeed = Pseudo.initialRandomSeed; + }, + + random: function() + { + var randomSeed = Pseudo.randomSeed; + randomSeed = ((randomSeed + 0x7ed55d16) + (randomSeed << 12)) & 0xffffffff; + randomSeed = ((randomSeed ^ 0xc761c23c) ^ (randomSeed >>> 19)) & 0xffffffff; + randomSeed = ((randomSeed + 0x165667b1) + (randomSeed << 5)) & 0xffffffff; + randomSeed = ((randomSeed + 0xd3a2646c) ^ (randomSeed << 9)) & 0xffffffff; + randomSeed = ((randomSeed + 0xfd7046c5) + (randomSeed << 3)) & 0xffffffff; + randomSeed = ((randomSeed ^ 0xb55a4f09) ^ (randomSeed >>> 16)) & 0xffffffff; + Pseudo.randomSeed = randomSeed; + return (randomSeed & 0xfffffff) / 0x10000000; + } +}; + +Statistics = +{ + sampleMean: function(numberOfSamples, sum) + { + if (numberOfSamples < 1) + return 0; + return sum / numberOfSamples; + }, + + // With sum and sum of squares, we can compute the sample standard deviation in O(1). + // See https://rniwa.com/2012-11-10/sample-standard-deviation-in-terms-of-sum-and-square-sum-of-samples/ + unbiasedSampleStandardDeviation: function(numberOfSamples, sum, squareSum) + { + if (numberOfSamples < 2) + return 0; + return Math.sqrt((squareSum - sum * sum / numberOfSamples) / (numberOfSamples - 1)); + }, + + geometricMean: function(values) + { + if (!values.length) + return 0; + var roots = values.map(function(value) { return Math.pow(value, 1 / values.length); }) + return roots.reduce(function(a, b) { return a * b; }); + }, + + // Cumulative distribution function + cdf: function(value, mean, standardDeviation) + { + return 0.5 * (1 + Statistics.erf((value - mean) / (Math.sqrt(2 * standardDeviation * standardDeviation)))); + }, + + // Approximation of Gauss error function, Abramowitz and Stegun 7.1.26 + erf: function(value) + { + var sign = (value >= 0) ? 1 : -1; + value = Math.abs(value); + + var a1 = 0.254829592; + var a2 = -0.284496736; + var a3 = 1.421413741; + var a4 = -1.453152027; + var a5 = 1.061405429; + var p = 0.3275911; + + var t = 1.0 / (1.0 + p * value); + var y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-value * value); + return sign * y; + }, + + largestDeviationPercentage: function(low, mean, high) + { + return Math.max(Math.abs(low / mean - 1), (high / mean - 1)); + } +}; + +Experiment = Utilities.createClass( + function(includeConcern) + { + if (includeConcern) + this._maxHeap = Heap.createMaxHeap(Experiment.defaults.CONCERN_SIZE); + this.reset(); + }, { + + reset: function() + { + this._sum = 0; + this._squareSum = 0; + this._numberOfSamples = 0; + if (this._maxHeap) + this._maxHeap.init(); + }, + + get sampleCount() + { + return this._numberOfSamples; + }, + + sample: function(value) + { + this._sum += value; + this._squareSum += value * value; + if (this._maxHeap) + this._maxHeap.push(value); + ++this._numberOfSamples; + }, + + mean: function() + { + return Statistics.sampleMean(this._numberOfSamples, this._sum); + }, + + standardDeviation: function() + { + return Statistics.unbiasedSampleStandardDeviation(this._numberOfSamples, this._sum, this._squareSum); + }, + + cdf: function(value) + { + return Statistics.cdf(value, this.mean(), this.standardDeviation()); + }, + + percentage: function() + { + var mean = this.mean(); + return mean ? this.standardDeviation() * 100 / mean : 0; + }, + + concern: function(percentage) + { + if (!this._maxHeap) + return this.mean(); + + var size = Math.ceil(this._numberOfSamples * percentage / 100); + var values = this._maxHeap.values(size); + return values.length ? values.reduce(function(a, b) { return a + b; }) / values.length : 0; + }, + + score: function(percentage) + { + return Statistics.geometricMean([this.mean(), Math.max(this.concern(percentage), 1)]); + } +}); + +Experiment.defaults = +{ + CONCERN: 5, + CONCERN_SIZE: 100, +}; + +Regression = Utilities.createClass( + function(samples, getComplexity, getFrameLength, startIndex, endIndex, options) + { + var desiredFrameLength = options.desiredFrameLength || 1000/60; + var bestProfile; + + if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.slope) { + var slope = this._calculateRegression(samples, getComplexity, getFrameLength, startIndex, endIndex, { + shouldClip: true, + s1: desiredFrameLength, + t1: 0 + }); + if (!bestProfile || slope.error < bestProfile.error) { + bestProfile = slope; + this.profile = Strings.json.profiles.slope; + } + } + if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.flat) { + var flat = this._calculateRegression(samples, getComplexity, getFrameLength, startIndex, endIndex, { + shouldClip: true, + s1: desiredFrameLength, + t1: 0, + t2: 0 + }); + + if (!bestProfile || flat.error < bestProfile.error) { + bestProfile = flat; + this.profile = Strings.json.profiles.flat; + } + } + + this.startIndex = Math.min(startIndex, endIndex); + this.endIndex = Math.max(startIndex, endIndex); + + this.complexity = bestProfile.complexity; + this.s1 = bestProfile.s1; + this.t1 = bestProfile.t1; + this.s2 = bestProfile.s2; + this.t2 = bestProfile.t2; + this.stdev1 = bestProfile.stdev1; + this.stdev2 = bestProfile.stdev2; + this.n1 = bestProfile.n1; + this.n2 = bestProfile.n2; + this.error = bestProfile.error; + }, { + + valueAt: function(complexity) + { + if (this.n1 == 1 || complexity > this.complexity) + return this.s2 + this.t2 * complexity; + return this.s1 + this.t1 * complexity; + }, + + // A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya + // + // Minimize sum of (y - y')^2 + // where y = s1 + t1*x + // y = s2 + t2*x + // y' = s1 + t1*x' = s2 + t2*x' if x_0 <= x' <= x_n + // + // Allows for fixing s1, t1, s2, t2 + // + // x is assumed to be complexity, y is frame length. Can be used for pure complexity-FPS + // analysis or for ramp controllers since complexity monotonically decreases with time. + _calculateRegression: function(samples, getComplexity, getFrameLength, startIndex, endIndex, options) + { + if (startIndex == endIndex) { + // Only one sample point; we can't calculate any regression. + var x = getComplexity(samples, startIndex); + return { + complexity: x, + s1: x, + t1: 0, + s2: x, + t2: 0, + error1: 0, + error2: 0 + }; + } + + // x is expected to increase in complexity + var iterationDirection = endIndex > startIndex ? 1 : -1; + var lowComplexity = getComplexity(samples, startIndex); + var highComplexity = getComplexity(samples, endIndex); + var a1 = 0, b1 = 0, c1 = 0, d1 = 0, h1 = 0, k1 = 0; + var a2 = 0, b2 = 0, c2 = 0, d2 = 0, h2 = 0, k2 = 0; + + // Iterate from low to high complexity + for (var i = startIndex; iterationDirection * (endIndex - i) > -1; i += iterationDirection) { + var x = getComplexity(samples, i); + var y = getFrameLength(samples, i); + a2 += 1; + b2 += x; + c2 += x * x; + d2 += y; + h2 += y * x; + k2 += y * y; + } + + var s1_best, t1_best, s2_best, t2_best, n1_best, n2_best, error1_best, error2_best, x_best, x_prime; + + function setBest(s1, t1, error1, s2, t2, error2, splitIndex, x_prime, x) + { + s1_best = s1; + t1_best = t1; + error1_best = error1; + s2_best = s2; + t2_best = t2; + error2_best = error2; + // Number of samples included in the first segment, inclusive of splitIndex + n1_best = iterationDirection * (splitIndex - startIndex) + 1; + // Number of samples included in the second segment + n2_best = iterationDirection * (endIndex - splitIndex); + if (!options.shouldClip || (x_prime >= lowComplexity && x_prime <= highComplexity)) + x_best = x_prime; + else { + // Discontinuous piecewise regression + x_best = x; + } + } + + // Iterate from startIndex to endIndex - 1, inclusive + for (var i = startIndex; iterationDirection * (endIndex - i) > 0; i += iterationDirection) { + var x = getComplexity(samples, i); + var y = getFrameLength(samples, i); + var xx = x * x; + var yx = y * x; + var yy = y * y; + // a1, b1, etc. is sum from startIndex to i, inclusive + a1 += 1; + b1 += x; + c1 += xx; + d1 += y; + h1 += yx; + k1 += yy; + // a2, b2, etc. is sum from i+1 to endIndex, inclusive + a2 -= 1; + b2 -= x; + c2 -= xx; + d2 -= y; + h2 -= yx; + k2 -= yy; + + var A = c1*d1 - b1*h1; + var B = a1*h1 - b1*d1; + var C = a1*c1 - b1*b1; + var D = c2*d2 - b2*h2; + var E = a2*h2 - b2*d2; + var F = a2*c2 - b2*b2; + var s1 = options.s1 !== undefined ? options.s1 : (A / C); + var t1 = options.t1 !== undefined ? options.t1 : (B / C); + var s2 = options.s2 !== undefined ? options.s2 : (D / F); + var t2 = options.t2 !== undefined ? options.t2 : (E / F); + // Assumes that the two segments meet + var x_prime = (s1 - s2) / (t2 - t1); + + var error1 = (k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) || Number.MAX_VALUE; + var error2 = (k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) || Number.MAX_VALUE; + + if (i == startIndex) { + setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x); + continue; + } + + if (C == 0 || F == 0) + continue; + + // Projected point is not between this and the next sample + if (x_prime > getComplexity(samples, i + iterationDirection) || x_prime < x) { + // Calculate lambda, which divides the weight of this sample between the two lines + + // These values remove the influence of this sample + var I = c1 - 2*b1*x + a1*xx; + var H = C - I; + var G = A + B*x - C*y; + + var J = D + E*x - F*y; + var K = c2 - 2*b2*x + a2*xx; + + var lambda = (G*F + G*K - H*J) / (I*J + G*K); + if (lambda > 0 && lambda < 1) { + var lambda1 = 1 - lambda; + s1 = options.s1 !== undefined ? options.s1 : ((A - lambda1*(-h1*x + d1*xx + c1*y - b1*yx)) / (C - lambda1*I)); + t1 = options.t1 !== undefined ? options.t1 : ((B - lambda1*(h1 - d1*x - b1*y + a1*yx)) / (C - lambda1*I)); + s2 = options.s2 !== undefined ? options.s2 : ((D + lambda1*(-h2*x + d2*xx + c2*y - b2*yx)) / (F + lambda1*K)); + t2 = options.t2 !== undefined ? options.t2 : ((E + lambda1*(h2 - d2*x - b2*y + a2*yx)) / (F + lambda1*K)); + x_prime = (s1 - s2) / (t2 - t1); + + error1 = ((k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) - lambda1 * Math.pow(y - (s1 + t1*x), 2)) || Number.MAX_VALUE; + error2 = ((k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) + lambda1 * Math.pow(y - (s2 + t2*x), 2)) || Number.MAX_VALUE; + } else if (t1 != t2) + continue; + } + + if (error1 + error2 < error1_best + error2_best) + setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x); + } + + return { + complexity: x_best, + s1: s1_best, + t1: t1_best, + stdev1: Math.sqrt(error1_best / n1_best), + s2: s2_best, + t2: t2_best, + stdev2: Math.sqrt(error2_best / n2_best), + error: error1_best + error2_best, + n1: n1_best, + n2: n2_best + }; + } +}); + +Utilities.extendObject(Regression, { + bootstrap: function(samples, iterationCount, processResample, confidencePercentage) + { + var sampleLength = samples.length; + var resample = new Array(sampleLength); + + var bootstrapEstimator = new Experiment; + var bootstrapData = new Array(iterationCount); + + Pseudo.resetRandomSeed(); + for (var i = 0; i < iterationCount; ++i) { + for (var j = 0; j < sampleLength; ++j) + resample[j] = samples[Math.floor(Pseudo.random() * sampleLength)]; + + var resampleResult = processResample(resample); + bootstrapEstimator.sample(resampleResult); + bootstrapData[i] = resampleResult; + } + + bootstrapData.sort(function(a, b) { return a - b; }); + return { + confidenceLow: bootstrapData[Math.round((iterationCount - 1) * (1 - confidencePercentage) / 2)], + confidenceHigh: bootstrapData[Math.round((iterationCount - 1) * (1 + confidencePercentage) / 2)], + median: bootstrapData[Math.round(iterationCount / 2)], + mean: bootstrapEstimator.mean(), + data: bootstrapData, + confidencePercentage: confidencePercentage + }; + } +}); diff --git a/third_party/webkit/PerformanceTests/MotionMark/resources/strings.js b/third_party/webkit/PerformanceTests/MotionMark/resources/strings.js new file mode 100644 index 0000000000..b58f67e991 --- /dev/null +++ b/third_party/webkit/PerformanceTests/MotionMark/resources/strings.js @@ -0,0 +1,51 @@ +var Strings = { + text: { + testName: "Test Name", + score: "Score" + }, + json: { + marks: "marks", + samplingStartTimeOffset: "Start sampling", + samplingEndTimeOffset: "End sampling", + + samples: "samples", + dataFieldMap: "dataFieldMap", + controller: "controller", + time: "time", + complexity: "complexity", + complexityAverage: "complexityAverage", + frameLength: "frameLength", + smoothedFrameLength: "smoothedFrameLength", + + result: "result", + configuration: "configuration", + score: "score", + scoreLowerBound: "scoreLowerBound", + scoreUpperBound: "scoreUpperBound", + bootstrap: "bootstrap", + measurements: { + average: "average", + concern: "concern", + stdev: "stdev", + percent: "percent" + }, + + regressions: { + startIndex: "startIndex", + endIndex: "endIndex", + segment1: "segment1", + segment2: "segment2", + profile: "profile" + }, + + profiles: { + slope: "slope", + flat: "flat" + }, + + results: { + iterations: "iterationsResults", + tests: "testsResults" + } + } +}; -- cgit v1.2.3