Home | History | Annotate | Download | only in model
      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