| OLD | NEW |
| (Empty) |
| 1 <html> | |
| 2 | |
| 3 <!-- | |
| 4 Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 5 Use of this source code is governed by a BSD-style license that can be | |
| 6 found in the LICENSE file. | |
| 7 --> | |
| 8 | |
| 9 <!-- | |
| 10 For testing this file locally, start a localhost server at the root of the | |
| 11 perf directory (e.g. with "python -m SimpleHTTPServer") and pass in a | |
| 12 baseUrl as a query parameter, e.g. | |
| 13 http://localhost:8000/dashboard/ui/generic_plotter.html?history=150&rev=-1&gra
ph=dom&baseUrl=http://localhost:8000/data/linux-release-webkit-latest/dromaeo_do
mcore/. | |
| 14 | |
| 15 You need a localhost server to get around Chromium's restrictions on loading | |
| 16 file urls in XMLHttpRequests. | |
| 17 | |
| 18 A brief note on terminology as used here: a "graph" is a plotted screenful | |
| 19 of data, showing the results of one type of test: for example, the | |
| 20 page-load-time graph. A "trace" is a single line on a graph, showing one | |
| 21 one for the test: for example, the reference build trace on the | |
| 22 page-load-time graph. | |
| 23 | |
| 24 This page plots arbitrary numerical data loaded from files in a specific | |
| 25 format. It uses two or more data files, all JSON-encoded: | |
| 26 | |
| 27 graphs.dat: a list of objects, each with these properties: name (the name | |
| 28 of a graph) and units (the units for the data to be read by humans). | |
| 29 Schematically: | |
| 30 [{"name": <graph_name>, "units": <units>}, ...] | |
| 31 | |
| 32 <graphname>-summary.dat: for each of the graphs listed in graphs.dat, the | |
| 33 corresponding summary file holds rows of data. Each row of data is an | |
| 34 object with several properties: | |
| 35 "rev": the revision number for this row of data | |
| 36 "traces": an object with several properties of its own. The name of | |
| 37 the property corresponds to a trace name, used only as an | |
| 38 internal identifier, and the property's value is an array of | |
| 39 its measurement and that measurement's standard deviation (or | |
| 40 other measurement error). | |
| 41 Schematically: | |
| 42 {"rev": <rev>, | |
| 43 "traces": {<trace_name1>: [<value1>, <stddev1>], | |
| 44 <trace_name2>: [<value2>, <stddev2>], ...} | |
| 45 } | |
| 46 --> | |
| 47 <head> | |
| 48 <style> | |
| 49 body { | |
| 50 font-family: sans-serif; | |
| 51 } | |
| 52 div#output { | |
| 53 cursor: pointer; | |
| 54 } | |
| 55 div#switcher * { | |
| 56 border: 1px solid black; | |
| 57 border-radius: 4px 4px 0 0; | |
| 58 padding-left: 0.5em; | |
| 59 padding-right: 0.5em; | |
| 60 } | |
| 61 div#switcher a { | |
| 62 background: #ddd; | |
| 63 cursor: pointer; | |
| 64 } | |
| 65 canvas.plot { | |
| 66 border: 1px solid black; | |
| 67 } | |
| 68 div.plot-coordinates { | |
| 69 font-family: monospace; | |
| 70 } | |
| 71 iframe { | |
| 72 display: none; | |
| 73 width: 100%; | |
| 74 height: 100%; | |
| 75 border: none; | |
| 76 } | |
| 77 .selector { | |
| 78 border: solid 1px black; | |
| 79 cursor: pointer; | |
| 80 padding-left: 0.3em; | |
| 81 background-color: white; | |
| 82 width: 80px; | |
| 83 display: inline-block; | |
| 84 } | |
| 85 .selector:hover { | |
| 86 background-color: rgb(200,200,250); | |
| 87 } | |
| 88 div#selectors { | |
| 89 display: none; | |
| 90 right: 6px; | |
| 91 position: absolute; | |
| 92 } | |
| 93 #explain { | |
| 94 font-size: 0.75em; | |
| 95 font-style: italic; | |
| 96 color: rgb(100,100,100); | |
| 97 } | |
| 98 #views { | |
| 99 border: 1px solid black; | |
| 100 width: 100%; | |
| 101 display: none; | |
| 102 } | |
| 103 #webkit-tab { | |
| 104 border-left: none; | |
| 105 display: none; | |
| 106 } | |
| 107 </style> | |
| 108 | |
| 109 <script src="js/common.js"></script> | |
| 110 <script src="js/plotter.js"></script> | |
| 111 <script src="js/coordinates.js"></script> | |
| 112 <script src="config.js"></script> | |
| 113 <script> | |
| 114 document.title = Config.title + ' - ' + Config.buildslave; | |
| 115 | |
| 116 var did_position_details = false; | |
| 117 var units = 'thing-a-ma-bobs'; | |
| 118 var graph_list = []; | |
| 119 var first_trace = ''; | |
| 120 | |
| 121 var refresh_params = false; | |
| 122 var params = ParseParams(); | |
| 123 if (!('history' in params)) { | |
| 124 params.history = 150; | |
| 125 refresh_params = true; | |
| 126 } | |
| 127 if (!('rev' in params)) { | |
| 128 params.rev = -1; // -1 specifies the latest revision. | |
| 129 refresh_params = true; | |
| 130 } | |
| 131 if (refresh_params) | |
| 132 window.location.href = MakeURL(params); | |
| 133 | |
| 134 if (!Config.detailTabs) | |
| 135 Config.detailTabs = {'view-change': 'CL'}; | |
| 136 | |
| 137 /** | |
| 138 * Encapsulates a *-summary.dat file. | |
| 139 * @constructor | |
| 140 */ | |
| 141 function Rows(data) { | |
| 142 this.rows = data.split('\n'); | |
| 143 this.length = this.rows.length; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Returns the row at the given index. | |
| 148 */ | |
| 149 Rows.prototype.get = function(i) { | |
| 150 if (!i in this.rows || this.rows[i] === undefined) return null; | |
| 151 if (!this.rows[i].length) return null; | |
| 152 var row = JSON.parse(this.rows[i]); | |
| 153 row.revision = isNaN(row['rev']) ? row['rev'] : parseInt(row['rev']); | |
| 154 row.webkitRevision = isNaN(row['webkit_rev']) ? | |
| 155 row['webkit_rev'] : parseInt(row['webkit_rev']); | |
| 156 return row; | |
| 157 }; | |
| 158 | |
| 159 function report_error(error) { | |
| 160 document.getElementById("output").innerHTML = "<p>" + error + "</p>"; | |
| 161 } | |
| 162 | |
| 163 function received_graph_list(data, error) { | |
| 164 if (error) { | |
| 165 report_error(error); | |
| 166 return; | |
| 167 } | |
| 168 graph_list = JSON.parse(data); | |
| 169 | |
| 170 if (!('graph' in params) || params.graph == '') { | |
| 171 if (graph_list.length > 0) | |
| 172 params.graph = graph_list[0].name | |
| 173 } | |
| 174 | |
| 175 // Add a selection tab for each graph, and find the units for the selected | |
| 176 // one while we're at it. | |
| 177 tabs = []; | |
| 178 for (var index = 0; index < graph_list.length; ++index) { | |
| 179 var graph = graph_list[index]; | |
| 180 tabs.push(graph.name); | |
| 181 if (graph.name == params.graph) | |
| 182 units = graph.units; | |
| 183 } | |
| 184 initPlotSwitcher(tabs); | |
| 185 | |
| 186 // Fetch the data for the selected graph. | |
| 187 fetch_summary(); | |
| 188 | |
| 189 } | |
| 190 | |
| 191 function go_to(graph) { | |
| 192 params.graph = graph; | |
| 193 if (params.graph == '') | |
| 194 delete params.graph; | |
| 195 window.location.href = MakeURL(params); | |
| 196 } | |
| 197 | |
| 198 function get_url() { | |
| 199 var new_url = encodeURI(window.location.href); | |
| 200 new_url = new_url.replace(/'/g, '%27'); | |
| 201 new_url = new_url.replace(/\&lookout=1/, ''); | |
| 202 if (new_url.indexOf('http://') == 0 || new_url.indexOf('https://') == 0) | |
| 203 return new_url; | |
| 204 return ''; | |
| 205 } | |
| 206 | |
| 207 function on_clicked_plot(prev_entry, current_entry) { | |
| 208 if ('lookout' in params) { | |
| 209 window.open(get_url()); | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 // Define sources for detail tabs | |
| 214 if ('view-change' in Config.detailTabs) { | |
| 215 // If the changeLinkPrefix has {PREV_CL}/{CL} markers, replace them. | |
| 216 // Otherwise, append to the URL. | |
| 217 var url = Config.changeLinkPrefix; | |
| 218 if (url.indexOf('{PREV_CL}') >= 0 || url.indexOf('{CL}') >= 0) { | |
| 219 url = url.replace('{PREV_CL}', prev_entry.chromium); | |
| 220 url = url.replace('{CL}', current_entry.chromium); | |
| 221 } else { | |
| 222 url += prev_entry.chromium + ':' + current_entry.chromium; | |
| 223 } | |
| 224 document.getElementById('view-change').setAttribute('src', url); | |
| 225 } | |
| 226 if ('view-pages' in Config.detailTabs) { | |
| 227 document.getElementById('view-pages').src = 'details.html?cl=' + | |
| 228 current_entry.chromium + '&graph=' + params.graph + '&trace=' + | |
| 229 first_trace; | |
| 230 } | |
| 231 if ('view-coverage' in Config.detailTabs) { | |
| 232 document.getElementById('view-coverage').src = | |
| 233 Config.coverageLinkPrefix + current_entry.chromium; | |
| 234 } | |
| 235 if (!isNaN(prev_entry.webkit) && !isNaN(current_entry.webkit) && | |
| 236 prev_entry.webkit <= current_entry.webkit) { | |
| 237 Config.detailTabs['view-webkit-change'] = 'Webkit'; | |
| 238 document.getElementById('webkit-tab').style.display = 'inline-block'; | |
| 239 var url = 'http://trac.webkit.org/log/?verbose=on&rev=' + | |
| 240 current_entry.webkit + '&stop_rev=' + prev_entry.webkit; | |
| 241 document.getElementById('view-webkit-change').src = url; | |
| 242 } else { | |
| 243 var webkitView = document.getElementById('view-webkit-change'); | |
| 244 if (webkitView.style.display == 'block') | |
| 245 show_first_view(); | |
| 246 delete Config.detailTabs['view-webkit-change']; | |
| 247 document.getElementById('webkit-tab').style.display = 'none'; | |
| 248 } | |
| 249 | |
| 250 if (!did_position_details) { | |
| 251 show_first_view(); | |
| 252 position_details(); | |
| 253 did_position_details = true; | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 function show_first_view() { | |
| 258 for (var tab in Config.detailTabs) { | |
| 259 change_view(tab); | |
| 260 break; | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 function received_summary(data, error) { | |
| 265 if (error) { | |
| 266 report_error(error); | |
| 267 return; | |
| 268 } | |
| 269 // Parse the summary data file. | |
| 270 var rows = new Rows(data); | |
| 271 var max_rows = rows.length; | |
| 272 if (max_rows > params.history) | |
| 273 max_rows = params.history; | |
| 274 | |
| 275 var allTraces = {}; | |
| 276 | |
| 277 // Find the start and end of the data slice we will focus on. | |
| 278 var start_row = 0; | |
| 279 if (params.rev > 0) { | |
| 280 var i = 0; | |
| 281 while (i < rows.length) { | |
| 282 var row = rows.get(i); | |
| 283 | |
| 284 // If the current row's revision is higher than the desired revision, | |
| 285 // continue searching. | |
| 286 if (row.revision > params.rev) { | |
| 287 i++; | |
| 288 continue; | |
| 289 } | |
| 290 | |
| 291 // We're either just under or at the desired revision. | |
| 292 start_row = i; | |
| 293 | |
| 294 // If the desired revision does not exist, use the row before it. | |
| 295 if (row.revision < params.rev && start_row > 0) | |
| 296 start_row -= 1; | |
| 297 | |
| 298 break; | |
| 299 } | |
| 300 } | |
| 301 | |
| 302 // Some summary files contain data not listed in rev-descending order. For | |
| 303 // those cases, it is possible we will find a start row in the middle of the | |
| 304 // data whose neighboring data is not nearby. See xp-release-dual-core | |
| 305 // moz rev 265 => no graph. | |
| 306 var end_row = start_row + max_rows; | |
| 307 | |
| 308 // Build and order a list of revision numbers. | |
| 309 var revisionNumbers = []; | |
| 310 var hasNumericRevisions = true; | |
| 311 // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...} | |
| 312 var graphData = {}; | |
| 313 for (var i = start_row; i < end_row; ++i) { | |
| 314 var row = rows.get(i); | |
| 315 if (!row) | |
| 316 continue; | |
| 317 var traces = row['traces']; | |
| 318 for (var j = 0; j < traces.length; ++j) | |
| 319 traces[j] = parseFloat(traces[j]); | |
| 320 | |
| 321 if (!(row.revision in graphData)) { | |
| 322 graphData[row.revision] = {}; | |
| 323 } | |
| 324 graphData[row.revision][row.webkitRevision] = traces; | |
| 325 if (isNaN(row.revision) || isNaN(row.webkitRevision)) { | |
| 326 hasNumericRevisions = false; | |
| 327 } | |
| 328 revisionNumbers.push( | |
| 329 { chromium: row.revision, webkit: row.webkitRevision }); | |
| 330 | |
| 331 // Collect unique trace names. If traces are explicitly specified in | |
| 332 // params, delete unspecified trace data. | |
| 333 for (var traceName in traces) { | |
| 334 if (typeof(params['trace']) != 'undefined' && | |
| 335 params['trace'][traceName] != 1) { | |
| 336 delete(traces[traceName]); | |
| 337 } | |
| 338 allTraces[traceName] = 1; | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 // Build a list of all the trace names we've seen, in the order in which | |
| 343 // they appear in the data file. Although JS objects are not required by | |
| 344 // the spec to iterate their properties in order, in practice they do, | |
| 345 // because it causes compatibility problems otherwise. | |
| 346 var traceNames = []; | |
| 347 for (var traceName in allTraces) | |
| 348 traceNames.push(traceName); | |
| 349 | |
| 350 first_trace = traceNames[0]; | |
| 351 | |
| 352 // If the revisions are numeric (svn), sort them numerically to ensure they | |
| 353 // are in ascending order. Otherwise, if the revisions aren't numeric (git), | |
| 354 // reverse them under the assumption the rows were prepended to the file. | |
| 355 if (hasNumericRevisions) { | |
| 356 revisionNumbers.sort(function(a, b) { | |
| 357 var revdiff = parseInt(a.chromium, 10) - parseInt(b.chromium, 10); | |
| 358 if (revdiff != 0) { | |
| 359 return revdiff; | |
| 360 } | |
| 361 return parseInt(a.webkit, 10) - parseInt(b.webkit, 10); | |
| 362 }); | |
| 363 } else { | |
| 364 revisionNumbers.reverse(); | |
| 365 } | |
| 366 | |
| 367 // Build separate ordered lists of trace data. | |
| 368 var traceData = {}; | |
| 369 for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) { | |
| 370 var rev = revisionNumbers[revIndex].chromium; | |
| 371 var webkitrev = revisionNumbers[revIndex].webkit; | |
| 372 var revisionData = graphData[rev][webkitrev]; | |
| 373 for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) { | |
| 374 var traceName = traceNames[nameIndex]; | |
| 375 if (!traceData[traceName]) | |
| 376 traceData[traceName] = []; | |
| 377 if (!revisionData[traceName]) | |
| 378 traceData[traceName].push([NaN, NaN]); | |
| 379 else | |
| 380 traceData[traceName].push([parseFloat(revisionData[traceName][0]), | |
| 381 parseFloat(revisionData[traceName][1])]); | |
| 382 } | |
| 383 } | |
| 384 var plotData = []; | |
| 385 for (var traceName in traceData) | |
| 386 plotData.push(traceData[traceName]); | |
| 387 var plotter = new Plotter(revisionNumbers, plotData, traceNames, units, | |
| 388 document.getElementById("output")); | |
| 389 plotter.onclick = on_clicked_plot; | |
| 390 plotter.plot(); | |
| 391 } | |
| 392 | |
| 393 function fetch_summary() { | |
| 394 if ('graph' in params) | |
| 395 file = escape(params.graph) + "-summary.dat" | |
| 396 else | |
| 397 file = "summary.dat" | |
| 398 var baseUrl = params.baseUrl || ''; | |
| 399 Fetch(baseUrl + file, received_summary); | |
| 400 } | |
| 401 | |
| 402 function fetch_graph_list() { | |
| 403 var baseUrl = params.baseUrl || ''; | |
| 404 Fetch(baseUrl + "graphs.dat", received_graph_list); | |
| 405 } | |
| 406 | |
| 407 function initPlotSwitcher(tabs) { | |
| 408 var switcher = document.getElementById("switcher"); | |
| 409 for (var i = 0; i < tabs.length; i++) { | |
| 410 var is_selected = tabs[i] == params.graph; | |
| 411 var tab = document.createElement(is_selected ? "span" : "a"); | |
| 412 tab.appendChild(document.createTextNode(tabs[i] + " ")); | |
| 413 if (!is_selected) | |
| 414 tab.addEventListener("click", goToClosure(tabs[i]), false); | |
| 415 switcher.appendChild(tab); | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 function goToClosure(graph) { | |
| 420 return function(){go_to(graph)}; | |
| 421 } | |
| 422 | |
| 423 function position_details() { | |
| 424 var views = document.getElementById("views"); | |
| 425 views.style.display = "block"; | |
| 426 var selectors = document.getElementById("selectors"); | |
| 427 selectors.style.display = "block"; | |
| 428 | |
| 429 var output = document.getElementById("output"); | |
| 430 var views_width = output.offsetWidth - selectors.offsetWidth; | |
| 431 | |
| 432 views.style.height = (window.innerHeight - output.offsetHeight - | |
| 433 output.offsetTop - 16) + "px"; | |
| 434 selectors.style.top = (views.offsetTop - selectors.offsetHeight + 1) + "px"; | |
| 435 } | |
| 436 | |
| 437 function change_view(target) { | |
| 438 for (var tab in Config.detailTabs) { | |
| 439 document.getElementById(tab).style.display = | |
| 440 (tab == target ? "block" : "none"); | |
| 441 } | |
| 442 } | |
| 443 | |
| 444 function init() { | |
| 445 // We need to fill the graph list before parsing the params or fetching the | |
| 446 // data, so we have a default graph in case none was specified. | |
| 447 fetch_graph_list(); | |
| 448 } | |
| 449 | |
| 450 window.addEventListener("resize", position_details, false); | |
| 451 window.addEventListener("load", init, false); | |
| 452 </script> | |
| 453 </head> | |
| 454 | |
| 455 | |
| 456 <body> | |
| 457 <div id="header_lookout" align="center"> | |
| 458 <font style='color: #0066FF; font-family: Arial, serif; | |
| 459 font-size: 20pt; font-weight: bold;'> | |
| 460 <script> | |
| 461 document.write("<a target=\"_blank\" href=\""); | |
| 462 document.write(get_url()); | |
| 463 document.write("\">"); | |
| 464 if ('header' in params && params.header != '') { | |
| 465 document.write(escape(params.header)); | |
| 466 } else { | |
| 467 document.write(Config.title); | |
| 468 } | |
| 469 document.write("</a>"); | |
| 470 </script> | |
| 471 </font> | |
| 472 </div> | |
| 473 | |
| 474 <div id="header_text"> | |
| 475 Builds generated by the <a href="http://build.chromium.org/">Chromium Buildbot</
a> | |
| 476 are run through <b> | |
| 477 <script> | |
| 478 document.write(Config.title); | |
| 479 </script> | |
| 480 </b>and the results of that test are charted here. | |
| 481 </div> | |
| 482 | |
| 483 <div id="explain"> | |
| 484 The vertical axis is measured values, and the horizontal | |
| 485 axis is the revision number for the build being tested. | |
| 486 </div> | |
| 487 <p></p> | |
| 488 <div id="switcher"> | |
| 489 | |
| 490 </div> | |
| 491 <div id="output"></div> | |
| 492 <div id="details"> | |
| 493 <div id="views"> | |
| 494 <script> | |
| 495 for (var tab in Config.detailTabs) { | |
| 496 document.write("<iframe id=\"" + tab + "\"></iframe>"); | |
| 497 } | |
| 498 </script> | |
| 499 <iframe id='view-webkit-change'></iframe> | |
| 500 </div> | |
| 501 <div id="selectors"> | |
| 502 <script> | |
| 503 var firstTab = true; | |
| 504 for (var tab in Config.detailTabs) { | |
| 505 document.write("<div "); | |
| 506 if (firstTab) { | |
| 507 firstTab = false; | |
| 508 } else { | |
| 509 document.write("style=\"border-left: none\" "); | |
| 510 } | |
| 511 document.write("class=\"selector\" onclick=\"change_view('" | |
| 512 + tab + "')\">" + Config.detailTabs[tab] + "</div>"); | |
| 513 } | |
| 514 </script><div id="webkit-tab" class="selector" | |
| 515 onclick="change_view('view-webkit-change')">Webkit</div> | |
| 516 </div> | |
| 517 </div> | |
| 518 <pre id="log"></pre> | |
| 519 <script> | |
| 520 if ('lookout' in params) { | |
| 521 document.getElementById("switcher").style.display = "none"; | |
| 522 document.getElementById("details").style.display = "none"; | |
| 523 document.getElementById("header_text").style.display = "none"; | |
| 524 document.getElementById("explain").style.display = "none"; | |
| 525 if ('thumbnail' in params) { | |
| 526 document.getElementById("header_lookout").style.display = "none"; | |
| 527 } | |
| 528 } else { | |
| 529 document.getElementById("header_lookout").style.display = "none"; | |
| 530 } | |
| 531 </script> | |
| 532 </body> | |
| 533 </html> | |
| OLD | NEW |