OLD | NEW |
| (Empty) |
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> '; | |
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> 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>'; | |
201 } | |
202 html += '</select></td></tr>' | |
203 | |
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>'; | |
213 } | |
214 html += '</select></td></tr>'; | |
215 | |
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>'; | |
226 } | |
227 html += '</select>'; | |
228 } else { | |
229 html += ' <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); | |
OLD | NEW |