summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/scripts/talos-debug.js
blob: 90c102071ed4f338775feae16412d10ae5915fda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** ****************************************************************
 * window.talosDebug provides some statistical functions
 * (sum, average, median, stddev) and a tpRecordTime method which
 * reports some statistics about the data set, including detected
 * stability point of the data (detecting first few noisy elements).
 * If tpRecordTime doesn't exist globally (i.e. running outside of
 * talos, e.g. in a browser), then it points it to window.talosDebug.tpRecordTime
 * Can be controlled by few properties (disable display, hardcoded
 * stability point, etc)
 *
 * talos-debug.js: Bug 849558
 *****************************************************************/
window.talosDebug = {
  // Optional config properties
  disabled: false,
  ignore: -1, // Number of items to ignore at the begining of the set. -1 for auto-detect.
  displayData: false, // If true, will also display all the data points.
  fixed: 2, // default floating point digits for display.
  // End of config

  sum(values) {
    return values.reduce(function (a, b) {
      return a + b;
    });
  },

  average(values) {
    var d = window.talosDebug;
    return values.length ? d.sum(values) / values.length : 999999;
  },

  median(values) {
    var clone = values.slice(0);
    var sorted = clone.sort(function (a, b) {
      // eslint-disable-next-line no-nested-ternary
      return a > b ? 1 : a < b ? -1 : 0;
    });
    var len = values.length;
    if (!len) {
      return 999999;
    }
    if (len % 2) {
      // We have a middle number
      return sorted[(len - 1) / 2];
    }
    return (sorted[len / 2] + sorted[len / 2 - 1]) / 2; // Average value of the two middle items.
  },

  stddev(values, avg) {
    if (values.length <= 1) {
      return 0;
    }

    return Math.sqrt(
      values
        .map(function (v) {
          return Math.pow(v - avg, 2);
        })
        .reduce(function (a, b) {
          return a + b;
        }) /
        (values.length - 1)
    );
  },

  // Estimate the number of warmup iterations of this data set (in a completely unscrientific way).
  // returns -1 if could not find a stability point (supposedly meaning that the values
  // are still in the process of convergence).
  // Algorithm (not based on a scientific method which I know of):
  // Target: Find a locally noisy value after the values show stability
  //         (stddev of a moving window stops decreasing), while trying to make sure
  //         that it wasn't a glitch and the next 2 floating window stddev are, indeed, not
  //         still decreasing.
  //         Do the above for few different window widths (3-7), get the maximum result of those.
  //         Now we should have an index which is at least the 2nd value within the stable part.
  //         We get stddev for that index..end (baseStd), and then go backwards as long as stddev is
  //         decreasing or within ~1% of baseStd, and return the earliest index for which it is.
  detectWarmup(values) {
    var MIN_WIDTH = 3;
    var MAX_WIDTH = 7;
    var d = window.talosDebug;

    function windowStd(from, winSize) {
      var win = values.slice(from, from + winSize);
      return d.stddev(win, d.median(values));
    }

    var stableFrom = -1;
    var overallAverage = d.median(values);
    // var overallStd = d.stddev(values, overallAverage);
    for (var winWidth = MIN_WIDTH; winWidth < MAX_WIDTH + 1; winWidth++) {
      var prevStd = windowStd(0, winWidth);
      for (var i = 1; i < values.length - winWidth - 3; i++) {
        var w0 = windowStd(i + 0, winWidth);
        var w1 = windowStd(i + 1, winWidth);
        var w2 = windowStd(i + 2, winWidth);
        // var currWindow = values.slice(i, i + winWidth);
        if (w0 >= prevStd && !(w1 < w0 && w2 < w1)) {
          if (i > stableFrom) {
            stableFrom = i;
          }
          break;
        }
        prevStd = w0;
      }
    }

    function withinPercentage(base, value, percentage) {
      return Math.abs(base - value) < (base * percentage) / 100;
    }
    // Now go backwards as long as stddev decreases or doesn't increase in more than 1%.
    var baseStd = d.stddev(values.slice(stableFrom), overallAverage);
    var len = values.length;
    while (true) {
      var current = d.stddev(values.slice(stableFrom - 1), overallAverage);
      // 100/len : the more items we have, the more sensitively we compare:
      //   for 100 items, we're allowing 2% over baseStd.
      if (
        stableFrom > 0 &&
        (current < baseStd ||
          withinPercentage(baseStd, current, 200 / (len ? len : 100)))
      ) {
        stableFrom--;
      } else {
        break;
      }
    }

    return stableFrom;
  },

  statsDisplay(collection) {
    var d = window.talosDebug;
    var std = d.stddev(collection, d.average(collection));
    var avg = d.average(collection);
    var med = d.median(collection);
    return (
      "Count: " +
      collection.length +
      "\nAverage: " +
      avg.toFixed(d.fixed) +
      "\nMedian: " +
      med.toFixed(d.fixed) +
      "\nStdDev: " +
      std.toFixed(d.fixed) +
      " (" +
      ((100 * std) / (avg ? avg : 1)).toFixed(d.fixed) +
      "% of average)"
    );
  },

  tpRecordTime(dataCSV) {
    var d = window.talosDebug;
    if (d.disabled) {
      return;
    }

    var collection = ("" + dataCSV).split(",").map(function (item) {
      return parseFloat(item);
    });
    var res = d.statsDisplay(collection);

    var warmup = d.ignore >= 0 ? d.ignore : d.detectWarmup(collection);
    if (warmup >= 0) {
      res +=
        "\n\nWarmup " +
        (d.ignore >= 0 ? "requested: " : "auto-detected: ") +
        warmup;
      var warmedUp = collection.slice(warmup);
      if (warmup) {
        res +=
          "\nAfter ignoring first " +
          (warmup > 1 ? warmup + " items" : "item") +
          ":\n";
        res += d.statsDisplay(warmedUp);
      }
    } else {
      res += "\n\nWarmup auto-detection: Failed.";
    }

    if (d.displayData) {
      var disp = collection.map(function (item) {
        return item.toFixed(d.fixed);
      });
      if (warmup >= 0) {
        disp.splice(warmup, 0, "[warmed-up:]");
      }
      res += "\n\nRecorded:\n" + disp.join(", ");

      res += "\n\nStddev from item NN to last:\n";
      disp = collection.map(function (value, index) {
        return d
          .stddev(collection.slice(index), d.average(collection.slice(index)))
          .toFixed(d.fixed);
      });
      if (warmup >= 0) {
        disp.splice(warmup, 0, "[warmed-up:]");
      }
      res += disp.join(", ");
    } else {
      res += "\n\n[set window.talosDebug.displayData=true for data]";
    }

    alert(res);
  },
};

// Enable testing outside of talos by providing an alternative report function.
if (typeof tpRecordTime === "undefined") {
  tpRecordTime = window.talosDebug.tpRecordTime;
}