<html> <!-- Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <!-- A brief note on terminology as used here: a "graph" is a plotted screenful of data, showing the results of one type of test: for example, the page-load-time graph. A "trace" is a single line on a graph, showing one one for the test: for example, the reference build trace on the page-load-time graph. This page plots arbitrary numerical data loaded from files in a specific format. It uses two or more data files, all JSON-encoded: graphs.dat: a list of objects, each with these properties: name (the name of a graph) and units (the units for the data to be read by humans). Schematically: [{"name": <graph_name>, "units": <units>}, ...] <graphname>-summary.dat: for each of the graphs listed in graphs.dat, the corresponding summary file holds rows of data. Each row of data is an object with several properties: "rev": the revision number for this row of data "traces": an object with several properties of its own. The name of the property corresponds to a trace name, used only as an internal identifier, and the property's value is an array of its measurement and that measurement's standard deviation (or other measurement error). Schematically: {"rev": <rev>, "traces": {<trace_name1>: [<value1>, <stddev1>], <trace_name2>: [<value2>, <stddev2>], ...} } --> <head> <style> body { font-family: sans-serif; } div#output { cursor: pointer; } div#switcher { cursor: pointer; } div#switcher a { border-top: 1px solid black; border-left: 1px solid black; padding-left: 0.5em; padding-right: 0.5em; } canvas.plot { border: 1px solid black; } div.plot-coordinates { font-family: monospace; } iframe { display: none; width: 100%; height: 100%; border: none; } div.selector { border: solid 1px black; cursor: pointer; padding-left: 0.3em; background-color: white; } div.selector:hover { background-color: rgb(200,200,250); } div.selected { border-left: none; } div#selectors { width: 80px; display: none; } #explain { font-size: 0.75em; font-style: italic; color: rgb(100,100,100); } </style> <script src="js/common.js"></script> <script src="js/plotter.js"></script> <script src="js/coordinates.js"></script> <script src="config.js"></script> <script> Config.source = "http://scons.tigris.org/svn/scons/trunk"; Config.changeLinkPrefix = "changelog.html?mode=html&range="; Config.builder = "TODO"; Config.buildbotLink = "http://buildbot.scons.org:8010/"; Config.detailTabs = {'view-change': 'CL'}; document.title = Config.title + ' - ' + Config.buildslave; var did_position_details = false; var units = 'thing-a-ma-bobs'; var graph_list = []; var first_trace = ''; var params = ParseParams(); function jsonToJs(data) { return eval('(' + data + ')') } function report_error(error) { document.getElementById("output").innerHTML = "<p>" + error + "</p>"; } function received_graph_list(data, error) { if (error) { report_error(error); return; } graph_list = jsonToJs(data); if (!('graph' in params) || params.graph == '') { if (graph_list.length > 0) params.graph = graph_list[0].name } // Add a selection tab for each graph, and find the units for the selected // one while we're at it. tabs = []; for (var index = 0; index < graph_list.length; ++index) { var graph = graph_list[index]; tabs.push(graph.name); if (graph.name == params.graph) units = graph.units; } initPlotSwitcher(tabs); // Fetch the data for the selected graph. fetch_summary(); } function go_to(graph) { params.graph = graph; if (params.graph == '') delete params.graph; window.location.href = MakeURL(params); } function get_url() { new_url = window.location.href; new_url = new_url.replace(/\?lookout/, "?"); new_url = new_url.replace(/\&thumbnail/, ""); return new_url; } function on_clicked_plot(prev_cl, cl) { if ('lookout' in params) { window.open(get_url()); return; } // Define sources for detail tabs if ('view-change' in Config.detailTabs) { document.getElementById('view-change'). // TODO: The tigris.org source browser only lets us pull up // one revision. That's okay for our current behavior of // timing each revision separately, but if we go back to merging // build requests from multiple revisions, we'll need an // intermediary CGI script. //setAttribute('src', Config.changeLinkPrefix + prev_cl + ':' + cl); setAttribute('src', 'http://scons.tigris.org/source/browse/scons?view=rev&revision=' + cl); } if ('view-pages' in Config.detailTabs) { document.getElementById('view-pages'). setAttribute('src', 'details.html?cl=' + cl + '&trace=' + first_trace); } if ('view-coverage' in Config.detailTabs) { document.getElementById('view-coverage'). setAttribute('src', Config.coverageLinkPrefix + cl); } if (!did_position_details) { position_details(); did_position_details = true; } } function received_summary(data, error) { if (error) { report_error(error); return; } // Parse the summary data file. var rows = data.split('\n'); var max_rows = rows.length; if ('history' in params && max_rows > params.history) { max_rows = params.history; } else if ('lookout' in params && max_rows > 150) { max_rows = 150; } var allTraces = {}; // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...} var graphData = {}; for (var i = 0; i < max_rows; ++i) { if (!rows[i].length) continue; var row = jsonToJs(rows[i]); var traces = row['traces']; var revision = parseInt(row['rev']); graphData[revision] = traces; // Collect unique trace names. for (var traceName in traces) allTraces[traceName] = 1; } // Build a list of all the trace names we've seen, in the order in which // they appear in the data file. Although JS objects are not required by // the spec to iterate their properties in order, in practice they do, // because it causes compatibility problems otherwise. var traceNames = []; for (var traceName in allTraces) traceNames.push(traceName); first_trace = traceNames[0]; // Build and numerically sort a list of revision numbers. var revisionNumbers = []; for (var rev in graphData) revisionNumbers.push(rev); revisionNumbers.sort( function(a, b) { return parseInt(a, 10) - parseInt(b, 10) }); // Build separate ordered lists of trace data. var traceData = {}; for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) { var rev = revisionNumbers[revIndex]; var revisionData = graphData[rev]; for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) { var traceName = traceNames[nameIndex]; if (!traceData[traceName]) traceData[traceName] = []; if (!revisionData[traceName]) traceData[traceName].push([NaN, NaN]); else traceData[traceName].push(revisionData[traceName]); } } var plotData = []; for (var traceName in traceData) plotData.push(traceData[traceName]); var plotter = new Plotter(revisionNumbers, plotData, traceNames, units, document.getElementById("output"), true); plotter.onclick = on_clicked_plot; plotter.plot(); } function fetch_summary() { if ('graph' in params) file = escape(params.graph) + ".dat" else file = "summary.dat" Fetch(file, received_summary); } function fetch_graph_list() { Fetch("graphs.dat", received_graph_list); } function initPlotSwitcher(tabs) { var switcher = document.getElementById("switcher"); for(var i = 0; i < tabs.length; i++) { var anchor = document.createElement("a"); anchor.appendChild(document.createTextNode(tabs[i] + " ")); anchor.addEventListener("click", goToClosure(tabs[i]), false); switcher.appendChild(anchor); } } function goToClosure(graph) { return function(){go_to(graph)}; } function position_details() { var output = document.getElementById("output"); var win_height = window.innerHeight; var details = document.getElementById("views"); var views = document.getElementById("views"); var selectors = document.getElementById("selectors"); selectors.style.display = "block"; var views_width = output.offsetWidth - selectors.offsetWidth; views.style.border = "1px solid black"; views.style.width = views_width + "px"; views.style.height = (win_height - output.offsetHeight - output.offsetTop - 30) + "px"; selectors.style.position = "absolute"; selectors.style.left = (views.offsetLeft + views_width + 1) + "px"; selectors.style.top = views.offsetTop + "px"; // Change to the first detail tab for (var tab in Config.detailTabs) { change_view(tab); break; } } function change_view(target) { for (var tab in Config.detailTabs) { document.getElementById(tab).style.display = (tab == target ? "block" : "none"); } } function init() { // We need to fill the graph list before parsing the params or fetching the // data, so we have a default graph in case none was specified. fetch_graph_list(); } window.addEventListener("load", init, false); </script> </head> <body> <div id="header_lookout" align="center"> <font style='color: #0066FF; font-family: Arial, serif; font-size: 20pt; font-weight: bold;'> <script> document.write("<a target=\"_blank\" href=\""); document.write(get_url()); document.write("\">"); if ('header' in params && params.header != '') { document.write(escape(params.header)); } else { document.write(Config.title); } document.write("</a>"); </script> </font> </div> <div id="header_text"> <script> document.write('<a href="' + Config.buildbotLink + '">SCons buildbot</a>' + ' timings for the <b>' + Config.title + '</b> configuration.') if ('graph' in params) document.write(' Displaying values for <b>' + params.graph + '</b>.'); </script> </div> <div id="explain"> The vertical axis is measured values, and the horizontal axis is the revision number being tested. </div> <p></p> <div id="switcher"> </div> <div id="output"></div> <div id="details"> <div id="views"> <script> for (var tab in Config.detailTabs) { document.write("<iframe id=\"" + tab + "\"></iframe>"); } </script> </div> <div id="selectors"> <script> var firstTab = true; for (var tab in Config.detailTabs) { document.write("<div "); if (firstTab) { firstTab = false; } else { document.write("style=\"border-top: none\" "); } document.write("class=\"selector\" onclick=\"change_view('" + tab + "')\">" + Config.detailTabs[tab] + "</div>"); } </script> </div> </div> <pre id="log"></pre> <script> if ('lookout' in params) { document.getElementById("switcher").style.display = "none"; document.getElementById("details").style.display = "none"; document.getElementById("header_text").style.display = "none"; document.getElementById("explain").style.display = "none"; if ('thumbnail' in params) { document.getElementById("header_lookout").style.display = "none"; } } else { document.getElementById("header_lookout").style.display = "none"; } </script> </body> </html>