diff options
Diffstat (limited to 'testing/talos/talos/scripts/talos-debug.js')
-rw-r--r-- | testing/talos/talos/scripts/talos-debug.js | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/testing/talos/talos/scripts/talos-debug.js b/testing/talos/talos/scripts/talos-debug.js new file mode 100644 index 0000000000..90c102071e --- /dev/null +++ b/testing/talos/talos/scripts/talos-debug.js @@ -0,0 +1,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; +} |