| '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; |