| 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 {Object} 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 {boolean} Whether or not new plot data was actually appended to the | |
| 262 * given arrays. | |
| 263 */ | |
| 264 function addToPlotData(revisionNum, dataRows, plotData, dataDescriptions) { | |
| 265 // Get data for the revision number(s) to plot. | |
| 266 var revData = null; | |
| 267 for (var i = 0; i < dataRows.length; ++i) { | |
| 268 var row = dataRows.get(i); | |
| 269 if (!row) | |
| 270 continue; | |
| 271 | |
| 272 var revision = row.revision; | |
| 273 var traces = row.traces; | |
| 274 | |
| 275 if (revisionNum == revision) { | |
| 276 revData = traces; | |
| 277 break; | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 if (!revData) | |
| 282 return false; | |
| 283 | |
| 284 // Identify the (single) trace name associated with this revision. | |
| 285 var traceName = ''; | |
| 286 for (var t in revData) { | |
| 287 if (traceName) { | |
| 288 reportError('Only one trace per revision is supported.'); | |
| 289 return false; | |
| 290 } | |
| 291 traceName = t; | |
| 292 } | |
| 293 | |
| 294 var traceData = []; | |
| 295 for (var pointIndex = 0; pointIndex < revData[traceName].length; | |
| 296 ++pointIndex) { | |
| 297 traceData.push([parseFloat(revData[traceName][pointIndex][0]), | |
| 298 parseFloat(revData[traceName][pointIndex][1])]); | |
| 299 } | |
| 300 | |
| 301 plotData.push(traceData); | |
| 302 dataDescriptions.push(traceName + ' [r' + revisionNum + ']'); | |
| 303 return true; | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * Callback for when a *-summary.dat data file has been read. | |
| 308 * | |
| 309 * @param {string} data The string data from the inputted text file. | |
| 310 * @param {string} error A string error message, in case an error occurred | |
| 311 * during the file read. | |
| 312 */ | |
| 313 function receivedSummary(data, error) { | |
| 314 if (error) { | |
| 315 reportError(error); | |
| 316 return; | |
| 317 } | |
| 318 | |
| 319 var errorMessages = ''; | |
| 320 var rows = new Rows(data); | |
| 321 | |
| 322 // Build and order a list of revision numbers. | |
| 323 revisionNumbers = []; | |
| 324 for (var i = 0; i < rows.length; ++i) { | |
| 325 var row = rows.get(i); | |
| 326 if (!row) | |
| 327 continue; | |
| 328 revisionNumbers.push(row.revision); | |
| 329 } | |
| 330 revisionNumbers.sort( | |
| 331 function(a, b) { return parseInt(a, 10) - parseInt(b, 10) }); | |
| 332 | |
| 333 // Get the revision number to plot. | |
| 334 if (!('revision' in params) || params.revision == '') { | |
| 335 if (revisionNumbers.length >= 2 && 'lookout' in params) { | |
| 336 // Since the last graph (test run) might still be in progress, get the | |
| 337 // second-to-last graph to display on the summary page. That one | |
| 338 // is assumed to have finished running to completion. | |
| 339 params.revision = revisionNumbers[revisionNumbers.length-2]; | |
| 340 } else { | |
| 341 if (revisionNumbers.length >= 1) { | |
| 342 params.revision = revisionNumbers[revisionNumbers.length-1]; | |
| 343 } else { | |
| 344 reportError('No revision information to plot.'); | |
| 345 return; | |
| 346 } | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 var plotData = []; // plotData is a list of graph lines; each graph line is | |
| 351 // a list of points; each point is a list of 2 values, | |
| 352 // representing the (x, y) pair. | |
| 353 var dataDescriptions = []; | |
| 354 | |
| 355 if (!addToPlotData(params.revision, rows, plotData, dataDescriptions)) | |
| 356 errorMessages += 'No data for the specified revision.<br>'; | |
| 357 | |
| 358 if ('revisionOther' in params) { | |
| 359 if (!addToPlotData(params.revisionOther, rows, plotData, dataDescriptions)) | |
| 360 errorMessages += 'No data for the revision to compare against.<br>'; | |
| 361 } | |
| 362 | |
| 363 if ('graphOther' in params) { | |
| 364 if (addToPlotData(params.revision, graphDataOtherRows, plotData, | |
| 365 dataDescriptions)) { | |
| 366 for (var i = 0; i < graphList.length; ++i) { | |
| 367 if (graphList[i].name == params.graphOther) { | |
| 368 unitsYOther = graphList[i].units; | |
| 369 break; | |
| 370 } | |
| 371 } | |
| 372 } else { | |
| 373 errorMessages += 'No data for the measurement to compare against.<br>'; | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 // Identify the events for the current revision. | |
| 378 if (eventRows) { | |
| 379 for (var index = 0; index < eventRows.length; ++index) { | |
| 380 var info = eventRows.get(index); | |
| 381 if (params.revision == info['rev']) { | |
| 382 eventInfo = info; | |
| 383 break; | |
| 384 } | |
| 385 } | |
| 386 if (eventInfo != null) { | |
| 387 for (var key in eventInfo['events']) { | |
| 388 eventTypes.push(key); | |
| 389 } | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 // Get data for the events to display, if one was requested in the params. | |
| 394 var eventNameToPlot = null; | |
| 395 var eventInfoToPlot = null; | |
| 396 if ('event' in params && eventInfo != null) { | |
| 397 for (var key in eventInfo['events']) { | |
| 398 if (key == params['event']) { | |
| 399 eventInfoToPlot = eventInfo['events'][key]; | |
| 400 eventNameToPlot = key; | |
| 401 } | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 // Draw everything. | |
| 406 if (errorMessages == '') { | |
| 407 var plotter = new Plotter( | |
| 408 plotData, | |
| 409 dataDescriptions, | |
| 410 eventNameToPlot, eventInfoToPlot, | |
| 411 unitsX, unitsY, unitsYOther, | |
| 412 document.getElementById('output'), | |
| 413 'lookout' in params); | |
| 414 | |
| 415 plotter.plot(); | |
| 416 } else { | |
| 417 errorMessages = '<br><br><br><table border=2 cellpadding=5><tr><td>' + | |
| 418 errorMessages + '</td></tr></table><br><br>'; | |
| 419 document.getElementById('output').innerHTML = errorMessages; | |
| 420 } | |
| 421 | |
| 422 if (!('lookout' in params)) { | |
| 423 initRevisionOptions(); | |
| 424 initComparisonOptions(); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 /** | |
| 429 * Callback for when a second *-summary.dat data file has been read, in the | |
| 430 * event that a second graph line is being overlayed on top of an existing | |
| 431 * graph line. | |
| 432 * | |
| 433 * @param {string} data The string data from the inputted text file. | |
| 434 * @param {string} error A string error message, in case an error occurred | |
| 435 * during the file read. | |
| 436 */ | |
| 437 function receivedSummaryGraphOther(data, error) { | |
| 438 if (error) { | |
| 439 reportError(error); | |
| 440 return; | |
| 441 } | |
| 442 | |
| 443 graphDataOtherRows = new Rows(data); | |
| 444 Fetch(escape(params.graph) + '-summary.dat', receivedSummary); | |
| 445 } | |
| 446 | |
| 447 /** | |
| 448 * Callback for when an event info file has been read. | |
| 449 * | |
| 450 * @param {string} data The string data from the inputted text file. | |
| 451 * @param {string} error A string error message, in case an error occurred | |
| 452 * during the file read. | |
| 453 */ | |
| 454 function receivedEvents(data, error) { | |
| 455 if (!error) | |
| 456 eventRows = new Rows(data); | |
| 457 fetchSummary(); | |
| 458 } | |
| 459 | |
| 460 /** | |
| 461 * Callback for when a graphs.dat data file has been read. | |
| 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 receivedGraphList(data, error) { | |
| 468 if (error) { | |
| 469 reportError(error); | |
| 470 return; | |
| 471 } | |
| 472 graphList = jsonToJs(data); | |
| 473 | |
| 474 if (!('graph' in params) || params.graph == '') | |
| 475 if (graphList.length > 0) | |
| 476 params.graph = graphList[0].name | |
| 477 | |
| 478 // Add a selection tab for each graph, and find the units for the selected | |
| 479 // one while we're at it. | |
| 480 tabs = []; | |
| 481 for (var index = 0; index < graphList.length; ++index) { | |
| 482 var graph = graphList[index]; | |
| 483 tabs.push(graph.name); | |
| 484 if (graph.name == params.graph) { | |
| 485 unitsX = graph.units_x; | |
| 486 unitsY = graph.units; | |
| 487 } | |
| 488 } | |
| 489 initPlotSwitcher(tabs); | |
| 490 | |
| 491 fetchEvents(); | |
| 492 } | |
| 493 | |
| 494 /** | |
| 495 * Starts fetching a *-summary.dat file. | |
| 496 */ | |
| 497 function fetchSummary() { | |
| 498 if ('graphOther' in params) { | |
| 499 // We need to overlay a second graph over the first one, so we need to | |
| 500 // fetch that summary data too. Do it first. | |
| 501 Fetch(escape(params.graphOther) + '-summary.dat', | |
| 502 receivedSummaryGraphOther); | |
| 503 } else { | |
| 504 Fetch(escape(params.graph) + '-summary.dat', | |
| 505 receivedSummary); | |
| 506 } | |
| 507 } | |
| 508 | |
| 509 /** | |
| 510 * Starts fetching an event info file. | |
| 511 */ | |
| 512 function fetchEvents() { | |
| 513 Fetch('_EVENT_-summary.dat', receivedEvents); | |
| 514 } | |
| 515 | |
| 516 /** | |
| 517 * Starts fetching a graphs.dat file. | |
| 518 */ | |
| 519 function fetchGraphList() { | |
| 520 Fetch('graphs.dat', receivedGraphList); | |
| 521 } | |
| 522 | |
| 523 window.addEventListener('load', fetchGraphList, false); | |
| OLD | NEW |