// Copyright 2014 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. /** * Return the range of intersection between the two lists. Ranges are sets of * CLs, so it's inclusive on both ends. */ function rangeIntersection(a, b) { if (a[0] > b[1]) return null; if (a[1] < b[0]) return null; // We know they intersect, result is the larger lower bound to the smaller // upper bound. return [Math.max(a[0], b[0]), Math.min(a[1], b[1])]; } /** * Finds the intersections between the blamelists. * Input is a object mapping botname => [low, high]. * * Result: * [ * [ low0, high0 ], [ bot1, bot2, bot3 ], * [ low1, high1 ], [ bot4, ... ], * ..., * ] */ function findIntersections(testData) { var keys = Object.keys(testData); var intersections = []; var botName = keys[0]; var range = testData[botName]; intersections.push([range, [botName]]); for (var i = 1; i < keys.length; ++i) { botName = keys[i]; range = testData[botName]; var intersectedSome = false; for (var j = 0; j < intersections.length; ++j) { var intersect = rangeIntersection(intersections[j][0], range); if (intersect) { intersections[j][0] = intersect; intersections[j][1].push(botName); intersectedSome = true; break; } } if (!intersectedSome) { intersections.push([range, [botName]]); } } return intersections; } /** Flatten out the list of tests and sort them by the binary/test name. */ function flattenAndSortTests(rangesByTest) { var rangesByTestNames = Object.keys(rangesByTest); var flat = []; for (var i = 0; i < rangesByTestNames.length; ++i) { var name = rangesByTestNames[i]; flat.push([name, rangesByTest[name]]); } flat.sort(function(a, b) { if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0; }); return flat; } /** * Build the HTML table row for a test failure. |test| is [ name, testData ]. * |testData| contains ranges suitable for input to |findIntersections|. */ function buildTestFailureTableRowHTML(test) { var row = document.createElement('tr'); var binaryCell = row.insertCell(-1); var nameParts = test[0].split('-'); binaryCell.innerHTML = nameParts[0]; binaryCell.className = 'category'; var flakinessLink = document.createElement('a'); flakinessLink.href = 'http://test-results.appspot.com/dashboards/' + 'flakiness_dashboard.html#testType=' + nameParts[0] + '&tests=' + nameParts[1]; flakinessLink.innerHTML = nameParts[1]; row.appendChild(flakinessLink); var intersections = findIntersections(test[1]); for (var j = 0; j < intersections.length; ++j) { var intersection = intersections[j]; var range = row.insertCell(-1); range.className = 'failure-range'; var low = intersection[0][0]; var high = intersection[0][1]; var url = 'http://build.chromium.org/f/chromium/perf/dashboard/ui/' + 'changelog.html?url=%2Ftrunk%2Fsrc&range=' + low + '%3A' + high + '&mode=html'; range.innerHTML = '' + low + ' - ' + high + ': ' + truncateStatusText(intersection[1].join(', ')); } return row; } /** Updates the correlations contents. */ function updateCorrelationsHTML() { // The logic here is to try to narrow blamelists by using information across // bots. If a particular test has failed on multiple different // configurations, there's a good chance that it has the same root cause, so // calculate the intersection of blamelists of the first time it failed on // each builder. var allFailures = []; for (var i = 0; i < gWaterfallData.length; ++i) { var waterfallInfo = gWaterfallData[i]; var botInfo = waterfallInfo.botInfo; var allBotNames = Object.keys(botInfo); for (var j = 0; j < allBotNames.length; ++j) { var botName = allBotNames[j]; if (botInfo[botName].isSteadyGreen) continue; var builds = botInfo[botName].builds; var buildNames = Object.keys(builds); for (var k = 0; k < buildNames.length; ++k) { var build = builds[buildNames[k]]; if (build.failures) { for (var l = 0; l < build.failures.length; ++l) { allFailures.push(build.failures[l]); } } } } } // allFailures is now a list of lists, each containing: // [ botname, binaryname, testname, [low_rev, high_rev] ]. var rangesByTest = {}; for (var i = 0; i < allFailures.length; ++i) { var failure = allFailures[i]; var botName = failure[0]; var binaryName = failure[1]; var testName = failure[2]; var range = failure[3]; if (binaryName.indexOf('steps') != -1) continue; if (testName.indexOf('preamble') != -1) continue; var key = binaryName + '-' + testName; if (!rangesByTest.hasOwnProperty(key)) rangesByTest[key] = {}; // If there's no range, that's all we know. if (!rangesByTest[key].hasOwnProperty([botName])) { rangesByTest[key][botName] = range; } else { // Otherwise, track only the lowest range for this bot (we only want // when it turned red, not the whole range that it's been red for). if (range[0] < rangesByTest[key][botName][0]) { rangesByTest[key][botName] = range; } } } var table = document.getElementById('failure-info'); while (table.rows.length > 0) { table.deleteRow(-1); } var headerCell = document.createElement('td'); headerCell.colSpan = 15; headerCell.innerHTML = 'test failures (ranges are blamelist intersections of first failure on ' + 'each bot of retrieved data. so, if a test has been failing for a ' + 'while, the range may be incorrect)'; headerCell.className = 'section-header'; var headerRow = table.insertRow(-1); headerRow.appendChild(headerCell); var flat = flattenAndSortTests(rangesByTest); for (var i = 0; i < flat.length; ++i) { table.appendChild(buildTestFailureTableRowHTML(flat[i])); } }