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.
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
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).
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).
35 "traces": {<trace_name1>: [<value1>, <stddev1>],
36 <trace_name2>: [<value2>, <stddev2>], ...}
42 font-family: sans-serif;
51 border-top:
1px solid black;
52 border-left:
1px solid black;
57 border:
1px solid black;
59 div.plot-coordinates {
60 font-family: monospace;
69 border: solid
1px black;
72 background-color: white;
75 background-color: rgb(
200,
200,
250);
87 color: rgb(
100,
100,
100);
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>
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';
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
) {
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.
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
)
139 initPlotSwitcher(tabs
);
141 // Fetch the data for the selected graph.
145 function go_to(graph
) {
146 params
.graph
= graph
;
147 if (params
.graph
== '')
149 window
.location
.href
= MakeURL(params
);
153 new_url
= window
.location
.href
;
154 new_url
= new_url
.replace(/\?lookout/, "?");
155 new_url
= new_url
.replace(/\&thumbnail/, "");
159 function on_clicked_plot(prev_cl
, cl
) {
160 if ('lookout' in params
) {
161 window
.open(get_url());
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);
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
) {
188 did_position_details
= true;
192 function received_summary(data
, error
) {
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) {
208 // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...}
210 for (var i
= 0; i
< max_rows
; ++i
) {
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.
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.
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
]);
252 traceData
[traceName
].push(revisionData
[traceName
]);
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
;
265 function fetch_summary() {
266 if ('graph' in params
)
267 file
= escape(params
.graph
) + ".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
-
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
) {
320 function change_view(target
) {
321 for (var tab
in Config
.detailTabs
) {
322 document
.getElementById(tab
).style
.display
=
323 (tab
== target
? "block" : "none");
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.
333 window
.addEventListener("load", init
, false);
339 <div id=
"header_lookout" align=
"center">
340 <font style='color: #
0066FF; font-family: Arial, serif;
341 font-size:
20pt; font-weight: bold;'
>
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
));
349 document
.write(Config
.title
);
351 document
.write("</a>");
356 <div id=
"header_text">
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>.');
366 The vertical axis is measured values, and the horizontal
367 axis is the revision number being tested.
373 <div id=
"output"></div>
377 for (var tab
in Config
.detailTabs
) {
378 document
.write("<iframe id=\"" + tab
+ "\"></iframe>");
385 for (var tab
in Config
.detailTabs
) {
386 document
.write("<div ");
390 document
.write("style=\"border-top: none\" ");
392 document
.write("class=\"selector\" onclick=\"change_view('"
393 + tab
+ "')\">" + Config
.detailTabs
[tab
] + "</div>");
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";
409 document
.getElementById("header_lookout").style
.display
= "none";