- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / test / functional / perf / endure_graphs / endure_plotter.js
1 /*
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.
5 */
6
7 /**
8  * @fileoverview Handles drawing a general Chrome Endure graph.
9  */
10
11 document.title = Config.title + ' - ' + Config.buildslave;
12
13 var unitsX = 'unitsX';
14 var unitsY = 'unitsY';
15 var unitsYOther = null;
16 var graphList = [];
17 var revisionNumbers = [];
18 var graphDataOtherRows = null;
19
20 var eventRows = null;
21 var eventTypes = [];
22 var eventInfo = null;
23
24 var params = ParseParams();
25
26 /**
27  * Encapsulates a *-summary.dat file.
28  * @constructor
29  *
30  * @param {string} data Raw data from a *-summary.dat file.
31  */
32 function Rows(data) {
33   this.rows = data.split('\n');
34   this.length = this.rows.length;
35 }
36
37 /**
38  * Returns the row at the given index.
39  *
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.
42  */
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']);
47   return row;
48 };
49
50 /**
51  * Gets the current URL, but without the 'lookout' parameter.
52  *
53  * @return {string} The current URL, but without the 'lookout' parameter.
54  */
55 function get_url() {
56   new_url = window.location.href;
57   new_url = new_url.replace(/\&lookout=1/, '');
58   return new_url;
59 }
60
61 /**
62  * Reports an error message on the webpage.
63  *
64  * @param {string} error An error message to display on the page.
65  */
66 function reportError(error) {
67   document.getElementById('output').innerHTML = '<p>' + error + '</p>';
68 }
69
70 /**
71  * Converts a JSON string into a Javascript object.
72  *
73  * @param {string} data A string in JSON format.
74  * @return {Object} A Javascript object computed from the JSON string.
75  */
76 function jsonToJs(data) {
77   return eval('(' + data + ')')
78 }
79
80 /**
81  * Causes the page to navigate to another graph.
82  *
83  * @param {string} graph The name of the graph to which to navigate.
84  */
85 function goTo(graph) {
86   params.graph = graph;
87   window.location.href = MakeURL(params);
88 }
89
90 /**
91  * Returns a function that will navigate the page to another graph.
92  *
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.
95  */
96 function goToClosure(graph) {
97   return function(){goTo(graph)};
98 }
99
100 /**
101  * Changes the event being overlayed on the graph.
102  *
103  * @param {string} eventName The name of the event to overlay on the graph.
104  */
105 function changeEventCompare(eventName) {
106   delete params.revisionOther;
107   delete params.graphOther;
108   if (eventName == 'None') {
109     delete params.event;
110     window.location.href = MakeURL(params);
111   } else {
112     params.event = eventName;
113     window.location.href = MakeURL(params);
114   }
115 }
116
117 /**
118  * Changes the other measurement being overlayed on top of an original line on
119  * the graph.
120  *
121  * @param {string} graphName The name of the other graph to overlay on top of
122  *     the existing graph.
123  */
124 function changeMeasurementCompare(graphName) {
125   delete params.revisionOther;
126   delete params.event;
127   if (graphName == 'None') {
128     delete params.graphOther;
129     window.location.href = MakeURL(params);
130   } else {
131     params.graphOther = graphName;
132     window.location.href = MakeURL(params);
133   }
134 }
135
136 /**
137  * Changes the number of the other revision to compare against on the graph.
138  *
139  * @param {string} revision The revision number of the other line to plot on
140  *     the graph.
141  */
142 function changeRevisionCompare(revision) {
143   delete params.graphOther;
144   delete params.event;
145   if (revision == 'None') {
146     delete params.revisionOther;
147     window.location.href = MakeURL(params);
148   } else {
149     params.revisionOther = revision;
150     window.location.href = MakeURL(params);
151   }
152 }
153
154 /**
155  * Changes the displayed revision number of the graph line.
156  *
157  * @param {string} revision The revision number of the graph to display.
158  */
159 function changeRevision(revision) {
160   delete params.revisionOther;
161   delete params.graphOther;
162   delete params.event;
163   params.revision = revision;
164   window.location.href = MakeURL(params);
165 }
166
167 /**
168  * Initializes the UI for changing the revision number of the displayed graph.
169  */
170 function initRevisionOptions() {
171   var html = '<table cellpadding=5><tr><td>';
172   html += '<b>Chrome revision:</b>&nbsp;';
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>';
179   }
180   html += '</select></td></tr></table>';
181
182   document.getElementById('revisions').innerHTML = html;
183 }
184
185 /**
186  * Initializes the UI for changing what is compared against the current line
187  * on the displayed graph.
188  */
189 function initComparisonOptions() {
190   var html = '<table cellpadding=5>';
191   html += '<tr><td><b>Compare with (select one):</b></td></tr>';
192
193   html += '<tr><td>&nbsp;&nbsp;&nbsp;Another run:&nbsp;';
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>';
201   }
202   html += '</select></td></tr>'
203
204   html += '<tr><td>&nbsp;&nbsp;&nbsp;Another measurement of same run:&nbsp;';
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>';
213   }
214   html += '</select></td></tr>';
215
216   html += '<tr><td>&nbsp;&nbsp;&nbsp;Event overlay:&nbsp;';
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>';
226     }
227     html += '</select>';
228   } else {
229     html += '&nbsp;<i><font size=-1>No events for this revision</font></i>';
230   }
231   html += '</td></tr></table>';
232
233   document.getElementById('comparisons').innerHTML = html;
234 }
235
236 /**
237  * Initializes the UI for the tabs at the top of a graph to change the displayed
238  * line.
239  */
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] + ' '));
246     if (!is_selected)
247       tab.addEventListener('click', goToClosure(tabs[i]), false);
248     switcher.appendChild(tab);
249   }
250 }
251
252 /**
253  * Adds data to existing arrays indicating what data should be plotted.
254  *
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
258  *     be appended.
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.
263  */
264 function addToPlotData(revisionNum, dataRows, plotData, dataDescriptions) {
265   // Get data for the revision number(s) to plot.
266   var found = false;
267   for (var i = 0; i < dataRows.length; ++i) {
268     var row = dataRows.get(i);
269     if (row && row.revision == revisionNum) {
270       found = true;
271       break;
272     }
273   }
274   if (!found) {
275     return null;
276   }
277
278   if (row.stack) {
279     if (!row.stack_order) {
280       reportError('No stack order was specified.');
281       return null;
282     }
283     var traceList = row.stack_order;
284   } else {
285     // Identify the (single) trace name associated with this revision.
286     var traceName = null;
287     for (var t in row.traces) {
288       if (traceName) {
289         reportError('Only one trace per revision is supported for ' +
290                     'non-stacked graphs.');
291         return null;
292       }
293       traceName = t;
294     }
295     var traceList = [traceName];
296   }
297
298   var lines = [];
299   for (var i = 0, traceName; traceName = traceList[i]; ++i) {
300     var trace = row.traces[traceName];
301     if (!trace) {
302       reportError('No specified trace was found.');
303       return null;
304     }
305
306     var points = [];
307     for (var j = 0, point; point = trace[j]; ++j) {
308       points.push([parseFloat(point[0]), parseFloat(point[1])]);
309     }
310     lines.push(points);
311     dataDescriptions.push(traceName + ' [r' + row.revision + ']');
312   }
313
314   if (row.stack) {
315     lines = graphUtils.stackFrontToBack(graphUtils.interpolate(lines));
316   }
317
318   for (var i = 0, line; line = lines[i]; ++i) {
319     plotData.push(line);
320   }
321
322   return row;
323 }
324
325 /**
326  * Callback for when a *-summary.dat data file has been read.
327  *
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.
331  */
332 function receivedSummary(data, error) {
333   if (error) {
334     reportError(error);
335     return;
336   }
337
338   var errorMessages = '';
339   var rows = new Rows(data);
340
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);
345     if (!row)
346       continue;
347     revisionNumbers.push(row.revision);
348   }
349   revisionNumbers.sort(
350       function(a, b) { return parseInt(a, 10) - parseInt(b, 10) });
351
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];
359     } else {
360       if (revisionNumbers.length >= 1) {
361         params.revision = revisionNumbers[revisionNumbers.length-1];
362       } else {
363         reportError('No revision information to plot.');
364         return;
365       }
366     }
367   }
368
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 = [];
373
374   var row = addToPlotData(params.revision, rows, plotData, dataDescriptions);
375   if (!row) {
376     errorMessages += 'No data for the specified revision.<br>';
377   }
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;
381
382   var rowOther = null;
383   if ('revisionOther' in params) {
384     rowOther = addToPlotData(params.revisionOther, rows, plotData,
385                              dataDescriptions);
386     if (!rowOther)
387       errorMessages += 'No data for the revision to compare against.<br>';
388   }
389
390   if ('graphOther' in params) {
391     rowOther = addToPlotData(params.revision, graphDataOtherRows, plotData,
392                              dataDescriptions);
393     if (rowOther) {
394       for (var i = 0; i < graphList.length; ++i) {
395         if (graphList[i].name == params.graphOther) {
396           unitsYOther = graphList[i].units;
397           break;
398         }
399       }
400     } else {
401       errorMessages += 'No data for the measurement to compare against.<br>';
402     }
403   }
404
405   // Identify the events for the current revision.
406   if (eventRows) {
407     for (var index = 0; index < eventRows.length; ++index) {
408       var info = eventRows.get(index);
409       if (params.revision == info['rev']) {
410         eventInfo = info;
411         break;
412       }
413     }
414     if (eventInfo != null) {
415       for (var key in eventInfo['events']) {
416         eventTypes.push(key);
417       }
418     }
419   }
420
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;
429       }
430     }
431   }
432
433   // Draw everything.
434   if (errorMessages == '') {
435     var plotter = new Plotter(
436         plotData,
437         dataDescriptions,
438         eventNameToPlot, eventInfoToPlot,
439         unitsX, unitsY, unitsYOther, graphsOtherStartIndex,
440         document.getElementById('output'),
441         'lookout' in params,
442         !!row.stack,
443         rowOther && !!rowOther.stack);
444
445     plotter.plot();
446   } else {
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;
450   }
451
452   if (!('lookout' in params)) {
453     initRevisionOptions();
454     initComparisonOptions();
455   }
456 }
457
458 /**
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
461  * graph line.
462  *
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.
466  */
467 function receivedSummaryGraphOther(data, error) {
468   if (error) {
469     reportError(error);
470     return;
471   }
472
473   graphDataOtherRows = new Rows(data);
474   Fetch(escape(params.graph) + '-summary.dat', receivedSummary);
475 }
476
477 /**
478  * Callback for when an event info file has been read.
479  *
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.
483  */
484 function receivedEvents(data, error) {
485   if (!error)
486     eventRows = new Rows(data);
487   fetchSummary();
488 }
489
490 /**
491  * Callback for when a graphs.dat data file has been read.
492  *
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.
496  */
497 function receivedGraphList(data, error) {
498   if (error) {
499     reportError(error);
500     return;
501   }
502   graphList = jsonToJs(data);
503
504   if (!('graph' in params) || params.graph == '')
505     if (graphList.length > 0)
506       params.graph = graphList[0].name
507
508   // Add a selection tab for each graph, and find the units for the selected
509   // one while we're at it.
510   tabs = [];
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;
517     }
518   }
519   initPlotSwitcher(tabs);
520
521   fetchEvents();
522 }
523
524 /**
525  * Starts fetching a *-summary.dat file.
526  */
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);
533   } else {
534     Fetch(escape(params.graph) + '-summary.dat',
535           receivedSummary);
536   }
537 }
538
539 /**
540  * Starts fetching an event info file.
541  */
542 function fetchEvents() {
543   Fetch('_EVENT_-summary.dat', receivedEvents);
544 }
545
546 /**
547  * Starts fetching a graphs.dat file.
548  */
549 function fetchGraphList() {
550   Fetch('graphs.dat', receivedGraphList);
551 }
552
553 window.addEventListener('load', fetchGraphList, false);