Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
}