github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/triage/model.js (about)

     1  "use strict";
     2  
     3  // Return the minimum and maximum value of arr.
     4  // Doesn't cause stack overflows like Math.min(...arr).
     5  function minMaxArray(arr) {
     6    var min = arr[0];
     7    var max = arr[0];
     8    for (var i = 1; i < arr.length; i++) {
     9      if (arr[i] < min) min = arr[i];
    10      if (arr[i] > max) max = arr[i];
    11    }
    12    return [min, max];
    13  }
    14  
    15  function tsToString(ts) {
    16    return new Date(ts * 1000).toLocaleString();
    17  }
    18  
    19  // Store information about individual builds.
    20  class Builds {
    21    constructor(dict) {
    22      this.jobs = dict.jobs;
    23      this.jobPaths = dict.job_paths;
    24      this.cols = dict.cols;
    25      this.colStarted = this.cols.started;
    26      this.colPr = this.cols.pr;
    27      this.timespan = minMaxArray(this.cols.started);
    28      this.runCount = this.cols.started.length;
    29    }
    30  
    31    // Create a build object given a job and build number.
    32    get(job, number) {
    33      let indices = this.jobs[job];
    34      if (indices.constructor === Array) {
    35        let [start, count, base] = indices;
    36        if (number < start || number > start + count) {
    37          console.error('job ' + job + ' number ' + number + ' out of range.');
    38          return;
    39        }
    40        var index = base + (number - start);
    41      } else {
    42        var index = indices[number];
    43      }
    44      // Add more columns as necessary.
    45      // This is faster than dynamically adding properties to an object.
    46      return {
    47        job: job,
    48        number: number,
    49        started: this.colStarted[index],
    50        pr: this.colPr[index],
    51      };
    52    }
    53  
    54    // Count how many builds a job has.
    55    count(job) {
    56      let indices = this.jobs[job];
    57      if (indices.constructor === Array) {
    58        return indices[1];
    59      }
    60      return Object.keys(indices).length;
    61    }
    62  
    63    getStartTime() {
    64      return tsToString(this.timespan[0]);
    65    }
    66  
    67    getEndTime() {
    68      return tsToString(this.timespan[1]);
    69    }
    70  }
    71  
    72  function sum(arr, keyFunc) {
    73    if (arr.length === 0)
    74      return 0;
    75    return arr.map(keyFunc).reduce((a, b) => a + b);
    76  }
    77  
    78  function clustersSum(tests) {
    79    return sum(tests, t => sum(t.jobs, j => j.builds.length));
    80  }
    81  
    82  // Return arr sotred by value according to keyFunc, which
    83  // should take an element of arr and return an array of values.
    84  function sortByKey(arr, keyFunc) {
    85    var vals = arr.map((x, i) => [keyFunc(x), x]);
    86    vals.sort((a, b) => {
    87      for (var i = 0; i < a[0].length; i++) {
    88        let elA = a[0][i], elB = b[0][i];
    89        if (elA > elB) return 1;
    90        if (elA < elB) return -1;
    91      }
    92    });
    93    return vals.map(x => x[1]);
    94  }
    95  
    96  // Return a build for each test run that failed in the given cluster.
    97  // Builds will be duplicated if it has multiple failed tests in the cluster.
    98  function *buildsForCluster(entry) {
    99    for (let test of entry.tests) {
   100      for (let job of test.jobs) {
   101        for (let number of job.builds) {
   102          let build = builds.get(job.name, number);
   103          if (build) {
   104            yield build;
   105          }
   106        }
   107      }
   108    }
   109  }
   110  
   111  function *buildsWithContextForCluster(entry) {
   112    for (let test of entry.tests) {
   113      for (let job of test.jobs) {
   114        for (let number of job.builds) {
   115          let build = builds.get(job.name, number);
   116          if (build) {
   117            yield [build, job.name, test.name];
   118          }
   119        }
   120      }
   121    }
   122  }
   123  
   124  // Return the number of builds that completed in the last day's worth of data.
   125  function getHitsInLastDay(entry) {
   126    if (entry.dayHits) {
   127      return entry.dayHits;
   128    }
   129    var minStarted = builds.timespan[1] - 60 * 60 * 24;
   130    var count = 0;
   131    for (let build of buildsForCluster(entry)) {
   132      if (build.started > minStarted) {
   133        count++;
   134      }
   135    }
   136    entry.dayHits = count;
   137    return count;
   138  }
   139  
   140  // Store test clusters and support iterating and refiltering through them.
   141  class Clusters {
   142    constructor(clustered) {
   143      this.data = clustered;
   144      this.length = this.data.length;
   145      this.sum = sum(this.data, c => clustersSum(c.tests));
   146      this.sumRecent = sum(this.data, c => c.dayHits || 0);
   147      this.byId = {};
   148      for (let cluster of this.data) {
   149        let keyId = cluster.id;
   150        if (!this.byId[keyId]) {
   151          this.byId[keyId] = cluster;
   152        }
   153      }
   154    }
   155  
   156    buildsForClusterById(clusterId) {
   157      return buildsForCluster(this.byId[clusterId]);
   158    }
   159  
   160    buildsWithContextForClusterById(clusterId) {
   161      return buildsWithContextForCluster(this.byId[clusterId]);
   162    }
   163  
   164    getHitsInLastDayById(clusterId) {
   165      return getHitsInLastDay(this.byId[clusterId]);
   166    }
   167  
   168    // Iterate through all builds. Can return duplicates.
   169    *allBuilds() {
   170      for (let entry of this.data) {
   171        yield *buildsForCluster(entry);
   172      }
   173    }
   174  
   175    // Return a new Clusters object, with the given filters applied.
   176    refilter(opts) {
   177      var out = [];
   178      for (let cluster of this.data) {
   179        if (opts.reText && !opts.reText.test(cluster.text)) {
   180          continue;
   181        }
   182        if (opts.sig && opts.sig.length && opts.sig.indexOf(cluster.owner) < 0) {
   183          continue;
   184        }
   185        var testsOut = [];
   186        for (let test of cluster.tests) {
   187          if (opts.reTest && !opts.reTest.test(test.name)) {
   188            continue;
   189          }
   190          var jobsOut = [];
   191          for (let job of test.jobs) {
   192            if (opts.reJob && !opts.reJob.test(job.name)) {
   193              continue;
   194            }
   195            if (job.name.startsWith("pr:")) {
   196              if (opts.pr) jobsOut.push(job);
   197            } else if (job.name.indexOf(":") === -1) {
   198              if (opts.ci) jobsOut.push(job);
   199            }
   200          }
   201          if (jobsOut.length > 0) {
   202            jobsOut = sortByKey(jobsOut, j => [-j.builds.length, j.name]);
   203            testsOut.push({name: test.name, jobs: jobsOut});
   204          }
   205        }
   206        if (testsOut.length > 0) {
   207          testsOut = sortByKey(testsOut, t => [-sum(t.jobs, j => j.builds.length)]);
   208          out.push(Object.assign({}, cluster, {tests: testsOut}));
   209        }
   210      }
   211  
   212      if (opts.sort) {
   213        var keyFunc = {
   214          total: c => [-clustersSum(c.tests)],
   215          message: c => [c.text],
   216          day: c => [-getHitsInLastDay(c), -clustersSum(c.tests)],
   217        }[opts.sort];
   218        out = sortByKey(out, keyFunc);
   219      }
   220  
   221      return new Clusters(out);
   222    }
   223  
   224    makeCounts(clusterId) {
   225      let start = builds.timespan[0];
   226      let width = 60 * 60 * 8;  // 8 hours
   227  
   228      function pickBucket(ts) {
   229        return ((ts - start) / width) | 0;
   230      }
   231  
   232      let size = pickBucket(builds.timespan[1]) + 1;
   233  
   234      let counts = {};
   235  
   236      function incr(key, bucket) {
   237        if (counts[key] === undefined) {
   238          counts[key] = new Uint32Array(size);
   239        }
   240        counts[key][bucket]++;
   241      }
   242  
   243      for (let [build, job, test] of this.buildsWithContextForClusterById(clusterId)) {
   244        let bucket = pickBucket(build.started);
   245        incr('', bucket);
   246        incr(job, bucket);
   247        incr(test, bucket);
   248        incr(job + " " + test, bucket);
   249      }
   250  
   251      return counts;
   252    }
   253  }
   254  
   255  if (typeof module !== 'undefined' && module.exports) {
   256    // enable node.js `require` to work for testing
   257    module.exports = {
   258      Builds: Builds,
   259      Clusters: Clusters,
   260    }
   261  }