Loading...
+ +
diff --git a/website/servo/awfy.js b/website/servo/awfy.js new file mode 100755 index 0000000..145687d --- /dev/null +++ b/website/servo/awfy.js @@ -0,0 +1,1059 @@ +// vim: set ts=4 sw=4 tw=99 et: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; +var AWFY = { }; + +AWFY.DEFAULT_MACHINE_ID = 1; +AWFY.refreshTime = 60 * 5; +AWFY.machineId = 0; +AWFY.hasLegend = false; +AWFY.panes = []; +AWFY.queryParams = null; +AWFY.aggregate = null; +AWFY.xhr = []; +AWFY.view = 'none'; +AWFY.suiteName = null; +AWFY.subtest = null; +AWFY.lastHash = null; +AWFY.lastRefresh = 0; + +AWFY.request = function (files, callback) { + var url = window.location.protocol + '//' + + window.location.host; + if (url[url.length - 1] != '/') + url += '/'; + url += 'data.php?file='; + + var count = 0; + var received = new Array(files.length); + var done = (function (jqXHR, textStatus) { + count++; + if (count == files.length) + callback(received); + this.xhr.splice(this.xhr.lastIndexOf(jqXHR), 1); + }).bind(this); + + for (var i = 0; i < files.length; i++) { + var success = (function (index) { + return function (data, textStatus, jqXHR) { + received[index] = data; + }; + })(i); + var req = { async: true, + complete: done, + success: success, + cache: false + }; + this.xhr.push($.ajax(url + files[i] + '.json', req)); + } +} + +AWFY.pushState = function () { + // Build URL query. + var vars = [] + vars.push('machine=' + this.machineId); + + if (this.view == 'breakdown') { + vars.push('view=breakdown'); + vars.push('suite=' + this.suiteName); + } + if (this.view == 'single') { + vars.push('view=single'); + vars.push('suite=' + this.suiteName); + if (this.subtest) + vars.push('subtest=' + this.subtest); + if (this.start && this.end) { + vars.push('start='+this.start); + vars.push('end='+this.end); + } + } + + if ($('#about').is(':visible')) + vars.push('about=1'); + + var text = '#' + vars.join('&'); + this.lastHash = text; + if (window.history.pushState) + window.history.pushState(null, 'AreWeFastYet', text); + else + window.location.hash = '#' + text; +} + +AWFY.loadAggregateGraph = function (blobgraph) { + if (!blobgraph) + return; + var lines = []; + var info = []; + for (var i = 0; i < blobgraph.lines.length; i++) { + var blobline = blobgraph.lines[i]; + + var points = []; + var k = 0; + for (var j = 0; j < blobline.data.length; j++) { + var point = blobline.data[j]; + // When there is no point, we normally just push a null point. + // This results in a line stop. Now e.g. firefox OS has 4 different + // engines reporting on different times. So every point has a gap. + // To counteract this we still draw a line (by not giving null points), + // if there has been less than the amount of lines (*1.5). + if (!point) { + if (k++ < blobgraph.lines.length*1.5) + continue + } else { + k = 0; + } + var score = point && point[0] + ? point[0] + : null; + points.push([j, score]); + } + + var mode = AWFYMaster.modes[blobline.modeid]; + if (!mode) + continue; + + var line = { color: mode.color, data: points }; + lines.push(line); + info.push(blobline); + } + + var graph = { lines: lines, + direction: blobgraph.direction, + aggregate: blobgraph.aggregate, + timelist: blobgraph.timelist, + earliest: blobgraph.earliest, + info: info + }; + return graph; +} + +AWFY.displayNewGraph = function (id, domid, graph) { + var elt = $('#' + domid + '-graph'); + var title = $('#' + domid + '-title'); + if (!elt.length) + return; + if (!graph) { + this.aggregate[id] = undefined; + elt.hide(); + title.hide(); + return; + } + elt.show(); + title.show(); + var display = elt.data('awfy-display'); + if (!display) { + display = new Display(this, id, domid, elt); + elt.data('awfy-display', display); + } + if (display.shouldRefresh()) { + display.setup(graph); + display.draw(); + } + this.aggregate[id] = graph; + if (this.start && this.end) { + AWFY.requestZoom(display, "condensed", this.start, this.end) + display.zoomInfo.level = 'month'; + } +} + +AWFY.drawLegend = function () { + // Draw the legend if needed. + if (this.hasLegend) + return; + + var legend = $("#legend"); + + legend.empty(); + + var modes = []; + for (var modename in AWFYMaster.modes) { + var mode = AWFYMaster.modes[modename]; + // hack - strip jm+ti, bc + if (modename == 12 || modename == 15) + continue; + if (AWFY.machineId != 14 && modename == 16) + continue; + if (!mode.used) + continue; + modes.push(mode); + } + + for (var i = 0; i < modes.length; i++) { + var mode = modes[i]; + var vendor = AWFYMaster.vendors[mode.vendor_id]; + if (!vendor) + continue; + var item = $('
'); + var link = $('' + + vendor.browser + + ' (' + + mode.name + + ')'); + + var onClick = (function (awfy, mode, link) { + return (function () { + if (mode.hidden) { + mode.hidden = false; + link.css('color', '#000000'); + } else { + mode.hidden = true; + link.css('color', '#cccccc'); + } + for (var i = 0; i < this.panes.length; i++) { + var elt = this.panes[i]; + var display = elt.data('awfy-display'); + if (!display) + continue; + display.draw(); + } + return false; + }).bind(awfy); + })(this, mode, link); + link.click(onClick); + + if (mode.hidden) + link.css('color', '#cccccc'); + else + link.css('color', '#000000'); + + link.appendTo(item); + item.appendTo(legend); + } + + this.hasLegend = true; +} + +AWFY.computeBreakdown = function (data, id, domid) { + var blob = typeof data == "string" + ? JSON.parse(data) + : data; + + // Should we handle version changes better? + if (blob.version != AWFYMaster.version) { + window.location.reload(); + return; + } + + var graph = this.loadAggregateGraph(blob['graph']); + this.displayNewGraph(id, domid, graph); +} + +AWFY.computeAggregate = function (received) { + var blob = typeof received[0] == "string" + ? JSON.parse(received[0]) + : received[0]; + + // Should we handle version changes better? + if (blob.version != AWFYMaster.version) { + window.location.reload(); + return; + } + + var graphs = { }; + for (var name in blob.graphs) { + var blobgraph = blob.graphs[name]; + if (!blobgraph) + continue; + graphs[name] = this.loadAggregateGraph(blobgraph); + } + + // Save this for if/when we need to zoom out. + this.aggregate = graphs; + + for (var id in graphs) { + this.displayNewGraph(id, id, graphs[id]); + } + + this.drawLegend(); +} + +AWFY.mergeJSON = function (blobs) { + var lines = { }; + var timelist = []; + + // We're guaranteed the blobs are in sorted order, which makes this simpler. + for (var i = 0; i < blobs.length; i++) { + var blob = blobs[i]; + + // Should we handle version changes better? + if (blob.version != AWFYMaster.version) { + window.location.reload(); + return; + } + + for (var j = 0; j < blob.graph.lines.length; j++) { + var blobline = blob.graph.lines[j]; + + var line = lines[blobline.modeid]; + if (!line) { + var points = []; + var info = []; + + // We have to pre-fill the array with slots for each blob + // we may have missed. + for (var k = 0; k < timelist.length; k++) { + points.push(null); + info.push(null); + } + + line = { points: points, info: info }; + lines[blobline.modeid] = line; + } + + var points = line.points; + var info = line.info; + + for (var k = 0; k < blobline.data.length; k++) { + var point = blobline.data[k]; + var score = point && point[0] + ? point[0] + : null; + points.push([timelist.length + k, score]); + info.push(point); + } + } + + for (var j = 0; j < blob.graph.timelist.length; j++) + timelist.push(blob.graph.timelist[j]); + + // If we missed updating any line, pre-fill it with null points. + for (var modeid in lines) { + var line = lines[modeid]; + if (line.points.length == timelist.length) + continue; + for (var j = line.points.length; j < timelist.length; j++) { + line.points.push(null); + line.info.push(null); + } + } + } + + var actual = []; + var info = []; + for (var modename in lines) { + if (!(modename in AWFYMaster.modes)) + continue; + var line = { data: lines[modename].points, + color: AWFYMaster.modes[modename].color + }; + actual.push(line); + info.push({ 'modeid': parseInt(modename), + 'data': lines[modename].info }); + } + + var graph = { lines: actual, + aggregate: false, + timelist: timelist, + info: info + }; + return graph; +} + +AWFY.condense = function (graph, max) { + if (graph.timelist.length <= max) + return graph; + + var slice = graph.timelist.length / max; + + var timelist = []; + var lines = []; + var info = []; + + // Setup the new structures. + for (var i = 0; i < graph.lines.length; i++) { + var newline = { 'color': graph.lines[i].color, + 'data': [] + }; + var newinfo = { 'modeid': graph.info[i].modeid, + 'data': [] + }; + lines.push(newline); + info.push(newinfo); + } + + var pos = 0; + for (var i = 0; i < max; i++) { + var start = Math.round(pos); + + for (var lineno = 0; lineno < lines.length; lineno++) { + var oldinfo = graph.info[lineno]; + var newline = lines[lineno]; + var newinfo = info[lineno]; + + var average = 0; + var count = 0; + var first = null; + var last = null; + var suite_version = null + var id = null + for (var j = start; j < pos + slice && j < oldinfo.data.length; j++) { + var point = oldinfo.data[j]; + if (!point || !point[0]) + continue; + if (!first) + first = point[1]; + if (point.length > 1 && point[2]) + last = point[2]; + else + last = first + average = ((average * count) + point[0]) / (count + 1); + suite_version = point[3] + id = point[4] + count += 1; + } + + var score = average ? average : null; + id = count == 1 ? id : null + newline.data.push([timelist.length, score]); + newinfo.data.push([average, first, last, suite_version, id]) + } + + timelist.push(graph.timelist[start]); + pos += slice; + } + + return { info: info, + lines: lines, + timelist: timelist, + direction: graph.direction }; +} + +AWFY.trim = function (graph, start, end) { + var timelist = []; + var lines = []; + var infos = []; + + // Setup the new structures. + for (var i = 0; i < graph.lines.length; i++) { + var newline = { 'color': graph.lines[i].color, + 'data': [] + }; + var newinfo = { 'modeid': graph.info[i].modeid, + 'data': [] + }; + lines.push(newline); + infos.push(newinfo); + } + + // Whether |end| is inclusive is not really clear, actually. + for (var i = start; i < end; i++) + timelist.push(graph.timelist[i]); + + for (var i = 0; i < graph.lines.length; i++) { + var oldline = graph.lines[i]; + var oldinfo = graph.info[i]; + var line = lines[i]; + var info = infos[i]; + for (var j = start; j < end; j++) { + var point = oldline.data[j]; + line.data.push([j - start, point ? point[1] : null]); + info.data.push(oldinfo.data[j]); + } + } + + return { lines: lines, + info: infos, + timelist: timelist, + direction: graph.direction, + start: start, + end: end + }; +} + +AWFY.computeZoom = function (display, received, start, end) { + // Get JSON blobs for each received text. + var blobs = []; + for (var i = 0; i < received.length; i++) { + if (!received[i]) + continue; + if (typeof received[i] == "string") + blobs.push(JSON.parse(received[i])); + else + blobs.push(received[i]); + } + + if (!blobs.length) { + display.cancelZoom(); + return; + } + + var graph = this.mergeJSON(blobs); + display.completeZoom(graph, start, end); +} + +AWFY.findX = function (graph, time) { + for (var i = 0; i < graph.timelist.length; i++) { + if (graph.timelist[i] >= time) + break; + } + return i; +} + +AWFY.requestZoom = function (display, kind, start_t, end_t) { + // Figure out the list of dates we'll need to query. + var files = []; + + var start = new Date(start_t * 1000); + var end = new Date(end_t * 1000); + for (var year = start.getUTCFullYear(); year <= end.getUTCFullYear(); year++) { + var firstMonth = (year == start.getUTCFullYear()) + ? start.getUTCMonth() + 1 + : 1; + var lastMonth = (year == end.getUTCFullYear()) + ? end.getUTCMonth() + 1 + : 12; + for (var month = firstMonth; month <= lastMonth; month++) { + var name = kind + '-' + + display.id + '-' + + this.machineId + '-' + + year + '-' + + month; + if (this.view == 'breakdown') + name = 'bk-' + name; + else if (this.view == 'single' && this.subtest) + name = 'bk-' + name; + files.push(name); + } + } + + var zoom = function (received) { + this.computeZoom(display, received, start_t, end_t); + } + + this.request(files, zoom.bind(this)); +} + +AWFY.trackZoom = function (start, end) { + // Only track in single modus + if (this.view != 'single') + return; + + this.start = start; + this.end = end; + this.pushState(); +} + +AWFY.showOverview = function () { + this.reset('overview'); + + $('#breakdown').empty(); + $('#breakdown').hide(); + + $('.graph-container').show(); + + this.suiteName = null; + this.subtest = null + this.start = null + this.end = null + this.panes = [$('#dromaeo-graph')]; + + this.request(['aggregate-' + this.machineId], this.computeAggregate.bind(this)); + this.lastRefresh = Date.now(); +} + +AWFY.showBreakdown = function (name) { + this.reset('breakdown'); + + // Clear the breakdown view. + var breakdown = $('#breakdown'); + breakdown.empty() + + $('.graph-container').hide(); + breakdown.show(); + + this.suiteName = name; + this.start = null + this.end = null + this.subtest = null; + this.panes = []; + + var total = 0; + + // Create a div for each sub-test. + var suite = AWFYMaster.suites[name]; + for (var i = 0; i < suite.tests.length; i++) { + var test = suite.tests[i]; + var id = name + '-' + test; + var domid = id.replace(/ /g,'-').replace(/\./g, '-'); + ( function (name, test) { + var title = $('').click( + (function (event) { + this.showSingle(name, test, null, null); + this.pushState(); + return false; + }).bind(this)) + .html('' + id + '') + .appendTo(breakdown); + title.hide(); + }.bind(this) )(name, test) + var div = $(''); + div.appendTo(breakdown); + div.hide(); + + this.panes.push(div); + + var callback = (function (id, domid) { + return (function (received) { + if (received[0]) + this.computeBreakdown(received[0], id, domid); + if (++total == suite.tests.length) + this.drawLegend(); + }).bind(this); + }).bind(this)(id, domid); + + // Fire off an XHR request for each test. + var file = 'bk-aggregate-' + id + '-' + this.machineId; + this.request([file], callback); + } + this.lastRefresh = Date.now(); +} + +AWFY.showSingle = function (name, subtest, start, end) { + this.reset('single'); + + // Clear the breakdown view. + var breakdown = $('#breakdown'); + breakdown.empty() + + $('.graph-container').hide(); + breakdown.show(); + + this.suiteName = name; + this.subtest = subtest; + this.start = start; + this.end = end; + this.panes = []; + + // Test existance of subtest + var suite = AWFYMaster.suites[name]; + var found = false; + for (var i = 0; i < suite.tests.length; i++) { + var test = suite.tests[i]; + if (subtest == test) { + found = true; + break; + } + } + + if (found) { + var id = name + '-' + test; + var domid = id.replace(/ /g,'-').replace(/\./g, '-'); + var title = $('').html('' + id + '').appendTo(breakdown); + title.hide(); + var div = $(''); + div.appendTo(breakdown); + div.hide(); + $('Who maintains this site?
+This site is maintained by Mozilla-Servo team.
+How does it work?
+It is automated. Throughout the day, we checkout the latest source code to Mozilla Servo, and compile it. Then we run it through some benchmark suites, and tally up the scores into a database. This data gets exported as JSON which can then be easily plotted.
+What are the graphs?
+The graph currently shows Servo's performance on Dromaeo test suite.
+What do the hover tips mean?
+"Source" is where we got the engine from. "Tested" is when we downloaded the engine, compiled, and tested it. "Rev" is the unique point in the engine's revision history we tested. If the datapoint represents a range, there may be multiple revs. These numbers/strings are for developers to see which changes happened in between points in the graph.
+Is this open source?
+Fo' sho', github.com/dhananjay92/arewefastyet
+(Adapted from github.com/h4writer/arewefastyet/)
+Suggestions?
+ +