OLD | NEW |
| (Empty) |
1 <html> | |
2 <head> | |
3 <title>Skia Buildslave Idle Time Analysis</title> | |
4 <link rel="icon" href="favicon.ico"> | |
5 <script type="text/javascript" src="https://www.google.com/jsapi"></script> | |
6 <script type="text/javascript" src="skia_tools.js"></script> | |
7 <script language="JavaScript"> | |
8 "use strict"; | |
9 | |
10 var selectedMaster = null; | |
11 | |
12 // Configuration options for the charts | |
13 var LINE_CHART_OPTIONS = { | |
14 "title": "Buildslave Busy Times", | |
15 "width": "100%", | |
16 "height": "100%", | |
17 "chartArea": {left: "9%", top: "9%", width: "86%", | |
18 height: "70%"}, | |
19 "vAxis": {"gridlines": {"count": 0}}, | |
20 "legend": {"textStyle": {"fontSize": 12}, | |
21 "position": "bottom"}, | |
22 "lineWidth": 20, | |
23 }; | |
24 | |
25 var LINE_CHART_RANGE_FILTER_OPTIONS = { | |
26 "filterColumnIndex": 0, | |
27 "ui": { | |
28 "chartType": "LineChart", | |
29 "minRangeSize": 1, | |
30 "chartOptions": { | |
31 "chartArea": {"width": "86%", | |
32 "height": "75%", | |
33 "left": "9%", | |
34 "right": "6%"}, | |
35 "width": "100%", | |
36 "height": "100%", | |
37 "colors": ["grey"], | |
38 "hAxis": {"baselineColor": "none"}, | |
39 "vAxis": {"baselineColor": "none"}, | |
40 }, | |
41 }, | |
42 }; | |
43 | |
44 var PIE_CHART_OPTIONS = {"title": "Busy/Idle Time"}; | |
45 | |
46 // Data for all builds for all build slaves. Filled in on-demand. | |
47 var data = null; | |
48 | |
49 // Dictionary with build slave names as keys and booleans as values, | |
50 // indicating whether or not build data has been obtained for each slave. | |
51 var gotData = {}; | |
52 | |
53 // Time window for data analysis in seconds. | |
54 // TODO(borenet): Make this configurable in the UI. | |
55 var RANGE_SECS = 86400; | |
56 | |
57 // Current time in UNIX seconds from epoch as of page load. | |
58 var CURRENT_TIME = Math.ceil(new Date().getTime() / 1000); | |
59 | |
60 // Beginning of the data analysis time window. | |
61 var MIN_TIME = CURRENT_TIME - RANGE_SECS; | |
62 | |
63 // Load the Visualization API | |
64 google.load("visualization", "1.0", {"packages":["corechart", "controls"]}); | |
65 | |
66 // Set a callback to run when the Google Visualization API is loaded. | |
67 google.setOnLoadCallback(init); | |
68 | |
69 /** | |
70 * Display text or HTML in the logging div. | |
71 * @param {string} msg The HTML or text to display. | |
72 */ | |
73 function setMessage(msg) { | |
74 console.log(msg); | |
75 document.getElementById("logging_div").innerHTML = msg; | |
76 } | |
77 | |
78 /** | |
79 * Set the HTML content in the pane on the right side of the page. | |
80 * @param {string} content HTML to display. | |
81 */ | |
82 function setRightContent(content) { | |
83 document.getElementById("right_content_div").innerHTML = content; | |
84 } | |
85 | |
86 /** | |
87 * Clear the divs containing charts. Useful when charts need to be updated. | |
88 */ | |
89 function clearCharts() { | |
90 document.getElementById("line_chart_div").innerHTML = ""; | |
91 document.getElementById("line_chart_range_filter_div").innerHTML = ""; | |
92 document.getElementById("pie_chart_div").innerHTML = ""; | |
93 } | |
94 | |
95 /** | |
96 * Draw a timeline-style line chart over the given range for the given set of | |
97 * build slaves. | |
98 * @param {Array.<string>} displaySlaves List of slave names indicating which | |
99 * slaves should be included in the chart. | |
100 * @param {number} rangeMin Beginning of the data analysis time window, in | |
101 UNIX seconds from epoch. | |
102 * @param {number} rangeMax End of the data analysis time window, in UNIX | |
103 seconds from epoch. | |
104 */ | |
105 function drawLineChart(displaySlaves, rangeMin, rangeMax) { | |
106 setMessage(""); | |
107 var table = new google.visualization.DataTable(); | |
108 table.addColumn("datetime", "Time"); | |
109 var dateMin = new Date(rangeMin * 1000); | |
110 var dateMax = new Date(rangeMax * 1000); | |
111 var times = [rangeMin]; | |
112 var currentBuild = {}; | |
113 for (var i = 0; i < displaySlaves.length; i++) { | |
114 var slave = displaySlaves[i]; | |
115 table.addColumn("number", displaySlaves[i]); | |
116 table.addColumn({"type": "string", | |
117 "role": "tooltip", | |
118 "p": {"html": true}}); | |
119 var slaveData = data[slave]; | |
120 currentBuild[slave] = null; | |
121 for (var j = 0; j < slaveData["allBuilds"].length; j++) { | |
122 var build = slaveData["allBuilds"][j]; | |
123 if (build.getStartTime() > rangeMin && | |
124 build.getStartTime() < rangeMax) { | |
125 times.push(Math.round(build.getStartTime() - 1)); | |
126 times.push(Math.round(build.getStartTime() + 1)); | |
127 if (currentBuild[slave] == null) { | |
128 currentBuild[slave] = j; | |
129 } | |
130 } | |
131 if (build.getEndTime() > rangeMin && build.getEndTime() < rangeMax) { | |
132 times.push(Math.round(build.getEndTime() - 1)); | |
133 times.push(Math.round(build.getEndTime() + 1)); | |
134 if (currentBuild[slave] == null) { | |
135 currentBuild[slave] = j; | |
136 } | |
137 } | |
138 } | |
139 } | |
140 times.push(rangeMax); | |
141 times.sort(); | |
142 var rows = []; | |
143 for (var i = 0; i < times.length; i++) { | |
144 var time = times[i]; | |
145 var row = [new Date(time * 1000)]; | |
146 for (j = 0; j < displaySlaves.length; j++) { | |
147 var slave = displaySlaves[j]; | |
148 var val = null; | |
149 var tooltip = null; | |
150 if (null != currentBuild[slave]) { | |
151 var buildIdx = currentBuild[slave]; | |
152 var buildData = data[slave]["allBuilds"][buildIdx] | |
153 var min = buildData.getStartTime(); | |
154 var max = buildData.getEndTime(); | |
155 if (time >= min) { | |
156 if (time <= max) { | |
157 val = j + 1; | |
158 tooltip = slave + ": " + buildData.getBuilder() + " Build #" + | |
159 buildData.getNumber(); | |
160 } else { | |
161 if (data[slave]["allBuilds"].length > currentBuild[slave] + 1) { | |
162 currentBuild[slave]++; | |
163 } else { | |
164 currentBuild[slave] = null; | |
165 } | |
166 } | |
167 } | |
168 } | |
169 row.push(val); | |
170 row.push(tooltip); | |
171 } | |
172 rows.push(row); | |
173 } | |
174 table.addRows(rows); | |
175 | |
176 var lineChart = new google.visualization.LineChart(document.getElementById( | |
177 "line_chart_div")); | |
178 var line_chart_options = LINE_CHART_OPTIONS; | |
179 line_chart_options["vAxis"]["viewWindow"] = {"min": 0, | |
180 "max": displaySlaves.length + 1}; | |
181 line_chart_options["hAxis"] = {"viewWindow": {"min": dateMin, | |
182 "max": dateMax}}; | |
183 lineChart.draw(table, line_chart_options); | |
184 | |
185 var lineChartRangeFilter = new google.visualization.ChartRangeFilter( | |
186 document.getElementById("line_chart_range_filter_div")); | |
187 google.visualization.events.addListener(lineChartRangeFilter, | |
188 "statechange", | |
189 function() { | |
190 var range = lineChartRangeFilter.getState().range; | |
191 line_chart_options["hAxis"]["viewWindow"] = {min: range.start, | |
192 max: range.end}; | |
193 lineChart.draw(table, line_chart_options); | |
194 if (displaySlaves.length == 1) { | |
195 drawPieChart(data[displaySlaves[0]]["allBuilds"], | |
196 range.start.getTime() / 1000, | |
197 range.end.getTime() / 1000); | |
198 } | |
199 }); | |
200 var line_chart_range_filter_options = LINE_CHART_RANGE_FILTER_OPTIONS; | |
201 line_chart_range_filter_options["ui"]["chartOptions"]["hAxis"] | |
202 ["viewWindow"] = {"min": dateMin, "max": dateMax}; | |
203 lineChartRangeFilter.draw(table, line_chart_range_filter_options, | |
204 {"range": {"start": dateMin, "end": dateMax}}); | |
205 if (displaySlaves.length == 1) { | |
206 drawPieChart(data[displaySlaves[0]]["allBuilds"], rangeMin, | |
207 rangeMax); | |
208 } | |
209 setRightContent(""); | |
210 } | |
211 | |
212 /** | |
213 * Draw a pie chart to display the proportions of busy and idle time over a | |
214 * particular time period for a given slave. | |
215 * @param {Array.<object>} builds List of builds for a slave, each | |
216 * represented as a list containing: | |
217 * <ul> | |
218 * <li>builderName {string} Name of the builder. | |
219 * <li>buildNum {number} Build number. | |
220 * <li>startTime {number} Time of the start of the build, in UNIX seconds | |
221 * from epoch. | |
222 * <li>endTime {number} Time of the end of the build, in UNIX seconds from | |
223 * epoch. | |
224 * </ul> | |
225 * @param {number} rangeMin Beginning of the analysis window in UNIX seconds | |
226 * from epoch. | |
227 * @param {number} rangeMax End of the analysis window in UNIX seconds from | |
228 * epoch. | |
229 */ | |
230 function drawPieChart(builds, rangeMin, rangeMax) { | |
231 var busyTime = 0; | |
232 for (var i = 0; i < builds.length; i++) { | |
233 var build = builds[i]; | |
234 var buildStart = build.getStartTime(); | |
235 var buildEnd = build.getEndTime(); | |
236 if (buildEnd < rangeMin || buildStart > rangeMax) { | |
237 continue; | |
238 } | |
239 if (buildStart < rangeMin) { | |
240 buildStart = rangeMin; | |
241 } | |
242 if (buildEnd > rangeMax) { | |
243 buildEnd = rangeMax; | |
244 } | |
245 busyTime += buildEnd - buildStart; | |
246 } | |
247 var idleTime = rangeMax - rangeMin - busyTime; | |
248 var table = new google.visualization.arrayToDataTable([ | |
249 ["Busy/Idle", "Time"], | |
250 ["Busy", busyTime], | |
251 ["Idle", idleTime], | |
252 ]); | |
253 | |
254 // Set chart options | |
255 var pie_chart_options = PIE_CHART_OPTIONS; | |
256 | |
257 // Instantiate and draw our chart, passing in some options. | |
258 var pieChart = new google.visualization.PieChart( | |
259 document.getElementById("pie_chart_div")); | |
260 pieChart.draw(table, pie_chart_options); | |
261 } | |
262 | |
263 /** | |
264 * Callback function to use when the selected items of the listbox have | |
265 * changed. This causes data to be loaded for the selected slaves and charts | |
266 * to be created for that data. | |
267 */ | |
268 function selectSlaves() { | |
269 var displaySlaves = []; | |
270 var msg = "<p style=\"font-size:0.8em;\">Loading builds for slaves:<ul>"; | |
271 var menu = document.getElementById("slave_menu"); | |
272 for (var i = 0; i < menu.options.length; i++) { | |
273 if (menu.options[i].selected) { | |
274 var slave = menu.options[i].text; | |
275 if (displaySlaves.indexOf(slave) == -1) { | |
276 displaySlaves.push(slave); | |
277 msg += "<li style=\"font-size:0.8em;\">" + slave; | |
278 } | |
279 } | |
280 } | |
281 msg += "</ul></p>"; | |
282 clearCharts(); | |
283 setMessage(msg); | |
284 var slavesToLoad = []; | |
285 for (var i = 0; i < displaySlaves.length; i++) { | |
286 var slave = displaySlaves[i]; | |
287 if (!gotData[slave] || gotData[slave] == undefined) { | |
288 console.log("Loading builds for " + slave + "..."); | |
289 slavesToLoad.push(slave); | |
290 data[slave].loadBuilds(MIN_TIME, CURRENT_TIME, function(buildList) { | |
291 data[slave]["allBuilds"] = buildList; | |
292 gotData[slave] = true; | |
293 console.log("Loaded builds for " + slave + "."); | |
294 slavesToLoad.splice(slave, 1); | |
295 if (slavesToLoad.length == 0) { | |
296 drawLineChart(displaySlaves, MIN_TIME, CURRENT_TIME); | |
297 } | |
298 }); | |
299 } | |
300 } | |
301 } | |
302 | |
303 /** | |
304 * Callback function use when the selected build master has changed. Loads | |
305 * the buildslaves for the given master. | |
306 */ | |
307 function selectMaster() { | |
308 var masterSelect = document.getElementById("master_menu"); | |
309 selectedMaster = new skiaTools.Master(masterSelect.value); | |
310 setMessage("Loading buildslaves for " + selectedMaster.getName() + " master.
.."); | |
311 selectedMaster.loadSlaves(function(slaveData) { | |
312 var allSlaves = []; | |
313 data = {}; | |
314 for (var slaveIdx = 0; slaveIdx < slaveData.length; slaveIdx++) { | |
315 var slave = slaveData[slaveIdx]; | |
316 data[slave.getName()] = slave; | |
317 allSlaves.push(slave.getName()); | |
318 gotData[slave.getName()] = false; | |
319 } | |
320 skiaTools.populateMenu("slave_menu", allSlaves); | |
321 setMessage("Loaded buildslaves."); | |
322 setMessage("Select one or more buildslaves."); | |
323 }); | |
324 } | |
325 | |
326 /** | |
327 * Callback function to use on page load. This causes the high-level builder | |
328 * data to be loaded and the builder menu populated. | |
329 */ | |
330 function init() { | |
331 setMessage("Loading masters..."); | |
332 skiaTools.loadMasterList(function(masters) { | |
333 skiaTools.populateMenu("master_menu", masters); | |
334 document.getElementById("master_menu").selectedIndex = -1; | |
335 setMessage("Loaded masters."); | |
336 setMessage("Please select a build master."); | |
337 }); | |
338 } | |
339 </script> | |
340 </head> | |
341 <body> | |
342 <div id="heading" style="font-size:2.5em; text-align:center; height:7%;"> | |
343 Skia Buildslave Idle Time Analysis | |
344 </div> | |
345 <div id="main_content_area" style="width:100%; height:90%; padding:0px; marg
in:0px;"> | |
346 <div id="slave_menu_div" style="float:left; width:18%; height:95%; padding
:0px; margin:0px;"> | |
347 <p> | |
348 Master:<br/> | |
349 <select id="master_menu" onChange="selectMaster();"> | |
350 <option value="" noselect></option> | |
351 </select> | |
352 </p> | |
353 <p> | |
354 Buildslaves:<br/> | |
355 <select id="slave_menu" multiple="multiple" | |
356 style="width:100%; height:95%; margin:0px; padding:0px;"> | |
357 </select> | |
358 <br/> | |
359 <input type="button" onClick="selectSlaves()" | |
360 value="Update Selection"/> | |
361 </p> | |
362 </div> | |
363 <div id="charts_div" style="float:left; width:51%; padding:0px; margin:0px
"> | |
364 <div id="logging_div" style="width:100%; padding:0px; margin:0px"></div> | |
365 <div id="line_chart_div" style="width:100%; height: 40%; padding:0px; ma
rgin:0px"></div> | |
366 <div id="line_chart_range_filter_div" style="width:100%; height: 10%; pa
dding:0px; margin:0px"></div> | |
367 <div id="pie_chart_div" style="width:100%; height: 50%; padding:0px; mar
gin:0px"></div> | |
368 </div> | |
369 <div id="right_content_div" style="float:right; width:31%; height:100%"></
div> | |
370 </div> | |
371 </body> | |
372 </html> | |
OLD | NEW |