blob: f0a232993a387d2583299248d2cbcc5b802e64cb [file] [log] [blame]
'use strict';
class Metric extends LabeledObject {
constructor(id, object)
{
super(id, object);
this._aggregatorName = object.aggregator;
object.test.addMetric(this);
this._test = object.test;
this._platforms = [];
const suffix = this.name().match('([A-z][a-z]+|FrameRate)$')[0];
this._unit = {
'FrameRate': 'fps',
'Runs': '/s',
'Time': 'ms',
'Duration': 'ms',
'Malloc': 'B',
'Heap': 'B',
'Allocations': 'B',
'Size': 'B',
'Score': 'pt',
}[suffix];
}
aggregatorName() { return this._aggregatorName; }
test() { return this._test; }
platforms() { return this._platforms; }
addPlatform(platform)
{
console.assert(platform instanceof Platform);
this._platforms.push(platform);
}
childMetrics()
{
var metrics = [];
for (var childTest of this._test.childTests()) {
for (var childMetric of childTest.metrics())
metrics.push(childMetric);
}
return metrics;
}
path() { return this._test.path().concat([this]); }
fullName() { return this._test.fullName() + ' : ' + this.label(); }
relativeName(path)
{
const relativeTestName = this._test.relativeName(path);
if (relativeTestName == null)
return this.label();
return relativeTestName + ' : ' + this.label();
}
aggregatorLabel()
{
switch (this._aggregatorName) {
case 'Arithmetic':
return 'Arithmetic mean';
case 'Geometric':
return 'Geometric mean';
case 'Harmonic':
return 'Harmonic mean';
case 'Total':
return 'Total';
}
return null;
}
label()
{
const aggregatorLabel = this.aggregatorLabel();
return this.name() + (aggregatorLabel ? ` : ${aggregatorLabel}` : '');
}
unit() { return this._unit; }
isSmallerBetter()
{
const unit = this._unit;
return unit != 'fps' && unit != '/s' && unit != 'pt';
}
labelForDifference(beforeValue, afterValue, progressionTypeName, regressionTypeName)
{
const diff = afterValue - beforeValue;
const changeType = diff < 0 == this.isSmallerBetter() ? progressionTypeName : regressionTypeName;
const relativeChange = diff / beforeValue * 100;
const changeLabel = Math.abs(relativeChange).toFixed(2) + '% ' + changeType;
return {changeType, relativeChange, changeLabel};
}
makeFormatter(sigFig, alwaysShowSign) { return Metric.makeFormatter(this.unit(), sigFig, alwaysShowSign); }
static makeFormatter(unit, sigFig = 2, alwaysShowSign = false)
{
let isMiliseconds = false;
if (unit == 'ms') {
isMiliseconds = true;
unit = 's';
}
if (!unit)
return function (value) { return value.toFixed(2) + ' ' + (unit || ''); }
var divisor = unit == 'B' ? 1024 : 1000;
var suffix = ['\u03BC', 'm', '', 'K', 'M', 'G', 'T', 'P', 'E'];
var threshold = sigFig >= 3 ? divisor : (divisor / 10);
let formatter = function (value, maxAbsValue = 0) {
var i;
var sign = value >= 0 ? (alwaysShowSign ? '+' : '') : '-';
value = Math.abs(value);
let sigFigForValue = sigFig;
// The number of sig-figs to reduce in order to match that of maxAbsValue
// e.g. 0.5 instead of 0.50 when maxAbsValue is 2.
let adjustment = 0;
if (maxAbsValue && value)
adjustment = Math.max(0, Math.floor(Math.log(maxAbsValue) / Math.log(10)) - Math.floor(Math.log(value) / Math.log(10)));
for (i = isMiliseconds ? 1 : 2; value && value < 1 && i > 0; i--)
value *= divisor;
for (; value >= threshold; i++)
value /= divisor;
if (adjustment) // Make the adjustment only for decimal positions below 1.
adjustment = Math.min(adjustment, Math.max(0, -Math.floor(Math.log(value) / Math.log(10))));
return sign + value.toPrecision(sigFig - adjustment) + ' ' + suffix[i] + (unit || '');
}
formatter.divisor = divisor;
return formatter;
};
static formatTime(utcTime)
{
// FIXME: This is incorrect when the offset cross day-life-saving change. It's good enough for now.
const offsetInMinutes = (new Date(utcTime)).getTimezoneOffset();
const timeInLocalTimeZone = new Date(utcTime - offsetInMinutes * 60 * 1000);
return timeInLocalTimeZone.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
}
}
if (typeof module != 'undefined')
module.exports.Metric = Metric;