1 <!-- 2 Copyright 2014 The Chromium Authors. All rights reserved. 3 Use of this source code is governed by a BSD-style license that can be 4 found in the LICENSE file. 5 --> 6 7 <link rel="import" href="../lib/net.html"> 8 <link rel="import" href="../lib/update-util.html"> 9 <link rel="import" href="ct-builder-failure.html"> 10 <link rel="import" href="ct-step-failure.html"> 11 <link rel="import" href="ct-failure-group.html"> 12 <link rel="import" href="ct-builder-failure-group-data.html"> 13 <link rel="import" href="ct-step-failure-group-data.html"> 14 <link rel="import" href="ct-trooper-failure-group-data.html"> 15 <link rel="import" href="ct-commit-list.html"> 16 17 <script> 18 function CTFailures(commitLog) { 19 this.commitLog = commitLog; 20 // Maps a tree id to an array of CTFailureGroups within that tree. 21 this.failures = null; 22 this.lastUpdateDate = null; 23 } 24 25 (function() { 26 'use strict'; 27 28 // FIXME: This could potentially move onto CTStepFailureGroupData as it isn't relevant to 29 // trooper failures. 30 // Reverse sorting order, if a > b, return a negative number. 31 CTFailures.prototype._failureByTreeListComparator = function(tree, a, b) { 32 if (tree === undefined) 33 tree = 'chromium'; 34 35 var rev_a = a.data.commitList.revisions; 36 var rev_b = b.data.commitList.revisions; 37 38 if (!rev_a || !Object.keys(rev_a).length) { 39 if (!rev_b || !Object.keys(rev_b).length) 40 return 0; 41 return 1; 42 } else if (!rev_b || !Object.keys(rev_b).length) { 43 return -1; 44 } 45 46 // Prioritize the tree's revision, if they are unequal (else, fallback below) 47 if (rev_a[tree] && rev_b[tree] && 48 rev_a[tree].last() != rev_b[tree].last()) { 49 return rev_b[tree].last() - rev_a[tree].last(); 50 } 51 52 // Compare other revisions in alphabetical order. 53 var keys = Object.keys(rev_a).sort(); 54 for (var i = 0; i < keys.length; i++) { 55 if (keys[i] == tree) // Already taken care of, above. 56 continue; 57 58 var a_list = rev_a[keys[i]]; 59 var b_list = rev_b[keys[i]]; 60 if (!b_list) 61 return -1; 62 63 if (a_list.last() != b_list.last()) 64 return b_list.last() - a_list.last(); 65 } 66 return 0; 67 }; 68 69 // Updates the format of the alerts array to be easier to parse. 70 CTFailures.prototype._mungeAlerts = function(alerts) { 71 alerts.forEach(function(failure) { 72 // FIXME: Have the server store the actual failure type in a different 73 // field instead of smashing it into the reason. 74 if (failure.failureType) { 75 // The server has been fixed. 76 } else { 77 if (failure.reason) { 78 var parts = failure.reason.split(':'); 79 failure.reason = parts[0]; 80 failure.failureType = parts[1] || 'FAIL'; 81 } else { 82 failure.failureType = 'UNKNOWN'; 83 } 84 } 85 }); 86 }; 87 88 CTFailures.prototype.update = function() { 89 var annotationPromise = CTFailureGroup.fetchAnnotations(); 90 return Promise.all([annotationPromise, net.json('https://sheriff-o-matic.appspot.com/alerts'), 91 net.json('https://trooper-o-matic.appspot.com/alerts')]).then(function(data_array) { 92 var annotations = data_array[0]; 93 var sheriff_data = data_array[1]; 94 var trooper_data = data_array[2]; 95 96 var newFailures = {}; 97 this.lastUpdateDate = new Date(sheriff_data.date * 1000); 98 this._mungeAlerts(sheriff_data.alerts); 99 // FIXME: Change builder_alerts to expose the alerts as a map instead of an array. 100 var alertsByKey = {} 101 sheriff_data.alerts.forEach(function(alert) { 102 alertsByKey[alert.key] = alert; 103 }); 104 // Update |failures| with the appropriate CTFailureGroup's for each 105 // tree. 106 sheriff_data.range_groups.forEach(function(rangeGroup) { 107 this._processFailuresForRangeGroup(newFailures, rangeGroup, alertsByKey, annotations); 108 }.bind(this)); 109 110 // Sort failure groups so that newer failures are shown at the top 111 // of the UI. 112 Object.keys(newFailures, function (tree, failuresByTree) { 113 failuresByTree.sort(this._failureByTreeListComparator.bind(this, tree)); 114 }.bind(this)); 115 116 sheriff_data.stale_builder_alerts.forEach(function(alert) { 117 this._processBuilderFailure(newFailures, alert); 118 }.bind(this)); 119 120 this.failures = updateUtil.updateLeft(this.failures, newFailures); 121 this._processTrooperFailures(trooper_data); 122 }.bind(this)); 123 }; 124 125 CTFailures.prototype._failureComparator = function(a, b) { 126 if (a.step > b.step) 127 return 1; 128 if (a.step < b.step) 129 return -1 130 if (a.testName > b.testName) 131 return 1; 132 if (a.testName < b.testName) 133 return -1 134 return 0; 135 }; 136 137 CTFailures.prototype._processTrooperFailures = function(data) { 138 var trooper_failures = []; 139 Object.keys(data, function(failureType, failuresByTree) { 140 Object.keys(failuresByTree, function(tree, failure) { 141 if (failure.should_alert) { 142 trooper_failures.push(new CTFailureGroup('', 143 new CTTrooperFailureGroupData(failure.details, failure.url, failure, failureType, tree))); 144 } 145 }); 146 }); 147 this.failures['trooper'] = trooper_failures; 148 }; 149 150 CTFailures.prototype._groupFailuresByTreeAndReason = function(failures, annotations) { 151 var failuresByTree = {}; 152 failures.forEach(function(failure) { 153 // Establish the key to uniquely identify a failure by reason. 154 var failureKey = CTStepFailure.createKey(failure); 155 156 var reasonKey = JSON.stringify({ 157 step: failure.step_name, 158 reason: failure.reason, 159 }); 160 161 // FIXME: Use a model class instead of a dumb object. 162 if (!failuresByTree[failure.tree]) 163 failuresByTree[failure.tree] = {}; 164 if (!failuresByTree[failure.tree][reasonKey]) 165 failuresByTree[failure.tree][reasonKey] = {}; 166 failuresByTree[failure.tree][reasonKey][failure.builder_name] = { 167 key: failureKey, 168 // FIXME: Rename to failureType. 169 actual: failure.failureType, 170 lastFailingBuild: failure.last_failing_build, 171 earliestFailingBuild: failure.failing_build, 172 masterUrl: failure.master_url, 173 failingBuildCount: (1 + failure.last_failing_build - failure.failing_build), 174 annotation: annotations[failureKey], 175 }; 176 }); 177 return failuresByTree 178 }; 179 180 CTFailures.prototype._processFailuresForRangeGroup = function(newFailures, rangeGroup, alerts, annotations) { 181 // A rangeGroup may be related to multiple alerts (via |failure_keys|). Categorize 182 // these failures by reason (cause of failure), so that they can be grouped in the UI 183 // (via a CTFailureGroup). 184 var failures = rangeGroup.failure_keys.map(function(failure_key) { 185 return alerts[failure_key]; 186 }); 187 var failuresByTree = this._groupFailuresByTreeAndReason(failures, annotations); 188 189 if (Object.isEmpty(failuresByTree)) 190 return; 191 192 Object.keys(failuresByTree, function(tree, resultsByReason) { 193 var treeFailures = []; 194 Object.keys(resultsByReason, function(reasonKey, resultsByBuilder) { 195 var failure = JSON.parse(reasonKey); 196 treeFailures.push( 197 new CTStepFailure(failure.step, failure.reason, resultsByBuilder)); 198 }) 199 treeFailures.sort(this._failureComparator); 200 201 if (!newFailures[tree]) 202 newFailures[tree] = []; 203 var commitList = new CTCommitList(this.commitLog, rangeGroup.likely_revisions); 204 newFailures[tree].push(new CTFailureGroup(rangeGroup.sort_key, new CTStepFailureGroupData(treeFailures, commitList))); 205 }.bind(this)); 206 }; 207 208 CTFailures.prototype._processBuilderFailure = function(newFailures, alert) { 209 var timeSinceLastUpdate = (this.lastUpdateDate.valueOf() / 1000) - alert.last_update_time; 210 var failure = new CTBuilderFailure(alert.tree, alert.master_url, alert.builder_name, alert.state, 211 timeSinceLastUpdate, alert.pending_builds); 212 var data = new CTBuilderFailureGroupData(failure, alert.builder_name, 213 alert.master_url + "/builders/" + alert.builder_name); 214 if (!newFailures[alert.tree]) 215 newFailures[alert.tree] = []; 216 newFailures[alert.tree].push(new CTFailureGroup(failure.key, data, 'builders')); 217 }; 218 219 })(); 220 221 222