added blurb to RELEASE.txt
[scons.git] / timings / graph.html
blobde12ff5b8cf3b7472d49c8ad254a911274204c7d
1 <html>
3 <!--
4 Copyright (c) 2006-2009 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 -->
9 <!--
10 A brief note on terminology as used here: a "graph" is a plotted screenful
11 of data, showing the results of one type of test: for example, the
12 page-load-time graph. A "trace" is a single line on a graph, showing one
13 one for the test: for example, the reference build trace on the
14 page-load-time graph.
16 This page plots arbitrary numerical data loaded from files in a specific
17 format. It uses two or more data files, all JSON-encoded:
19 graphs.dat: a list of objects, each with these properties: name (the name
20 of a graph) and units (the units for the data to be read by humans).
21 Schematically:
22 [{"name": <graph_name>, "units": <units>}, ...]
24 <graphname>-summary.dat: for each of the graphs listed in graphs.dat, the
25 corresponding summary file holds rows of data. Each row of data is an
26 object with several properties:
27 "rev": the revision number for this row of data
28 "traces": an object with several properties of its own. The name of
29 the property corresponds to a trace name, used only as an
30 internal identifier, and the property's value is an array of
31 its measurement and that measurement's standard deviation (or
32 other measurement error).
33 Schematically:
34 {"rev": <rev>,
35 "traces": {<trace_name1>: [<value1>, <stddev1>],
36 <trace_name2>: [<value2>, <stddev2>], ...}
38 -->
39 <head>
40 <style>
41 body {
42 font-family: sans-serif;
44 div#output {
45 cursor: pointer;
47 div#switcher {
48 cursor: pointer;
50 div#switcher a {
51 border-top: 1px solid black;
52 border-left: 1px solid black;
53 padding-left: 0.5em;
54 padding-right: 0.5em;
56 canvas.plot {
57 border: 1px solid black;
59 div.plot-coordinates {
60 font-family: monospace;
62 iframe {
63 display: none;
64 width: 100%;
65 height: 100%;
66 border: none;
68 div.selector {
69 border: solid 1px black;
70 cursor: pointer;
71 padding-left: 0.3em;
72 background-color: white;
74 div.selector:hover {
75 background-color: rgb(200,200,250);
77 div.selected {
78 border-left: none;
80 div#selectors {
81 width: 80px;
82 display: none;
84 #explain {
85 font-size: 0.75em;
86 font-style: italic;
87 color: rgb(100,100,100);
89 </style>
91 <script src="js/common.js"></script>
92 <script src="js/plotter.js"></script>
93 <script src="js/coordinates.js"></script>
94 <script src="config.js"></script>
95 <script>
96 Config.source = "http://scons.tigris.org/svn/scons/trunk";
97 Config.changeLinkPrefix = "changelog.html?mode=html&range=";
98 Config.builder = "TODO";
99 Config.buildbotLink = "http://buildbot.scons.org:8010/";
100 Config.detailTabs = {'view-change': 'CL'};
101 document.title = Config.title + ' - ' + Config.buildslave;
103 var did_position_details = false;
104 var units = 'thing-a-ma-bobs';
105 var graph_list = [];
106 var first_trace = '';
108 var params = ParseParams();
110 function jsonToJs(data) {
111 return eval('(' + data + ')')
114 function report_error(error) {
115 document.getElementById("output").innerHTML = "<p>" + error + "</p>";
118 function received_graph_list(data, error) {
119 if (error) {
120 report_error(error);
121 return;
123 graph_list = jsonToJs(data);
125 if (!('graph' in params) || params.graph == '') {
126 if (graph_list.length > 0)
127 params.graph = graph_list[0].name
130 // Add a selection tab for each graph, and find the units for the selected
131 // one while we're at it.
132 tabs = [];
133 for (var index = 0; index < graph_list.length; ++index) {
134 var graph = graph_list[index];
135 tabs.push(graph.name);
136 if (graph.name == params.graph)
137 units = graph.units;
139 initPlotSwitcher(tabs);
141 // Fetch the data for the selected graph.
142 fetch_summary();
145 function go_to(graph) {
146 params.graph = graph;
147 if (params.graph == '')
148 delete params.graph;
149 window.location.href = MakeURL(params);
152 function get_url() {
153 new_url = window.location.href;
154 new_url = new_url.replace(/\?lookout/, "?");
155 new_url = new_url.replace(/\&thumbnail/, "");
156 return new_url;
159 function on_clicked_plot(prev_cl, cl) {
160 if ('lookout' in params) {
161 window.open(get_url());
162 return;
165 // Define sources for detail tabs
166 if ('view-change' in Config.detailTabs) {
167 document.getElementById('view-change').
168 // TODO: The tigris.org source browser only lets us pull up
169 // one revision. That's okay for our current behavior of
170 // timing each revision separately, but if we go back to merging
171 // build requests from multiple revisions, we'll need an
172 // intermediary CGI script.
173 //setAttribute('src', Config.changeLinkPrefix + prev_cl + ':' + cl);
174 setAttribute('src',
175 'http://scons.tigris.org/source/browse/scons?view=rev&revision=' + cl);
177 if ('view-pages' in Config.detailTabs) {
178 document.getElementById('view-pages').
179 setAttribute('src', 'details.html?cl=' + cl + '&trace=' + first_trace);
181 if ('view-coverage' in Config.detailTabs) {
182 document.getElementById('view-coverage').
183 setAttribute('src', Config.coverageLinkPrefix + cl);
186 if (!did_position_details) {
187 position_details();
188 did_position_details = true;
192 function received_summary(data, error) {
193 if (error) {
194 report_error(error);
195 return;
197 // Parse the summary data file.
198 var rows = data.split('\n');
199 var max_rows = rows.length;
200 if ('history' in params && max_rows > params.history) {
201 max_rows = params.history;
202 } else if ('lookout' in params && max_rows > 150) {
203 max_rows = 150;
206 var allTraces = {};
208 // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...}
209 var graphData = {};
210 for (var i = 0; i < max_rows; ++i) {
211 if (!rows[i].length)
212 continue;
213 var row = jsonToJs(rows[i]);
214 var traces = row['traces'];
215 var revision = parseInt(row['rev']);
216 graphData[revision] = traces;
218 // Collect unique trace names.
219 for (var traceName in traces)
220 allTraces[traceName] = 1;
223 // Build a list of all the trace names we've seen, in the order in which
224 // they appear in the data file. Although JS objects are not required by
225 // the spec to iterate their properties in order, in practice they do,
226 // because it causes compatibility problems otherwise.
227 var traceNames = [];
228 for (var traceName in allTraces)
229 traceNames.push(traceName);
231 first_trace = traceNames[0];
233 // Build and numerically sort a list of revision numbers.
234 var revisionNumbers = [];
235 for (var rev in graphData)
236 revisionNumbers.push(rev);
237 revisionNumbers.sort(
238 function(a, b) { return parseInt(a, 10) - parseInt(b, 10) });
240 // Build separate ordered lists of trace data.
241 var traceData = {};
242 for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) {
243 var rev = revisionNumbers[revIndex];
244 var revisionData = graphData[rev];
245 for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) {
246 var traceName = traceNames[nameIndex];
247 if (!traceData[traceName])
248 traceData[traceName] = [];
249 if (!revisionData[traceName])
250 traceData[traceName].push([NaN, NaN]);
251 else
252 traceData[traceName].push(revisionData[traceName]);
255 var plotData = [];
256 for (var traceName in traceData)
257 plotData.push(traceData[traceName]);
259 var plotter = new Plotter(revisionNumbers, plotData, traceNames, units,
260 document.getElementById("output"), true);
261 plotter.onclick = on_clicked_plot;
262 plotter.plot();
265 function fetch_summary() {
266 if ('graph' in params)
267 file = escape(params.graph) + ".dat"
268 else
269 file = "summary.dat"
270 Fetch(file, received_summary);
273 function fetch_graph_list() {
274 Fetch("graphs.dat", received_graph_list);
277 function initPlotSwitcher(tabs) {
278 var switcher = document.getElementById("switcher");
279 for(var i = 0; i < tabs.length; i++) {
280 var anchor = document.createElement("a");
281 anchor.appendChild(document.createTextNode(tabs[i] + " "));
282 anchor.addEventListener("click", goToClosure(tabs[i]), false);
283 switcher.appendChild(anchor);
287 function goToClosure(graph) {
288 return function(){go_to(graph)};
291 function position_details() {
292 var output = document.getElementById("output");
294 var win_height = window.innerHeight;
296 var details = document.getElementById("views");
298 var views = document.getElementById("views");
299 var selectors = document.getElementById("selectors");
300 selectors.style.display = "block";
302 var views_width = output.offsetWidth - selectors.offsetWidth;
304 views.style.border = "1px solid black";
305 views.style.width = views_width + "px";
306 views.style.height = (win_height - output.offsetHeight - output.offsetTop -
307 30) + "px";
309 selectors.style.position = "absolute";
310 selectors.style.left = (views.offsetLeft + views_width + 1) + "px";
311 selectors.style.top = views.offsetTop + "px";
313 // Change to the first detail tab
314 for (var tab in Config.detailTabs) {
315 change_view(tab);
316 break;
320 function change_view(target) {
321 for (var tab in Config.detailTabs) {
322 document.getElementById(tab).style.display =
323 (tab == target ? "block" : "none");
327 function init() {
328 // We need to fill the graph list before parsing the params or fetching the
329 // data, so we have a default graph in case none was specified.
330 fetch_graph_list();
333 window.addEventListener("load", init, false);
334 </script>
335 </head>
338 <body>
339 <div id="header_lookout" align="center">
340 <font style='color: #0066FF; font-family: Arial, serif;
341 font-size: 20pt; font-weight: bold;'>
342 <script>
343 document.write("<a target=\"_blank\" href=\"");
344 document.write(get_url());
345 document.write("\">");
346 if ('header' in params && params.header != '') {
347 document.write(escape(params.header));
348 } else {
349 document.write(Config.title);
351 document.write("</a>");
352 </script>
353 </font>
354 </div>
356 <div id="header_text">
357 <script>
358 document.write('<a href="' + Config.buildbotLink + '">SCons buildbot</a>' +
359 ' timings for the <b>' + Config.title + '</b> configuration.')
360 if ('graph' in params)
361 document.write(' Displaying values for <b>' + params.graph + '</b>.');
362 </script>
363 </div>
365 <div id="explain">
366 The vertical axis is measured values, and the horizontal
367 axis is the revision number being tested.
368 </div>
369 <p></p>
370 <div id="switcher">
372 </div>
373 <div id="output"></div>
374 <div id="details">
375 <div id="views">
376 <script>
377 for (var tab in Config.detailTabs) {
378 document.write("<iframe id=\"" + tab + "\"></iframe>");
380 </script>
381 </div>
382 <div id="selectors">
383 <script>
384 var firstTab = true;
385 for (var tab in Config.detailTabs) {
386 document.write("<div ");
387 if (firstTab) {
388 firstTab = false;
389 } else {
390 document.write("style=\"border-top: none\" ");
392 document.write("class=\"selector\" onclick=\"change_view('"
393 + tab + "')\">" + Config.detailTabs[tab] + "</div>");
395 </script>
396 </div>
397 </div>
398 <pre id="log"></pre>
399 <script>
400 if ('lookout' in params) {
401 document.getElementById("switcher").style.display = "none";
402 document.getElementById("details").style.display = "none";
403 document.getElementById("header_text").style.display = "none";
404 document.getElementById("explain").style.display = "none";
405 if ('thumbnail' in params) {
406 document.getElementById("header_lookout").style.display = "none";
408 } else {
409 document.getElementById("header_lookout").style.display = "none";
411 </script>
412 </body>
413 </html>