1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /** Random constants. */
6 var kMaxBuildStatusLength
= 50;
7 var kTicksBetweenRefreshes
= 60;
8 var kHistoricBuilds
= 4;
9 var kMaxMillisecondsToWait
= 5 * 60 * 1000;
11 var g_ticks_until_refresh
= kTicksBetweenRefreshes
;
13 /** Parsed JSON data. */
14 var g_waterfall_data
= {};
15 var g_status_data
= {};
16 var g_waterfall_data_dirty
= true;
17 var g_css_loading_rule
= null;
20 var g_requests_in_flight
= 0;
21 var g_requests_ignored
= 0;
22 var g_requests_retried
= 0;
25 /** Cut the status message down so it doesn't hog the whole screen. */
26 function truncateStatusText(text
) {
27 if (text
.length
> kMaxBuildStatusLength
) {
28 return text
.substr(0, kMaxBuildStatusLength
) + '...';
33 /** Information about a particular status page. */
34 function StatusPageInfo(status_page_url
) {
35 this.url
= status_page_url
+ 'current?format=json';
42 /** Information about a particular waterfall. */
43 function WaterfallInfo(waterfall_name
, waterfall_url
) {
44 var waterfall_link_element
= document
.createElement('a');
45 waterfall_link_element
.href
= waterfall_url
;
46 waterfall_link_element
.target
= '_blank';
47 waterfall_link_element
.innerHTML
= waterfall_name
;
49 var td_element
= document
.createElement('td');
50 td_element
.colSpan
= 15;
51 td_element
.appendChild(waterfall_link_element
);
54 this.url
= waterfall_url
;
56 this.td_element
= td_element
;
60 /** Information about a particular bot. */
61 function BotInfo(category
) {
62 // Sometimes the categories have digits at the beginning for ordering the
63 // category. Chop it off.
64 if (category
&& category
.length
> 0) {
65 var splitter_index
= category
.indexOf('|');
66 if (splitter_index
!= -1) {
67 category
= category
.substr(0, splitter_index
);
73 this.category
= category
;
74 this.build_numbers_running
= null;
75 this.is_steady_green
= false;
77 this.num_updates_offline
= 0;
80 /** Get the revisions involved in a build. */
81 function getRevisionRange(json
) {
82 var lowest
= parseInt(json
.sourceStamp
.changes
[0].revision
, 10);
83 var highest
= parseInt(json
.sourceStamp
.changes
[0].revision
, 10);
84 for (var i
= 1; i
< json
.sourceStamp
.changes
.length
; ++i
) {
85 var rev
= parseInt(json
.sourceStamp
.changes
[i
].revision
, 10);
91 return [lowest
, highest
];
94 /** Information about a particular build. */
95 function BuildInfo(json
) {
96 // Parse out the status message for the build.
98 if (json
.currentStep
) {
99 status_text
= 'running ' + json
.currentStep
.name
;
101 status_text
= json
.text
.join(' ');
104 var truncated_status_text
= truncateStatusText(status_text
);
106 // Determine what state the build is in.
108 if (status_text
.indexOf('exception') != -1) {
110 } else if (status_text
.indexOf('running') != -1) {
112 } else if (status_text
.indexOf('successful') != -1) {
114 } else if (status_text
.indexOf('failed') != -1) {
116 } else if (status_text
.indexOf('offline') != -1) {
122 if (state
== 'failed') {
123 // Save data about failed tests and blamelist so we can do intersections.
125 var revision_range
= getRevisionRange(json
);
126 var bot_name
= json
.builderName
;
127 for (var i
= 0; i
< json
.steps
.length
; ++i
) {
128 var step
= json
.steps
[i
];
129 var binary_name
= step
.name
;
130 if (step
.results
[0] != 0) { // Failed.
131 for (var j
= 0; j
< step
.logs
.length
; ++j
) {
132 var log
= step
.logs
[j
];
133 if (log
[0] == 'stdio')
135 var test_name
= log
[0];
136 failures
.push([bot_name
, binary_name
, test_name
, revision_range
]);
140 this.failures
= failures
;
142 this.failures
= null;
145 this.status_text
= status_text
;
146 this.truncated_status_text
= truncated_status_text
;
150 /** Send and parse an asynchronous request to get a repo status JSON. */
151 function requestStatusPageJSON(repo_name
) {
152 if (g_status_data
[repo_name
].in_flight
) return;
154 var request
= new XMLHttpRequest();
155 request
.open('GET', g_status_data
[repo_name
].url
, true);
156 request
.onreadystatechange = function() {
157 if (request
.readyState
== 4 && request
.status
== 200) {
158 g_status_data
[repo_name
].in_flight
--;
159 g_requests_in_flight
--;
161 status_page_json
= JSON
.parse(request
.responseText
);
162 g_status_data
[repo_name
].message
= status_page_json
.message
;
163 g_status_data
[repo_name
].state
= status_page_json
.general_state
;
164 g_status_data
[repo_name
].date
= status_page_json
.date
;
167 g_status_data
[repo_name
].in_flight
++;
168 g_requests_in_flight
++;
172 /** Send an asynchronous request to get the main waterfall's JSON. */
173 function requestWaterfallJSON(waterfall_info
) {
174 if (waterfall_info
.in_flight
) {
175 var elapsed
= new Date().getTime() - waterfall_info
.last_update
;
176 if (elapsed
< kMaxMillisecondsToWait
) return;
178 waterfall_info
.in_flight
--;
179 g_requests_in_flight
--;
180 g_requests_retried
++;
183 var waterfall_url
= waterfall_info
.url
;
184 var url
= waterfall_url
+ 'json/builders/';
186 var request
= new XMLHttpRequest();
187 request
.open('GET', url
, true);
188 request
.onreadystatechange = function() {
189 if (request
.readyState
== 4 && request
.status
== 200) {
190 waterfall_info
.in_flight
--;
191 g_requests_in_flight
--;
192 parseWaterfallJSON(JSON
.parse(request
.responseText
), waterfall_info
);
195 waterfall_info
.in_flight
++;
196 g_requests_in_flight
++;
197 waterfall_info
.last_update
= new Date().getTime();
201 /** Update info about the bot, including info about the builder's builds. */
202 function requestBuilderJSON(waterfall_info
,
206 // Prepare the bot info.
207 if (!waterfall_info
.bot_info
[builder_name
]) {
208 waterfall_info
.bot_info
[builder_name
] = new BotInfo(builder_json
.category
);
210 var bot_info
= waterfall_info
.bot_info
[builder_name
];
212 // Update the builder's state.
213 bot_info
.build_numbers_running
= builder_json
.currentBuilds
;
214 bot_info
.num_pending_builds
= builder_json
.pendingBuilds
;
215 bot_info
.state
= builder_json
.state
;
216 if (bot_info
.state
== 'offline') {
217 bot_info
.num_updates_offline
++;
218 console
.log(builder_name
+ ' has been offline for ' +
219 bot_info
.num_updates_offline
+ ' update(s) in a row');
221 bot_info
.num_updates_offline
= 0;
224 // Send an asynchronous request to get info about the builder's last builds.
225 var last_completed_build_number
=
226 guessLastCompletedBuildNumber(builder_json
, bot_info
);
227 if (last_completed_build_number
) {
228 for (var build_number
= last_completed_build_number
- kHistoricBuilds
;
229 build_number
<= last_completed_build_number
;
231 if (build_number
< 0) continue;
232 requestBuildJSON(waterfall_info
,
241 /** Given a builder's JSON, guess the last known build that it completely
243 function guessLastCompletedBuildNumber(builder_json
, bot_info
) {
244 // The cached builds line doesn't store every build so we can't just take the
246 var build_numbers_running
= builder_json
.currentBuilds
;
247 bot_info
.build_numbers_running
= build_numbers_running
;
249 var build_numbers_cached
= builder_json
.cachedBuilds
;
250 if (build_numbers_running
&& build_numbers_running
.length
> 0) {
252 Math
.max(build_numbers_cached
[build_numbers_cached
.length
- 1],
253 build_numbers_running
[build_numbers_running
.length
- 1]);
255 var completed_build_number
= max_build_number
;
256 while (build_numbers_running
.indexOf(completed_build_number
) != -1 &&
257 completed_build_number
>= 0) {
258 completed_build_number
--;
260 return completed_build_number
;
262 // Nothing's currently building. Just assume the last cached build is
264 return build_numbers_cached
[build_numbers_cached
.length
- 1];
268 /** Get the data for a particular build. */
269 function requestBuildJSON(waterfall_info
,
274 var bot_info
= waterfall_info
.bot_info
[builder_name
];
276 // Check if we already have the data on this build.
277 if (bot_info
.builds
[build_number
] &&
278 bot_info
.builds
[build_number
].state
!= 'running') {
279 g_requests_ignored
++;
285 root_json_url
+ 'builders/' + builder_name
+ '/builds/' + build_number
;
287 var request
= new XMLHttpRequest();
288 request
.open('GET', url
, true);
289 request
.onreadystatechange = function() {
290 if (request
.readyState
== 4 && request
.status
== 200) {
291 bot_info
.in_flight
--;
292 g_requests_in_flight
--;
294 var json
= JSON
.parse(request
.responseText
);
295 bot_info
.builds
[build_number
] = new BuildInfo(json
);
297 checkBotIsSteadyGreen(builder_name
, bot_info
);
298 g_waterfall_data_dirty
= true;
302 bot_info
.in_flight
++;
303 g_requests_in_flight
++;
307 function parseWaterfallJSON(builders_json
, waterfall_info
) {
308 var root_json_url
= waterfall_info
.url
+ 'json/';
310 // Go through each builder on the waterfall and get the latest status.
311 var builder_names
= Object
.keys(builders_json
);
312 for (var i
= 0; i
< builder_names
.length
; ++i
) {
313 var builder_name
= builder_names
[i
];
315 // TODO: Any filtering here.
317 var builder_json
= builders_json
[builder_name
];
319 waterfall_info
, root_json_url
, builder_json
, builder_name
);
320 g_waterfall_data_dirty
= true;
324 /** Queries all of the servers for their latest statuses. */
325 function queryServersForInfo() {
326 var waterfall_names
= Object
.keys(g_waterfall_data
);
327 for (var index
= 0; index
< waterfall_names
.length
; ++index
) {
328 var name
= waterfall_names
[index
];
329 var waterfall_info
= g_waterfall_data
[name
];
330 requestWaterfallJSON(waterfall_info
);
333 var status_page_names
= Object
.keys(g_status_data
);
334 for (var index
= 0; index
< status_page_names
.length
; ++index
) {
335 requestStatusPageJSON(status_page_names
[index
]);
340 * Return the range of intersection between the two lists. Ranges are sets of
341 * CLs, so it's inclusive on both ends.
343 function rangeIntersection(a
, b
) {
348 // We know they intersect, result is the larger lower bound to the smaller
350 return [Math
.max(a
[0], b
[0]), Math
.min(a
[1], b
[1])];
353 function findIntersections(test_data
) {
354 // Input is a object mapping botname => [low, high].
358 // [ low0, high0 ], [ bot1, bot2, bot3 ],
359 // [ low1, high1 ], [ bot4, ... ],
363 var keys
= Object
.keys(test_data
);
364 var intersections
= [];
365 var bot_name
= keys
[0];
366 var range
= test_data
[bot_name
];
367 intersections
.push([range
, [bot_name
]]);
368 for (var i
= 1; i
< keys
.length
; ++i
) {
370 range
= test_data
[bot_name
];
371 var intersected_some
= false;
372 for (var j
= 0; j
< intersections
.length
; ++j
) {
373 var intersect
= rangeIntersection(intersections
[j
][0], range
);
375 intersections
[j
][0] = intersect
;
376 intersections
[j
][1].push(bot_name
);
377 intersected_some
= true;
381 if (!intersected_some
) {
382 intersections
.push([range
, [bot_name
]]);
386 return intersections
;
389 /** Flatten out the list of tests and sort them by the binary/test name. */
390 function flattenAndSortTests(ranges_by_test) {
391 var ranges_by_test_names = Object.keys(ranges_by_test);
393 for (var i = 0; i < ranges_by_test_names.length; ++i) {
394 var name = ranges_by_test_names[i];
395 flat.push([name, ranges_by_test[name]]);
398 flat.sort(function(a, b) {
399 if (a[0] < b[0]) return -1;
400 if (a[0] > b[0]) return 1;
408 * Build the HTML table row for a test failure. |test| is [ name, test_data ].
409 * |test_data| contains ranges suitable for input to |findIntersections|.
411 function buildTestFailureTableRowHTML(test
) {
412 var row
= document
.createElement('tr');
413 var binary_cell
= row
.insertCell(-1);
414 var name_parts
= test
[0].split('-');
415 binary_cell
.innerHTML
= name_parts
[0];
416 binary_cell
.className
= 'category';
417 var flakiness_link
= document
.createElement('a');
418 flakiness_link
.href
=
419 'http://test-results.appspot.com/dashboards/' +
420 'flakiness_dashboard.html#testType=' +
421 name_parts
[0] + '&tests=' + name_parts
[1];
422 flakiness_link
.target
= '_blank';
423 flakiness_link
.innerHTML
= name_parts
[1];
424 row
.appendChild(flakiness_link
);
426 var intersections
= findIntersections(test
[1]);
427 for (var j
= 0; j
< intersections
.length
; ++j
) {
428 var intersection
= intersections
[j
];
429 var range
= row
.insertCell(-1);
430 range
.className
= 'failure_range';
431 var low
= intersection
[0][0];
432 var high
= intersection
[0][1];
434 'http://build.chromium.org/f/chromium/perf/dashboard/ui/' +
435 'changelog.html?url=%2Ftrunk%2Fsrc&range=' +
436 low
+ '%3A' + high
+ '&mode=html';
437 range
.innerHTML
= '<a target="_blank" href="' + url
+ '">' + low
+
438 ' - ' + high
+ '</a>: ' +
439 truncateStatusText(intersection
[1].join(', '));
445 /** Updates the correlations contents. */
446 function updateCorrelationsHTML() {
447 // The logic here is to try to narrow blamelists by using information across
448 // bots. If a particular test has failed on multiple different
449 // configurations, there's a good chance that it has the same root cause, so
450 // calculate the intersection of blamelists of the first time it failed on
453 var all_failures
= [];
454 for (var i
= 0; i
< kBuilderPages
.length
; ++i
) {
455 var waterfall_name
= kBuilderPages
[i
][0];
456 var waterfall_info
= g_waterfall_data
[waterfall_name
];
457 var bot_info
= waterfall_info
.bot_info
;
458 var all_bot_names
= Object
.keys(bot_info
);
459 for (var j
= 0; j
< all_bot_names
.length
; ++j
) {
460 var bot_name
= all_bot_names
[j
];
461 if (bot_info
[bot_name
].is_steady_green
)
463 var builds
= bot_info
[bot_name
].builds
;
464 var build_names
= Object
.keys(builds
);
465 for (var k
= 0; k
< build_names
.length
; ++k
) {
466 var build
= builds
[build_names
[k
]];
467 if (build
.failures
) {
468 for (var l
= 0; l
< build
.failures
.length
; ++l
) {
469 all_failures
.push(build
.failures
[l
]);
476 // all_failures is now a list of lists, each containing:
477 // [ botname, binaryname, testname, [low_rev, high_rev] ].
479 var ranges_by_test
= {};
480 for (var i
= 0; i
< all_failures
.length
; ++i
) {
481 var failure
= all_failures
[i
];
482 var bot_name
= failure
[0];
483 var binary_name
= failure
[1];
484 var test_name
= failure
[2];
485 var range
= failure
[3];
486 if (binary_name
.indexOf('steps') != -1)
488 if (test_name
.indexOf('preamble') != -1)
490 var key
= binary_name
+ '-' + test_name
;
492 if (!ranges_by_test
.hasOwnProperty(key
))
493 ranges_by_test
[key
] = {};
494 // If there's no range, that's all we know.
495 if (!ranges_by_test
[key
].hasOwnProperty([bot_name
])) {
496 ranges_by_test
[key
][bot_name
] = range
;
498 // Otherwise, track only the lowest range for this bot (we only want
499 // when it turned red, not the whole range that it's been red for).
500 if (range
[0] < ranges_by_test
[key
][bot_name
][0]) {
501 ranges_by_test
[key
][bot_name
] = range
;
506 var table
= document
.getElementById('failure_info');
507 while (table
.rows
.length
> 0) {
511 var header_cell
= document
.createElement('td');
512 header_cell
.colSpan
= 15;
513 header_cell
.innerHTML
=
514 'test failures (ranges are blamelist intersections of first failure on ' +
515 'each bot of retrieved data. so, if a test has been failing for a ' +
516 'while, the range may be incorrect)';
517 header_cell
.className
= 'waterfall_name';
518 var header_row
= table
.insertRow(-1);
519 header_row
.appendChild(header_cell
);
521 var flat
= flattenAndSortTests(ranges_by_test
);
523 for (var i
= 0; i
< flat
.length
; ++i
) {
524 table
.appendChild(buildTestFailureTableRowHTML(flat
[i
]));
528 /** Updates the sidebar's contents. */
529 function updateSidebarHTML() {
533 var status_names
= Object
.keys(g_status_data
);
534 for (var i
= 0; i
< status_names
.length
; ++i
) {
535 var status_name
= status_names
[i
];
536 var status_info
= g_status_data
[status_name
];
537 var status_url
= kStatusPages
[i
][1];
539 output
+= '<ul class="box ' + status_info
.state
+ '">';
540 output
+= '<li><a target="_blank" href="' + status_url
+ '">' +
541 status_name
+ '</a></li>';
542 output
+= '<li>' + status_info
.date
+ '</li>';
543 output
+= '<li>' + status_info
.message
+ '</li>';
547 var div
= document
.getElementById('sidebar_contents');
548 div
.innerHTML
= output
;
551 document
.getElementById('ticks_until_refresh').innerHTML
=
552 g_ticks_until_refresh
;
553 document
.getElementById('requests_in_flight').innerHTML
=
554 g_requests_in_flight
;
555 document
.getElementById('requests_ignored').innerHTML
= g_requests_ignored
;
556 document
.getElementById('requests_retried').innerHTML
= g_requests_retried
;
559 /** Organizes all of the bots by category, then alphabetically within their
561 function sortBotNamesByCategory(bot_info
) {
562 // Bucket all of the bots according to their category.
563 var all_bot_names
= Object
.keys(bot_info
);
564 var bucketed_names
= {};
565 for (var i
= 0; i
< all_bot_names
.length
; ++i
) {
566 var bot_name
= all_bot_names
[i
];
567 var category
= bot_info
[bot_name
].category
;
569 if (!bucketed_names
[category
]) bucketed_names
[category
] = [];
570 bucketed_names
[category
].push(bot_name
);
573 // Alphabetically sort bots within their buckets, then append them to the
575 var sorted_bot_names
= [];
576 var all_categories
= Object
.keys(bucketed_names
);
577 all_categories
.sort();
578 for (var i
= 0; i
< all_categories
.length
; ++i
) {
579 var category
= all_categories
[i
];
580 var bucket_bots
= bucketed_names
[category
];
583 for (var j
= 0; j
< bucket_bots
.length
; ++j
) {
584 sorted_bot_names
.push(bucket_bots
[j
]);
588 return sorted_bot_names
;
592 * Returns true IFF the last few builds are all green.
593 * Also alerts the user if the last completed build goes red after being
594 * steadily green (if desired).
596 function checkBotIsSteadyGreen(bot_name
, bot_info
) {
597 var ascending_build_numbers
= Object
.keys(bot_info
.builds
);
598 ascending_build_numbers
.sort();
600 for (var j
= ascending_build_numbers
.length
- 1;
601 j
>= 0 && j
>= ascending_build_numbers
.length
- 1 - kHistoricBuilds
;
603 var build_number
= ascending_build_numbers
[j
];
604 if (!build_number
) continue;
606 var build_info
= bot_info
.builds
[build_number
];
607 if (!build_info
) continue;
609 // Running builds throw heuristics out of whack. Keep the bot visible.
610 if (build_info
.state
== 'running') return false;
612 if (build_info
.state
!= 'success') {
613 if (bot_info
.is_steady_green
&&
614 document
.getElementById('checkbox_alert_steady_red').checked
) {
616 ' has failed for the first time in a while. Consider looking.');
618 bot_info
.is_steady_green
= false;
623 bot_info
.is_steady_green
= true;
627 /** Update all the waterfall data. */
628 function updateStatusHTML() {
629 var table
= document
.getElementById('build_info');
630 while (table
.rows
.length
> 0) {
634 for (var i
= 0; i
< kBuilderPages
.length
; ++i
) {
635 var waterfall_name
= kBuilderPages
[i
][0];
636 var waterfall_info
= g_waterfall_data
[waterfall_name
];
637 updateWaterfallStatusHTML(waterfall_name
, waterfall_info
);
641 /** Marks the waterfall data as dirty due to updated filter. */
642 function filterUpdated() {
643 g_waterfall_data_dirty
= true;
646 /** Creates a table cell for a particular build number. */
647 function createBuildNumberCellHTML(build_url
,
650 truncated_status_text
,
652 // Create a link to the build results.
653 var link_element
= document
.createElement('a');
654 link_element
.href
= build_url
;
655 link_element
.target
= '_blank';
657 // Display either the build number (for the last completed build), or show the
658 // status of the step.
659 var build_identifier_element
= document
.createElement('span');
661 build_identifier_element
.className
= 'build_identifier';
662 build_identifier_element
.innerHTML
= build_number
;
664 build_identifier_element
.className
= 'build_letter';
665 build_identifier_element
.innerHTML
= build_state
.toUpperCase()[0];
667 link_element
.appendChild(build_identifier_element
);
669 // Show the status of the build, in truncated form so it doesn't take up the
671 if (truncated_status_text
) {
672 var status_span_element
= document
.createElement('span');
673 status_span_element
.className
= 'build_status';
674 status_span_element
.innerHTML
= truncated_status_text
;
675 link_element
.appendChild(status_span_element
);
678 // Tack the cell onto the end of the row.
679 var build_number_cell
= document
.createElement('td');
680 build_number_cell
.className
= build_state
;
681 build_number_cell
.title
= full_status_text
;
682 build_number_cell
.appendChild(link_element
);
684 return build_number_cell
;
687 /** Updates the HTML for a particular waterfall. */
688 function updateWaterfallStatusHTML(waterfall_name
, waterfall_info
) {
689 var table
= document
.getElementById('build_info');
691 // Point at the waterfall.
692 var header_cell
= waterfall_info
.td_element
;
693 header_cell
.className
=
694 'waterfall_name' + (waterfall_info
.in_flight
> 0 ? ' in_flight' : '');
695 var header_row
= table
.insertRow(-1);
696 header_row
.appendChild(header_cell
);
698 // Print out useful bits about the bots.
699 var bot_names
= sortBotNamesByCategory(waterfall_info
.bot_info
);
700 for (var i
= 0; i
< bot_names
.length
; ++i
) {
701 var bot_name
= bot_names
[i
];
702 var bot_info
= waterfall_info
.bot_info
[bot_name
];
703 var bot_row
= document
.createElement('tr');
705 // Insert a cell for the bot category. Chop off any numbers used for
707 var category_cell
= bot_row
.insertCell(-1);
708 var category
= bot_info
.category
;
709 if (category
&& category
.length
> 0 && category
[0] >= '0' &&
710 category
[0] <= '9') {
711 category
= category
.substr(1, category
.length
);
713 category_cell
.innerHTML
= category
;
714 category_cell
.className
= 'category';
716 // Insert a cell for the bot name.
717 var bot_cell
= bot_row
.insertCell(-1);
718 var builder_url
= waterfall_info
.url
+ 'builders/' + bot_name
;
720 '<a target="_blank" href="' + builder_url
+ '">' + bot_name
+ '</a>';
721 bot_cell
.innerHTML
= bot_link
;
723 'bot_name' + (bot_info
.in_flight
> 0 ? ' in_flight' : '');
725 // Display information on the builds we have.
726 // This assumes that the build number always increases, but this is a bad
728 // builds get parallelized.
729 var build_numbers
= Object
.keys(bot_info
.builds
);
730 build_numbers
.sort();
732 if (bot_info
.num_pending_builds
) {
733 var pending_cell
= document
.createElement('span');
734 pending_cell
.className
= 'pending_count';
735 pending_cell
.innerHTML
= '+' + bot_info
.num_pending_builds
;
736 bot_row
.appendChild(pending_cell
);
738 bot_row
.insertCell(-1);
741 if (bot_info
.build_numbers_running
&&
742 bot_info
.build_numbers_running
.length
> 0) {
743 // Display the number of the highest numbered running build.
744 bot_info
.build_numbers_running
.sort();
745 var length
= bot_info
.build_numbers_running
.length
;
746 var build_number
= bot_info
.build_numbers_running
[length
- 1];
747 var build_url
= builder_url
+ '/builds/' + build_number
;
748 var build_number_cell
= createBuildNumberCellHTML(
749 build_url
, build_number
, null, null, 'running');
750 bot_row
.appendChild(build_number_cell
);
751 } else if (bot_info
.state
== 'offline' &&
752 bot_info
.num_updates_offline
>= 3) {
753 // The bot's supposedly offline. Wait a few updates to see since a
754 // builder can be marked offline in between builds and during reboots.
755 var build_number_cell
= document
.createElement('td');
756 build_number_cell
.className
= bot_info
.state
+ ' build_identifier';
757 build_number_cell
.innerHTML
= 'offline';
758 bot_row
.appendChild(build_number_cell
);
760 bot_row
.insertCell(-1);
763 // Display the last few builds.
764 for (var j
= build_numbers
.length
- 1;
765 j
>= 0 && j
>= build_numbers
.length
- 1 - kHistoricBuilds
;
767 var build_number
= build_numbers
[j
];
768 if (!build_number
) continue;
770 var build_info
= bot_info
.builds
[build_number
];
771 if (!build_info
) continue;
773 // Create and append the cell.
774 var is_last_build
= (j
== build_numbers
.length
- 1);
775 var build_url
= builder_url
+ '/builds/' + build_number
;
776 var status_text_full
=
777 'Build ' + build_number
+ ':\n' + build_info
.status_text
;
778 var build_number_cell
= createBuildNumberCellHTML(
780 is_last_build
? build_number
: null,
782 is_last_build
? build_info
.truncated_status_text
: null,
784 bot_row
.appendChild(build_number_cell
);
787 // Determine whether we should apply keyword filter.
788 var filter
= document
.getElementById('text_filter').value
.trim();
789 if (filter
.length
> 0) {
790 var keywords
= filter
.split(' ');
791 var build_numbers
= Object
.keys(bot_info
.builds
);
792 var matches_filter
= false;
793 console
.log(bot_info
);
795 for (var x
= 0; x
< build_numbers
.length
&& !matches_filter
; ++x
) {
796 var build_status
= bot_info
.builds
[build_numbers
[x
]].status_text
;
797 console
.log(build_status
);
799 for (var y
= 0; y
< keywords
.length
&& !matches_filter
; ++y
) {
800 if (build_status
.indexOf(keywords
[y
]) >= 0)
801 matches_filter
= true;
809 // If the user doesn't want to see completely green bots, hide it.
810 var should_hide_stable
=
811 document
.getElementById('checkbox_hide_stable').checked
;
812 if (should_hide_stable
&& bot_info
.is_steady_green
)
815 table
.appendChild(bot_row
);
819 /** Update the page content. */
820 function updateContent() {
821 if (--g_ticks_until_refresh
<= 0) {
822 g_ticks_until_refresh
= kTicksBetweenRefreshes
;
823 queryServersForInfo();
826 // Redraw the page content.
827 if (g_waterfall_data_dirty
) {
828 g_waterfall_data_dirty
= false;
830 updateCorrelationsHTML();
835 /** Initialize all the things. */
836 function initialize() {
837 var g_start_time
= new Date().getTime();
839 // Initialize the waterfall pages.
840 for (var i
= 0; i
< kBuilderPages
.length
; ++i
) {
841 g_waterfall_data
[kBuilderPages
[i
][0]] =
842 new WaterfallInfo(kBuilderPages
[i
][0], kBuilderPages
[i
][1]);
845 // Initialize the status pages.
846 for (var i
= 0; i
< kStatusPages
.length
; ++i
) {
847 g_status_data
[kStatusPages
[i
][0]] = new StatusPageInfo(kStatusPages
[i
][1]);
850 // Set up the useful links HTML in the sidebar.
851 var useful_links_ul
= document
.getElementById('useful_links');
852 for (var i
= 0; i
< kLinks
.length
; ++i
) {
853 var link_html
= '<a target="_blank" href="' + kLinks
[i
][1] + '">' +
854 kLinks
[i
][0] + '</a>';
855 var li_element
= document
.createElement('li');
856 li_element
.innerHTML
= link_html
;
857 useful_links_ul
.appendChild(li_element
);
860 // Kick off the main loops.
861 queryServersForInfo();
863 setInterval('updateContent()', 1000);