2 Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 Use of this source code is governed by a BSD-style license that can be
4 found in the LICENSE file.
8 * @fileoverview Handles drawing a general Chrome Endure graph.
11 document.title = Config.title + ' - ' + Config.buildslave;
13 var unitsX = 'unitsX';
14 var unitsY = 'unitsY';
15 var unitsYOther = null;
17 var revisionNumbers = [];
18 var graphDataOtherRows = null;
24 var params = ParseParams();
27 * Encapsulates a *-summary.dat file.
30 * @param {string} data Raw data from a *-summary.dat file.
33 this.rows = data.split('\n');
34 this.length = this.rows.length;
38 * Returns the row at the given index.
40 * @param {number} i The index of a row of data from the *-summary.dat file.
41 * @return {Object} An object representing a row of data from the input file.
43 Rows.prototype.get = function(i) {
44 if (!this.rows[i].length) return null;
45 var row = jsonToJs(this.rows[i]);
46 row.revision = isNaN(row['rev']) ? row['rev'] : parseInt(row['rev']);
51 * Gets the current URL, but without the 'lookout' parameter.
53 * @return {string} The current URL, but without the 'lookout' parameter.
56 new_url = window.location.href;
57 new_url = new_url.replace(/\&lookout=1/, '');
62 * Reports an error message on the webpage.
64 * @param {string} error An error message to display on the page.
66 function reportError(error) {
67 document.getElementById('output').innerHTML = '<p>' + error + '</p>';
71 * Converts a JSON string into a Javascript object.
73 * @param {string} data A string in JSON format.
74 * @return {Object} A Javascript object computed from the JSON string.
76 function jsonToJs(data) {
77 return eval('(' + data + ')')
81 * Causes the page to navigate to another graph.
83 * @param {string} graph The name of the graph to which to navigate.
85 function goTo(graph) {
87 window.location.href = MakeURL(params);
91 * Returns a function that will navigate the page to another graph.
93 * @param {string} graph The name of the graph to which to navigate.
94 * @return {Function} A function that will navigate the page to another graph.
96 function goToClosure(graph) {
97 return function(){goTo(graph)};
101 * Changes the event being overlayed on the graph.
103 * @param {string} eventName The name of the event to overlay on the graph.
105 function changeEventCompare(eventName) {
106 delete params.revisionOther;
107 delete params.graphOther;
108 if (eventName == 'None') {
110 window.location.href = MakeURL(params);
112 params.event = eventName;
113 window.location.href = MakeURL(params);
118 * Changes the other measurement being overlayed on top of an original line on
121 * @param {string} graphName The name of the other graph to overlay on top of
122 * the existing graph.
124 function changeMeasurementCompare(graphName) {
125 delete params.revisionOther;
127 if (graphName == 'None') {
128 delete params.graphOther;
129 window.location.href = MakeURL(params);
131 params.graphOther = graphName;
132 window.location.href = MakeURL(params);
137 * Changes the number of the other revision to compare against on the graph.
139 * @param {string} revision The revision number of the other line to plot on
142 function changeRevisionCompare(revision) {
143 delete params.graphOther;
145 if (revision == 'None') {
146 delete params.revisionOther;
147 window.location.href = MakeURL(params);
149 params.revisionOther = revision;
150 window.location.href = MakeURL(params);
155 * Changes the displayed revision number of the graph line.
157 * @param {string} revision The revision number of the graph to display.
159 function changeRevision(revision) {
160 delete params.revisionOther;
161 delete params.graphOther;
163 params.revision = revision;
164 window.location.href = MakeURL(params);
168 * Initializes the UI for changing the revision number of the displayed graph.
170 function initRevisionOptions() {
171 var html = '<table cellpadding=5><tr><td>';
172 html += '<b>Chrome revision:</b> ';
173 html += '<select onchange=\"changeRevision(this.value)\">';
174 for (var i = 0; i < revisionNumbers.length; ++i) {
175 html += '<option id=\"r' + revisionNumbers[i] + '\"';
176 if (revisionNumbers[i] == params.revision)
177 html += 'selected=\"true\"';
178 html += '>' + revisionNumbers[i] + '</option>';
180 html += '</select></td></tr></table>';
182 document.getElementById('revisions').innerHTML = html;
186 * Initializes the UI for changing what is compared against the current line
187 * on the displayed graph.
189 function initComparisonOptions() {
190 var html = '<table cellpadding=5>';
191 html += '<tr><td><b>Compare with (select one):</b></td></tr>';
193 html += '<tr><td> Another run: ';
194 html += '<select onchange=\"changeRevisionCompare(this.value)\">';
195 html += '<option selected=\"true\">None</option>';
196 for (var i = 0; i < revisionNumbers.length; ++i) {
197 html += '<option id=\"r' + revisionNumbers[i] + '\"';
198 if (revisionNumbers[i] == params.revisionOther)
199 html += 'selected=\"true\"';
200 html += '>' + revisionNumbers[i] + '</option>';
202 html += '</select></td></tr>'
204 html += '<tr><td> Another measurement of same run: ';
205 html += '<select onchange=\"changeMeasurementCompare(this.value)\">';
206 html += '<option selected=\"true\">None</option>';
207 for (var i = 0; i < graphList.length; ++i) {
208 var graph = graphList[i];
209 html += '<option id=\"r' + graph.name + '\"';
210 if (graph.name == params.graphOther)
211 html += 'selected=\"true\"';
212 html += '>' + graph.name + '</option>';
214 html += '</select></td></tr>';
216 html += '<tr><td> Event overlay: ';
217 if (eventTypes.length >= 1) {
218 html += '<select onchange=\"changeEventCompare(this.value)\">';
219 html += '<option selected=\"true\">None</option>';
220 for (var i = 0; i < eventTypes.length; ++i) {
221 var eventType = eventTypes[i];
222 html += '<option id=\"' + eventType + '\"';
223 if (eventType == params.event)
224 html += 'selected=\"true\"';
225 html += '>' + eventType + '</option>';
229 html += ' <i><font size=-1>No events for this revision</font></i>';
231 html += '</td></tr></table>';
233 document.getElementById('comparisons').innerHTML = html;
237 * Initializes the UI for the tabs at the top of a graph to change the displayed
240 function initPlotSwitcher(tabs) {
241 var switcher = document.getElementById('switcher');
242 for (var i = 0; i < tabs.length; ++i) {
243 var is_selected = tabs[i] == params.graph;
244 var tab = document.createElement(is_selected ? 'span' : 'a');
245 tab.appendChild(document.createTextNode(tabs[i] + ' '));
247 tab.addEventListener('click', goToClosure(tabs[i]), false);
248 switcher.appendChild(tab);
253 * Adds data to existing arrays indicating what data should be plotted.
255 * @param {number} revisionNum The revision number of the data to plot.
256 * @param {Rows} dataRows The |Rows| object containing the plot data.
257 * @param {Array} plotData A list of data lines to plot, to which new data will
259 * @param {Array} dataDescriptions A list of string descriptions corresponding
260 * to data lines in |plotData|, to which new data will be appended.
261 * @return {Object} A row object specified by {@code revisionNum} on success,
262 * otherwise returns null.
264 function addToPlotData(revisionNum, dataRows, plotData, dataDescriptions) {
265 // Get data for the revision number(s) to plot.
267 for (var i = 0; i < dataRows.length; ++i) {
268 var row = dataRows.get(i);
269 if (row && row.revision == revisionNum) {
279 if (!row.stack_order) {
280 reportError('No stack order was specified.');
283 var traceList = row.stack_order;
285 // Identify the (single) trace name associated with this revision.
286 var traceName = null;
287 for (var t in row.traces) {
289 reportError('Only one trace per revision is supported for ' +
290 'non-stacked graphs.');
295 var traceList = [traceName];
299 for (var i = 0, traceName; traceName = traceList[i]; ++i) {
300 var trace = row.traces[traceName];
302 reportError('No specified trace was found.');
307 for (var j = 0, point; point = trace[j]; ++j) {
308 points.push([parseFloat(point[0]), parseFloat(point[1])]);
311 dataDescriptions.push(traceName + ' [r' + row.revision + ']');
315 lines = graphUtils.stackFrontToBack(graphUtils.interpolate(lines));
318 for (var i = 0, line; line = lines[i]; ++i) {
326 * Callback for when a *-summary.dat data file has been read.
328 * @param {string} data The string data from the inputted text file.
329 * @param {string} error A string error message, in case an error occurred
330 * during the file read.
332 function receivedSummary(data, error) {
338 var errorMessages = '';
339 var rows = new Rows(data);
341 // Build and order a list of revision numbers.
342 revisionNumbers = [];
343 for (var i = 0; i < rows.length; ++i) {
344 var row = rows.get(i);
347 revisionNumbers.push(row.revision);
349 revisionNumbers.sort(
350 function(a, b) { return parseInt(a, 10) - parseInt(b, 10) });
352 // Get the revision number to plot.
353 if (!('revision' in params) || params.revision == '') {
354 if (revisionNumbers.length >= 2 && 'lookout' in params) {
355 // Since the last graph (test run) might still be in progress, get the
356 // second-to-last graph to display on the summary page. That one
357 // is assumed to have finished running to completion.
358 params.revision = revisionNumbers[revisionNumbers.length-2];
360 if (revisionNumbers.length >= 1) {
361 params.revision = revisionNumbers[revisionNumbers.length-1];
363 reportError('No revision information to plot.');
369 var plotData = []; // plotData is a list of graph lines; each graph line is
370 // a list of points; each point is a list of 2 values,
371 // representing the (x, y) pair.
372 var dataDescriptions = [];
374 var row = addToPlotData(params.revision, rows, plotData, dataDescriptions);
376 errorMessages += 'No data for the specified revision.<br>';
378 // From index {@code plotData.length} onwards, any graph lines in
379 // {@code plotData} are considered to be part of a second set of graphs.
380 var graphsOtherStartIndex = plotData.length;
383 if ('revisionOther' in params) {
384 rowOther = addToPlotData(params.revisionOther, rows, plotData,
387 errorMessages += 'No data for the revision to compare against.<br>';
390 if ('graphOther' in params) {
391 rowOther = addToPlotData(params.revision, graphDataOtherRows, plotData,
394 for (var i = 0; i < graphList.length; ++i) {
395 if (graphList[i].name == params.graphOther) {
396 unitsYOther = graphList[i].units;
401 errorMessages += 'No data for the measurement to compare against.<br>';
405 // Identify the events for the current revision.
407 for (var index = 0; index < eventRows.length; ++index) {
408 var info = eventRows.get(index);
409 if (params.revision == info['rev']) {
414 if (eventInfo != null) {
415 for (var key in eventInfo['events']) {
416 eventTypes.push(key);
421 // Get data for the events to display, if one was requested in the params.
422 var eventNameToPlot = null;
423 var eventInfoToPlot = null;
424 if ('event' in params && eventInfo != null) {
425 for (var key in eventInfo['events']) {
426 if (key == params['event']) {
427 eventInfoToPlot = eventInfo['events'][key];
428 eventNameToPlot = key;
434 if (errorMessages == '') {
435 var plotter = new Plotter(
438 eventNameToPlot, eventInfoToPlot,
439 unitsX, unitsY, unitsYOther, graphsOtherStartIndex,
440 document.getElementById('output'),
443 rowOther && !!rowOther.stack);
447 errorMessages = '<br><br><br><table border=2 cellpadding=5><tr><td>' +
448 errorMessages + '</td></tr></table><br><br>';
449 document.getElementById('output').innerHTML = errorMessages;
452 if (!('lookout' in params)) {
453 initRevisionOptions();
454 initComparisonOptions();
459 * Callback for when a second *-summary.dat data file has been read, in the
460 * event that a second graph line is being overlayed on top of an existing
463 * @param {string} data The string data from the inputted text file.
464 * @param {string} error A string error message, in case an error occurred
465 * during the file read.
467 function receivedSummaryGraphOther(data, error) {
473 graphDataOtherRows = new Rows(data);
474 Fetch(escape(params.graph) + '-summary.dat', receivedSummary);
478 * Callback for when an event info file has been read.
480 * @param {string} data The string data from the inputted text file.
481 * @param {string} error A string error message, in case an error occurred
482 * during the file read.
484 function receivedEvents(data, error) {
486 eventRows = new Rows(data);
491 * Callback for when a graphs.dat data file has been read.
493 * @param {string} data The string data from the inputted text file.
494 * @param {string} error A string error message, in case an error occurred
495 * during the file read.
497 function receivedGraphList(data, error) {
502 graphList = jsonToJs(data);
504 if (!('graph' in params) || params.graph == '')
505 if (graphList.length > 0)
506 params.graph = graphList[0].name
508 // Add a selection tab for each graph, and find the units for the selected
509 // one while we're at it.
511 for (var index = 0; index < graphList.length; ++index) {
512 var graph = graphList[index];
513 tabs.push(graph.name);
514 if (graph.name == params.graph) {
515 unitsX = graph.units_x;
516 unitsY = graph.units;
519 initPlotSwitcher(tabs);
525 * Starts fetching a *-summary.dat file.
527 function fetchSummary() {
528 if ('graphOther' in params) {
529 // We need to overlay a second graph over the first one, so we need to
530 // fetch that summary data too. Do it first.
531 Fetch(escape(params.graphOther) + '-summary.dat',
532 receivedSummaryGraphOther);
534 Fetch(escape(params.graph) + '-summary.dat',
540 * Starts fetching an event info file.
542 function fetchEvents() {
543 Fetch('_EVENT_-summary.dat', receivedEvents);
547 * Starts fetching a graphs.dat file.
549 function fetchGraphList() {
550 Fetch('graphs.dat', receivedGraphList);
553 window.addEventListener('load', fetchGraphList, false);