summaryrefslogtreecommitdiff
path: root/timings
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2009-12-18 08:03:50 +0000
committerSteven Knight <knight@baldmt.com>2009-12-18 08:03:50 +0000
commitea3efaf5c76922329aae72a0ae5613df7940f59a (patch)
tree6e1f3e3dd9a03cf38faa62500cd224b751d27905 /timings
parentf95d845fd451f6a0c1e3a6f1c51156250703b6ae (diff)
downloadscons-ea3efaf5c76922329aae72a0ae5613df7940f59a.tar.gz
Move the timings-specific pieces of the buildbot infrastructure into
the trunk/timings directory. We'll map them into the buildbot directory using svn:externals. This will let us keep all the pieces of a timing configuration, including its buildbot pieces, in one place, and will let us simplify the Master initialization (since it will be able to look on-disk for the configurations for which it should set up buildbot steps, instead of querying the SVN server).
Diffstat (limited to 'timings')
-rw-r--r--timings/changelog.html204
-rw-r--r--timings/graph.html411
-rw-r--r--timings/index.html196
-rw-r--r--timings/js/common.js96
-rw-r--r--timings/js/coordinates.js125
-rw-r--r--timings/js/plotter.js336
6 files changed, 1368 insertions, 0 deletions
diff --git a/timings/changelog.html b/timings/changelog.html
new file mode 100644
index 00000000..586ebadc
--- /dev/null
+++ b/timings/changelog.html
@@ -0,0 +1,204 @@
+<html>
+<head>
+<script src="js/common.js"></script>
+<style type="text/css">
+table {
+ border-collapse: collapse;
+}
+thead {
+ border-top: solid 1px gray;
+ border-left: solid 1px gray;
+}
+tbody {
+ border-top: solid 1px gray;
+ border-bottom: solid 1px gray;
+ border-left: solid 1px gray;
+}
+th {
+ text-align: center;
+ border-right: solid 1px gray;
+}
+td {
+ padding-left: 0.5em;
+ padding-top: 0.3em;
+ padding-bottom: 0.3em;
+ padding-right: 1.4em;
+ border-top: solid 1px gray;
+ vertical-align: top;
+ font-family: monospace;
+}
+form {
+ background-color: lightblue;
+ border: 1px solid gray;
+ padding: 2px;
+}
+iframe#content {
+ border: none;
+ width: 0px;
+ height: 0px;
+}
+/*
+form {
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+}
+*/
+</style>
+</head>
+<body>
+<form name="ui">
+ SVN path: <input id="url" type="text" name="url" value="">
+ SVN revision range: <input id="range" type="text" name="range" value="">
+ <input id="mode_text" type="radio" name="mode" value="text">text
+ <input id="mode_html" type="radio" name="mode" value="html">html
+ <input type="submit" value="Show Changelog">
+</form>
+
+<script>
+params = ParseParams();
+
+function fix_text(str, n) {
+ if (str.length > n)
+ return str.substring(0, n);
+
+ for (var i = str.length; i < n; ++i)
+ str = str + ' ';
+ return str;
+}
+
+function get_entries() {
+ return content.contentDocument.getElementsByTagName("logentry");
+}
+
+function get_info(entry) {
+ var r = new Object;
+ r.rev = entry.getAttribute("revision");
+ r.author = entry.getElementsByTagName("author")[0].textContent;
+ r.msg = entry.getElementsByTagName("msg")[0].textContent;
+ r.paths = [];
+ var paths = entry.getElementsByTagName("path")
+ for (var i = 0; i < paths.length; ++i) {
+ r.paths.push({"action" : paths[i].getAttribute("action"),
+ "value" : paths[i].textContent});
+ }
+ return r;
+}
+
+function render_log_callback() {
+ if ("mode" in params && params.mode == "text") {
+ var out = document.createElement("PRE");
+ document.body.appendChild(out);
+
+ var entries = get_entries();
+ for (var i = 0; i < entries.length; ++i) {
+ var info = get_info(entries[i]);
+
+ var msg = info.msg;
+ msg = msg.replace(/\n/g, ' ' );
+ msg = msg.replace(/\t/g, ' ' );
+ while (msg.charAt(0) == ' ')
+ msg = msg.substring(1);
+
+ var msg_clipped = msg.substring(0, 66);
+ if (msg_clipped.length < msg.length)
+ msg_clipped = msg_clipped + "...";
+
+ out.appendChild(document.createTextNode(
+ fix_text(info.rev, 6) + " " +
+ fix_text(info.author, 8) + " " +
+ msg_clipped + "\n"));
+ }
+ } else {
+ var table = document.createElement("TABLE");
+ table.setAttribute("class", "log");
+ document.body.appendChild(table);
+
+ var entries = get_entries();
+ for (var i = 0; i < entries.length; ++i) {
+ var info = get_info(entries[i]);
+
+ var tr = document.createElement("TR");
+ table.appendChild(tr);
+
+ var td, a;
+
+ // revision:
+ td = document.createElement("TD");
+ tr.appendChild(td);
+
+ a = document.createElement("A");
+ a.setAttribute("href", "http://scons.tigris.org/source/browse/scons?view=rev&revision=" + info.rev);
+ a.appendChild(document.createTextNode(info.rev));
+
+ td.appendChild(a);
+
+ // author:
+ td = document.createElement("TD");
+ tr.appendChild(td);
+
+ a = document.createElement("A");
+ a.setAttribute("href", "mailto:" + info.author);
+ a.appendChild(document.createTextNode(info.author));
+
+ td.appendChild(a);
+
+ // details:
+ td = document.createElement("TD");
+ tr.appendChild(td);
+
+ var p = document.createElement("PRE");
+ td.appendChild(p);
+
+ var s = info.msg;
+ p.appendChild(document.createTextNode(s));
+
+ for (var j = 0; j < info.paths.length; ++j) {
+ td.appendChild(document.createTextNode(info.paths[j]["action"] + " - "))
+ var a = document.createElement("A");
+ a.setAttribute("href", "http://scons.tigris.org/source/browse/scons" + info.paths[j]["value"] + "?r1=" + info.rev + "&r2=" + (info.rev - 1) + "&pathrev=" + info.rev);
+ a.appendChild(document.createTextNode(info.paths[j]["value"]));
+ td.appendChild(a);
+ td.appendChild(document.createElement("BR"));
+ }
+ }
+ }
+}
+
+function render_log() {
+ var svn_url = params["url"];
+ var svn_range = params["range"];
+ if (svn_url == undefined || svn_range == undefined)
+ return;
+
+ var url = "http://" + location.host + "/cgi-bin/svn-log?url=http://codf21.jail/svn/" +
+ unescape(svn_url) + "&range=" + unescape(svn_range);
+
+ // global 'content' variable: a hidden iframe used to fetch svn data.
+ content = document.createElement("IFRAME");
+ content.setAttribute("id", "content");
+ content.setAttribute("onload", "render_log_callback()");
+ content.setAttribute("src", url);
+ document.body.appendChild(content);
+
+ var el;
+ if ("mode" in params && params["mode"] == "text") {
+ el = document.getElementById("mode_text");
+ } else {
+ el = document.getElementById("mode_html");
+ }
+ el.setAttribute("checked", "1");
+
+ el = document.getElementById("url");
+ el.setAttribute("value", unescape(svn_url));
+
+ el = document.getElementById("range");
+ el.setAttribute("value", unescape(svn_range));
+}
+
+render_log()
+</script>
+</body>
+</html>
+
diff --git a/timings/graph.html b/timings/graph.html
new file mode 100644
index 00000000..e418069d
--- /dev/null
+++ b/timings/graph.html
@@ -0,0 +1,411 @@
+<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>
+var Config = {
+ 'title': "TODO title",
+ 'source': "http://scons.tigris.org/svn/scons/trunk",
+ 'changeLinkPrefix': "changelog.html?mode=html&range=",
+ 'builder': "TODO ",
+ 'builderLink': "http://buildbot.scons.org:8010/",
+ 'detailTabs': ['view-change'],
+};
+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();
+if (!('history' in params)) {
+ params.history = 150;
+ // make this option somewhat user discoverable :-/
+ window.location.href = MakeURL(params);
+}
+
+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(/50/, "150");
+ new_url = new_url.replace(/\&lookout/, "");
+ 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').
+ setAttribute('src', Config.changeLinkPrefix + prev_cl + ':' + 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 (max_rows > params.history)
+ max_rows = params.history;
+
+ 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">
+Builds generated by the <a href="http://buildbot.chromium.org/">buildbot</a>
+are run through <b>
+<script>
+document.write(Config.title);
+</script>
+</b>and the results of that test are charted here.
+</div>
+
+<div id="explain">
+The vertical axis is measured values, and the horizontal
+axis is the revision number for the build 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>
diff --git a/timings/index.html b/timings/index.html
new file mode 100644
index 00000000..de102ead
--- /dev/null
+++ b/timings/index.html
@@ -0,0 +1,196 @@
+<html>
+ <head>
+ <title>SCons Timings</title>
+ <script src="js/common.js"></script>
+ <script language="javascript">
+ var perf_url = DirName(window.location.href);
+ function DisplayGraph(name, heighth, width, thumbnail, graph) {
+ var params = ParseParams();
+ var url = perf_url + '/' + name + '/graph.html' +
+ '?history=50' +
+ '&lookout';
+ //var debug = url;
+ var debug = '';
+ if (typeof graph == 'undefined') {
+ if ('graph' in params) {
+ graph = params.graph;
+ }
+ url += '&header=';
+ } else if (graph != null) {
+ url += '&header=' + graph;
+ }
+ if (graph != null) {
+ url += '&graph=' + graph;
+ }
+ if (thumbnail) {
+ url += '&thumbnail';
+ }
+ document.write('<td>' + debug + '<iframe' +
+ //' scrolling="no"' +
+ ' height="' + heighth + '"' +
+ ' width="' + width + '"' +
+ ' src="' + url + '">' +
+ '</iframe></td>');
+ }
+ </script>
+ </head>
+ <body>
+ <center>
+ <script language="javascript">
+ var params = ParseParams();
+ // TODO: parameterize these in a .dat file
+ var builder_map = {
+ 'Ubuntu': 'ubuntu-timings',
+ };
+ var graphs_map = {
+ 'Performance': 'TimeSCons-elapsed',
+ 'Memory': 'memory-final',
+ };
+ var test_map = {
+ 'CPPPATH': 'CPPPATH',
+ 'JTimer': 'JTimer',
+ 'hundred': 'hundred',
+ };
+ if ('builder' in params) {
+ builder_list = params.builder.split(',');
+ } else {
+ builder_list = Keys(builder_map);
+ }
+ if ('graph' in params) {
+ graphs_list = param.graph.split(',');
+ } else {
+ graphs_list = Keys(graphs_map);
+ }
+ if ('test' in params) {
+ test_list = params.test.split(',');
+ } else {
+ test_list = Keys(test_map);
+ }
+ function write_builder_table() {
+ document.write('<table><tr>');
+ for (var tindex=0; tindex < test_list.length; tindex++) {
+ test = test_list[tindex];
+ test_name = test_map[test];
+ DisplayGraph(builder_id + '/' + test, 250, 400, false);
+ DisplayGraph(builder_id + '/' + test, 250, 400, false);
+ if ((tindex % 3) == 2) {
+ document.write('</tr><tr>');
+ }
+ }
+ document.write('</tr></table>');
+ }
+ function write_test_table() {
+ document.write('<table>');
+ row = [];
+ function write_row(row) {
+ document.write('<tr>');
+ for (var bidx in row) {
+ builder = row[bidx];
+ document.write('<th align="center"><p><br>' +
+ builder + '</p></th>');
+ }
+ document.write('</tr><tr>');
+ for (var bidx in row) {
+ builder_id = builder_map[row[bidx]];
+ DisplayGraph(builder_id + '/' + test, 250, 400, true);
+ }
+ document.write('</tr>');
+ }
+ for (var bindex=0; bindex < builder_list.length; bindex++) {
+ builder = builder_list[bindex];
+ row.push(builder)
+ if ((bindex % 3) == 2) {
+ write_row(row);
+ row = [];
+ }
+ }
+ if (row.length > 0) {
+ write_row(row);
+ }
+ document.write('</table>');
+ }
+ function write_builders_header_row() {
+ document.write('<tr><th>&nbsp;</th>');
+ for (var bindex=0; bindex < builder_list.length; bindex++) {
+ builder = builder_list[bindex];
+ url = MakeURL({'builder':escape(builder)});
+ var s ='<th align="center" colspan="' +
+ graphs_list.length +
+ '">' +
+ '<a href="' + url + '">' +
+ builder +
+ '</a></th>';
+ document.write(s);
+ }
+ document.write('</tr>');
+ }
+ function write_graphs_header_row() {
+ document.write('<tr><th>&nbsp;</th>');
+ for (var bindex=0; bindex < builder_list.length; bindex++) {
+ for (var gindex=0; gindex < graphs_list.length; gindex++) {
+ graph = graphs_list[gindex];
+ document.write('<th align="center">' + graph + '</th>');
+ }
+ }
+ }
+ function write_overview_table() {
+ document.write('<table>');
+ var width = document.body.scrollWidth / (builder_list.length + 2);
+ write_builders_header_row();
+ for (var tindex=0; tindex < test_list.length; tindex++) {
+ // Write a graphs header every four rows for navigability.
+ // TODO: Use more sophisticated freezing of the header rows?
+ if ((tindex % 4) == 0) {
+ write_graphs_header_row();
+ }
+ test = test_list[tindex];
+ test_name = test_map[test];
+ document.write('<tr>');
+ url = MakeURL({'test':escape(test)});
+ document.write('<th valign="center"><a href="' + url + '">'
+ + test_name + '</a></th>');
+ for (var bindex=0; bindex < builder_list.length; bindex++) {
+ builder = builder_list[bindex];
+ builder_id = builder_map[builder];
+ DisplayGraph(builder_id + '/' + test, 250, width, true, "TimeSCons-elapsed")
+ DisplayGraph(builder_id + '/' + test, 250, width, true, "memory-final")
+ }
+ document.write('<th valign="center"><a href="' + url + '">'
+ + test_name + '</a></th>');
+ document.write('</tr>');
+ }
+ write_graphs_header_row();
+ write_builders_header_row();
+ document.write('</table>');
+ }
+ function write_header(header_string, url_string) {
+ document.write('<h1><a href="' + window.location.pathname + '">'
+ + 'SCons Timings</a>');
+ if (header_string) {
+ document.write(': ' + header_string);
+ }
+ if ('graph' in params) {
+ document.write(': ' + escape(params.graph))
+ } else {
+ document.write(': overview');
+ }
+ document.write('</h1>');
+ }
+ if (builder_list.length == 0) {
+ builder = builder_list[0];
+ builder_id = builder_map[builder];
+ write_header(builder, '&builder=' + escape(builder));
+ write_builder_table()
+ } else if (test_list.length == 1) {
+ test = test_list[0];
+ test_name = test_map[test];
+ write_header(test_name, '&test=' + escape(test));
+ write_test_table()
+ } else {
+ write_header('', '');
+ write_overview_table();
+ }
+ </script>
+ </center>
+ </body>
+</html>
diff --git a/timings/js/common.js b/timings/js/common.js
new file mode 100644
index 00000000..80510b32
--- /dev/null
+++ b/timings/js/common.js
@@ -0,0 +1,96 @@
+/*
+ Copyright (c) 2006-2008 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.
+*/
+
+/*
+ Common methods for performance-plotting JS.
+*/
+
+function Fetch(url, callback) {
+ var r = new XMLHttpRequest();
+ r.open("GET", url, true);
+ r.setRequestHeader("pragma", "no-cache");
+ r.setRequestHeader("cache-control", "no-cache");
+ r.onreadystatechange = function() {
+ if (r.readyState == 4) {
+ var error;
+ var text = r.responseText;
+ if (r.status != 200) {
+ error = url + ": " + r.status + ": " + r.statusText;
+ } else if (! text) {
+ error = url + ": null response";
+ }
+ callback(text, error);
+ }
+ }
+
+ r.send(null);
+}
+
+// Returns the keys of an object.
+function Keys(obj) {
+ result = [];
+ for (key in obj) {
+ result.push(key)
+ }
+ return result
+}
+
+// Returns the "directory name" portion of the string (URL),
+// stripping the last element.
+function DirName(s) {
+ elements = s.split('/')
+ elements.pop()
+ return elements.join('/')
+}
+
+// Returns an Object with properties given by the parameters specified in the
+// URL's query string.
+function ParseParams() {
+ var result = new Object();
+ var s = window.location.search.substring(1).split('&');
+ for (i = 0; i < s.length; ++i) {
+ var v = s[i].split('=');
+ result[v[0]] = unescape(v[1]);
+ }
+ return result;
+}
+
+// Creates the URL constructed from the current pathname and the given params.
+function MakeURL(params) {
+ var url = window.location.pathname;
+ var sep = '?';
+ for (p in params) {
+ if (!p)
+ continue;
+ url = url + sep + p + '=' + params[p];
+ sep = '&';
+ }
+ return url;
+}
+
+// Returns a string describing an object, recursively. On the initial call,
+// |name| is optionally the name of the object and |indent| is not needed.
+function DebugDump(obj, opt_name, opt_indent) {
+ var name = opt_name || '';
+ var indent = opt_indent || '';
+ if (typeof obj == "object") {
+ var child = null;
+ var output = indent + name + "\n";
+
+ for (var item in obj) {
+ try {
+ child = obj[item];
+ } catch (e) {
+ child = "<Unable to Evaluate>";
+ }
+ output += DebugDump(child, item, indent + " ");
+ }
+
+ return output;
+ } else {
+ return indent + name + ": " + obj + "\n";
+ }
+}
diff --git a/timings/js/coordinates.js b/timings/js/coordinates.js
new file mode 100644
index 00000000..69cb4c22
--- /dev/null
+++ b/timings/js/coordinates.js
@@ -0,0 +1,125 @@
+/*
+ Copyright (c) 2006-2008 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.
+*/
+
+/**
+ * 'Understands' plot data positioning.
+ * @constructor
+ *
+ * @param {Array} plotData data that will be displayed
+ */
+function Coordinates(plotData) {
+ this.plotData = plotData;
+
+ height = window.innerHeight - 16;
+ width = window.innerWidth - 16;
+
+ this.widthMax = width;
+ this.heightMax = Math.min(400, height - 85);
+
+ this.xMinValue = -0.5;
+ this.xMaxValue = (this.plotData[0].length - 1)+ 0.5;
+ this.processYValues_();
+}
+
+Coordinates.prototype.processYValues_ = function () {
+ var merged = [];
+ for (var i = 0; i < this.plotData.length; i++)
+ for (var j = 0; j < this.plotData[i].length; j++)
+ merged.push(this.plotData[i][j][0]);
+ var max = Math.max.apply( Math, merged );
+ var min = Math.min.apply( Math, merged );
+
+ // If we have a missing value, find the real max and min the hard way.
+ if (isNaN(min)) {
+ for (var i = 0; i < merged.length; ++i) {
+ if (isNaN(min) || merged[i] < min)
+ min = merged[i];
+ if (isNaN(max) || merged[i] > max)
+ max = merged[i];
+ }
+ }
+ var yd = (max - min) / 10.0;
+ if (yd == 0)
+ yd = max / 10;
+ this.yMinValue = min - yd;
+ this.yMaxValue = max + yd;
+};
+
+/**
+ * Difference between horizontal max min values.
+ */
+Coordinates.prototype.xValueRange = function() {
+ return this.xMaxValue - this.xMinValue;
+};
+
+/**
+ * Difference between vertical max min values.
+ */
+Coordinates.prototype.yValueRange = function() {
+ return this.yMaxValue - this.yMinValue
+};
+
+/**
+ * Converts horizontal data value to pixel value on canvas.
+ * @param {number} value horizontal data value
+ */
+Coordinates.prototype.xPoints = function(value) {
+ return this.widthMax * ((value - this.xMinValue) / this.xValueRange());
+};
+
+/**
+ * Converts vertical data value to pixel value on canvas.
+ * @param {number} value vertical data value
+ */
+Coordinates.prototype.yPoints = function(value) {
+ /* Converts value to canvas Y position in pixels. */
+ return this.heightMax - this.heightMax * (value - this.yMinValue) /
+ this.yValueRange();
+};
+
+/**
+ * Converts X point on canvas to value it represents.
+ * @param {number} position horizontal point on canvas.
+ */
+Coordinates.prototype.xValue = function(position) {
+ /* Converts canvas X pixels to value. */
+ return position / this.widthMax * (this.xValueRange()) + this.xMinValue;
+};
+
+/**
+ * Converts Y point on canvas to value it represents.
+ * @param {number} position vertical point on canvas.
+ */
+Coordinates.prototype.yValue = function(position) {
+ /* Converts canvas Y pixels to value.
+ position is point value is from top.
+ */
+ var position = this.heightMax - position;
+ var ratio = parseFloat(this.heightMax / position);
+ return this.yMinValue + this.yValueRange() / ratio;
+};
+
+/**
+ * Converts canvas X pixel to data index.
+ * @param {number} xPosition horizontal point on canvas
+ */
+Coordinates.prototype.dataSampleIndex = function(xPosition) {
+ var xValue = this.xValue(xPosition);
+ var index;
+ if (xValue < 0) {
+ index = 0;
+ } else if (xValue > this.plotData[0].length - 1) {
+ index = this.plotData[0].length - 1;
+ } else {
+ index = xValue.toFixed(0);
+ }
+ return index;
+};
+
+Coordinates.prototype.log = function(val) {
+ document.getElementById('log').appendChild(
+ document.createTextNode(val + '\n'));
+};
diff --git a/timings/js/plotter.js b/timings/js/plotter.js
new file mode 100644
index 00000000..86fb2304
--- /dev/null
+++ b/timings/js/plotter.js
@@ -0,0 +1,336 @@
+/*
+ Copyright (c) 2006-2008 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.
+*/
+
+// Collection of classes used to plot data in a <canvas>. Create a Plotter()
+// to generate a plot.
+
+// vertical marker for columns
+function Marker(color) {
+ var m = document.createElement("DIV");
+ m.setAttribute("class", "plot-cursor");
+ m.style.backgroundColor = color;
+ m.style.opacity = "0.3";
+ m.style.position = "absolute";
+ m.style.left = "-2px";
+ m.style.top = "-2px";
+ m.style.width = "0px";
+ m.style.height = "0px";
+ return m;
+}
+
+/**
+ * HorizontalMarker class
+ * Create a horizontal marker at the indicated mouse location.
+ * @constructor
+ *
+ * @param canvasRect {Object} The canvas bounds (in client coords).
+ * @param clientY {Number} The vertical mouse click location that spawned
+ * the marker, in the client coordinate space.
+ * @param yValue {Number} The plotted value corresponding to the clientY
+ * click location.
+ */
+function HorizontalMarker(canvasRect, clientY, yValue) {
+ // Add a horizontal line to the graph.
+ var m = document.createElement("DIV");
+ m.setAttribute("class", "plot-baseline");
+ m.style.backgroundColor = HorizontalMarker.COLOR;
+ m.style.opacity = "0.3";
+ m.style.position = "absolute";
+ m.style.left = canvasRect.offsetLeft;
+ var h = HorizontalMarker.HEIGHT;
+ m.style.top = (clientY - h/2).toFixed(0) + "px";
+ m.style.width = canvasRect.width + "px";
+ m.style.height = h + "px";
+ this.markerDiv_ = m;
+
+ this.value = yValue;
+}
+
+HorizontalMarker.HEIGHT = 5;
+HorizontalMarker.COLOR = "rgb(0,100,100)";
+
+// Remove the horizontal line from the graph.
+HorizontalMarker.prototype.remove_ = function() {
+ this.markerDiv_.parentNode.removeChild(this.markerDiv_);
+}
+
+/**
+ * Plotter class
+ * @constructor
+ *
+ * Draws a chart using CANVAS element. Takes array of lines to draw with
+ * deviations values for each data sample.
+ *
+ * @param {Array} clNumbers list of clNumbers for each data sample.
+ * @param {Array} plotData list of arrays that represent individual lines.
+ * The line itself is an Array of value and stdd.
+ * @param {Array} dataDescription list of data description for each line
+ * in plotData.
+ * @units {string} units name of measurement used to describe plotted data.
+ *
+ * Example of the plotData:
+ * [
+ * [line 1 data],
+ * [line 2 data]
+ * ].
+ * Line data looks like [[point one], [point two]].
+ * And individual points are [value, deviation value]
+ */
+function Plotter(clNumbers, plotData, dataDescription, units, resultNode) {
+ this.clNumbers_ = clNumbers;
+ this.plotData_ = plotData;
+ this.dataDescription_ = dataDescription;
+ this.resultNode_ = resultNode;
+ this.units_ = units;
+ this.coordinates = new Coordinates(plotData);
+
+ // A color palette that's unambigous for normal and color-deficient viewers.
+ // Values are (red, green, blue) on a scale of 255.
+ // Taken from http://jfly.iam.u-tokyo.ac.jp/html/manuals/pdf/color_blind.pdf
+ this.colors = [[0, 114, 178], // blue
+ [230, 159, 0], // orange
+ [0, 158, 115], // green
+ [204, 121, 167], // purplish pink
+ [86, 180, 233], // sky blue
+ [213, 94, 0], // dark orange
+ [0, 0, 0], // black
+ [240, 228, 66] // yellow
+ ];
+}
+
+/**
+ * Does the actual plotting.
+ */
+Plotter.prototype.plot = function() {
+ var canvas = this.canvas();
+ this.coordinates_div_ = this.coordinates_();
+ this.ruler_div_ = this.ruler();
+ // marker for the result-point that the mouse is currently over
+ this.cursor_div_ = new Marker("rgb(100,80,240)");
+ // marker for the result-point for which details are shown
+ this.marker_div_ = new Marker("rgb(100,100,100)");
+ var ctx = canvas.getContext("2d");
+ for (var i = 0; i < this.plotData_.length; i++)
+ this.plotLine_(ctx, this.nextColor(i), this.plotData_[i]);
+
+ this.resultNode_.appendChild(canvas);
+ this.resultNode_.appendChild(this.coordinates_div_);
+
+ this.resultNode_.appendChild(this.ruler_div_);
+ this.resultNode_.appendChild(this.cursor_div_);
+ this.resultNode_.appendChild(this.marker_div_);
+ this.attachEventListeners(canvas);
+ this.canvasRectangle = {
+ "offsetLeft": canvas.offsetLeft,
+ "offsetTop": canvas.offsetTop,
+ "width": canvas.offsetWidth,
+ "height": canvas.offsetHeight
+ };
+};
+
+Plotter.prototype.drawDeviationBar_ = function(context, strokeStyles, x, y,
+ deviationValue) {
+ context.strokeStyle = strokeStyles;
+ context.lineWidth = 1.0;
+ context.beginPath();
+ context.moveTo(x, (y + deviationValue));
+ context.lineTo(x, (y - deviationValue));
+ context.moveTo(x, (y - deviationValue));
+ context.closePath();
+ context.stroke();
+};
+
+Plotter.prototype.plotLine_ = function(ctx, strokeStyles, data) {
+ ctx.strokeStyle = strokeStyles;
+ ctx.lineWidth = 2.0;
+ ctx.beginPath();
+ var initial = true;
+ var deviationData = [];
+ for (var i = 0; i < data.length; i++) {
+ var x = this.coordinates.xPoints(i);
+ var value = data[i][0];
+ var stdd = data[i][1];
+ var y = 0.0;
+ var err = 0.0;
+ if (isNaN(value)) {
+ // Re-set 'initial' if we're at a gap in the data.
+ initial = true;
+ } else {
+ y = this.coordinates.yPoints(value);
+ // We assume that the stdd will only be NaN (missing) when the value is.
+ if (parseFloat(value) != 0.0)
+ err = y * parseFloat(stdd) / parseFloat(value);
+ if (initial)
+ initial = false;
+ else
+ ctx.lineTo(x, y);
+ }
+
+ ctx.moveTo(x, y);
+ deviationData.push([x, y, err])
+ }
+ ctx.closePath();
+ ctx.stroke();
+
+ for (var i = 0; i < deviationData.length; i++) {
+ this.drawDeviationBar_(ctx, strokeStyles, deviationData[i][0],
+ deviationData[i][1], deviationData[i][2]);
+ }
+};
+
+Plotter.prototype.attachEventListeners = function(canvas) {
+ var self = this;
+ canvas.parentNode.addEventListener(
+ "mousemove", function(evt) { self.onMouseMove_(evt); }, false);
+ this.cursor_div_.addEventListener(
+ "click", function(evt) { self.onMouseClick_(evt); }, false);
+};
+
+Plotter.prototype.updateRuler_ = function(evt) {
+ var r = this.ruler_div_;
+ r.style.left = this.canvasRectangle.offsetLeft + "px";
+
+ r.style.top = this.canvasRectangle.offsetTop + "px";
+ r.style.width = this.canvasRectangle.width + "px";
+ var h = evt.clientY - this.canvasRectangle.offsetTop;
+ if (h > this.canvasRectangle.height)
+ h = this.canvasRectangle.height;
+ r.style.height = h + "px";
+};
+
+Plotter.prototype.updateCursor_ = function() {
+ var c = this.cursor_div_;
+ c.style.top = this.canvasRectangle.offsetTop + "px";
+ c.style.height = this.canvasRectangle.height + "px";
+ var w = this.canvasRectangle.width / this.clNumbers_.length;
+ var x = (this.canvasRectangle.offsetLeft +
+ w * this.current_index_).toFixed(0);
+ c.style.left = x + "px";
+ c.style.width = w + "px";
+};
+
+
+Plotter.prototype.onMouseMove_ = function(evt) {
+ var canvas = evt.currentTarget.firstChild;
+ var positionX = evt.clientX - this.canvasRectangle.offsetLeft;
+ var positionY = evt.clientY - this.canvasRectangle.offsetTop;
+
+ this.current_index_ = this.coordinates.dataSampleIndex(positionX);
+ var yValue = this.coordinates.yValue(positionY);
+
+ this.coordinates_td_.innerHTML =
+ "r" + this.clNumbers_[this.current_index_] + ": " +
+ this.plotData_[0][this.current_index_][0].toFixed(2) + " " +
+ this.units_ + " +/- " +
+ this.plotData_[0][this.current_index_][1].toFixed(2) + " " +
+ yValue.toFixed(2) + " " + this.units_;
+
+ // If there is a horizontal marker, also display deltas relative to it.
+ if (this.horizontal_marker_) {
+ var baseline = this.horizontal_marker_.value;
+ var delta = yValue - baseline
+ var fraction = delta / baseline; // allow division by 0
+
+ var deltaStr = (delta >= 0 ? "+" : "") + delta.toFixed(0) + " " +
+ this.units_;
+ var percentStr = (fraction >= 0 ? "+" : "") +
+ (fraction * 100).toFixed(3) + "%";
+
+ this.baseline_deltas_td_.innerHTML = deltaStr + ": " + percentStr;
+ }
+
+ this.updateRuler_(evt);
+ this.updateCursor_();
+};
+
+Plotter.prototype.onMouseClick_ = function(evt) {
+ // Shift-click controls the horizontal reference line.
+ if (evt.shiftKey) {
+ if (this.horizontal_marker_) {
+ this.horizontal_marker_.remove_();
+ }
+
+ var canvasY = evt.clientY - this.canvasRectangle.offsetTop;
+ this.horizontal_marker_ = new HorizontalMarker(this.canvasRectangle,
+ evt.clientY, this.coordinates.yValue(canvasY));
+
+ // Insert before cursor node, otherwise it catches clicks.
+ this.cursor_div_.parentNode.insertBefore(
+ this.horizontal_marker_.markerDiv_, this.cursor_div_);
+ } else {
+ var index = this.current_index_;
+ var m = this.marker_div_;
+ var c = this.cursor_div_;
+ m.style.top = c.style.top;
+ m.style.left = c.style.left;
+ m.style.width = c.style.width;
+ m.style.height = c.style.height;
+ if ("onclick" in this) {
+ var this_x = this.clNumbers_[index];
+ var prev_x = index > 0 ? (parseInt(this.clNumbers_[index-1]) + 1) :
+ this_x;
+ this.onclick(prev_x, this_x);
+ }
+ }
+};
+
+Plotter.prototype.canvas = function() {
+ var canvas = document.createElement("CANVAS");
+ canvas.setAttribute("id", "_canvas");
+ canvas.setAttribute("class", "plot");
+ canvas.setAttribute("width", this.coordinates.widthMax);
+ canvas.setAttribute("height", this.coordinates.heightMax);
+ return canvas;
+};
+
+Plotter.prototype.ruler = function() {
+ ruler = document.createElement("DIV");
+ ruler.setAttribute("class", "plot-ruler");
+ ruler.style.borderBottom = "1px dotted black";
+ ruler.style.position = "absolute";
+ ruler.style.left = "-2px";
+ ruler.style.top = "-2px";
+ ruler.style.width = "0px";
+ ruler.style.height = "0px";
+ return ruler;
+};
+
+Plotter.prototype.coordinates_ = function() {
+ var coordinatesDiv = document.createElement("DIV");
+ var table_html =
+ "<table border=0 width='100%'><tbody><tr>" +
+ "<td colspan=2 class='legend'>Legend: ";
+ for (var i = 0; i < this.dataDescription_.length; i++) {
+ if (i > 0)
+ table_html += ", ";
+ table_html += "<span class='legend_item' style='color:" +
+ this.nextColor(i) + "'>" + this.dataDescription_[i] + "</span>";
+ }
+ table_html += "</td></tr><tr>" +
+ "<td class='plot-coordinates'><i>move mouse over graph</i></td>" +
+ "<td align=right style='color: " + HorizontalMarker.COLOR +
+ "'><i>Shift-click to place baseline</i></td>" +
+ "</tr></tbody></table>";
+ coordinatesDiv.innerHTML = table_html;
+
+ var tr = coordinatesDiv.firstChild.firstChild.childNodes[1];
+ this.coordinates_td_ = tr.childNodes[0];
+ this.baseline_deltas_td_ = tr.childNodes[1];
+
+ return coordinatesDiv;
+};
+
+Plotter.prototype.nextColor = function(i) {
+ var index = i % this.colors.length;
+ return "rgb(" + this.colors[index][0] + "," +
+ this.colors[index][1] + "," +
+ this.colors[index][2] + ")";
+};
+
+Plotter.prototype.log = function(val) {
+ document.getElementById('log').appendChild(
+ document.createTextNode(val + '\n'));
+};