// vim: set ts=4 sw=4 tw=99 et: "use strict"; var AWFY = { }; 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.lastHash = null; AWFY.lastRefresh = 0; AWFY.request = function (files, callback) { var url = window.location.protocol + '//' + window.location.host + window.location.pathname; if (url[url.length - 1] != '/') url += '/'; url += 'data/'; 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 ($('#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) { var lines = []; var info = []; for (var i = 0; i < blobgraph.lines.length; i++) { var blobline = blobgraph.lines[i]; var points = []; for (var j = 0; j < blobline.data.length; j++) { var point = blobline.data[j]; 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 (name, graph) { var elt = $('#' + name + '-graph'); if (!elt.length) return; var display = elt.data('awfy-display'); if (!display) { display = new Display(this, name, elt); elt.data('awfy-display', display); } if (display.shouldRefresh()) { display.setup(graph); display.draw(); } this.aggregate[name] = graph; } 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) { 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, 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, 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; 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); count += 1; } var score = average ? average : null; newline.data.push([timelist.length, score]); if (count) newinfo.data.push([average, first, last]) else newinfo.data.push([average, first]) } 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 }; } 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; files.push(name); } } var zoom = function (received) { this.computeZoom(display, received, start_t, end_t); } this.request(files, zoom.bind(this)); } AWFY.showOverview = function () { this.reset('overview'); $('#breakdown').empty(); $('#breakdown').hide(); $('.graph-container').show(); this.suiteName = null; this.panes = [$('#ss-graph'), /*$('#v8real-graph'),*/ $('#kraken-graph'), $('#octane-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.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; $('').html('' + id + '').appendTo(breakdown); var div = $(''); div.appendTo(breakdown); $('