| import * as Statistics from "./statistics.mjs"; |
| |
| export const MILLISECONDS_PER_MINUTE = 60 * 1000; |
| |
| export class Metric { |
| static separator = "/"; |
| |
| constructor(name, unit = "ms") { |
| if (typeof name !== "string") |
| throw new Error(`Invalid metric.name=${name}, expected string.`); |
| this.name = name; |
| this.unit = unit; |
| |
| this.mean = 0.0; |
| this.geomean = 0.0; |
| this.delta = 0.0; |
| this.percentDelta = 0.0; |
| |
| this.sum = 0.0; |
| this.min = 0.0; |
| this.max = 0.0; |
| |
| this.values = []; |
| this.children = []; |
| |
| // Mark properties which refer to other Metric objects as |
| // non-enumerable to avoid issue with JSON.stringify due to circular |
| // references. |
| Object.defineProperties(this, { |
| parent: { |
| writable: true, |
| value: undefined, |
| }, |
| }); |
| } |
| |
| get shortName() { |
| return this.parent ? this.name.replace(`${this.parent.name}-`, "") : this.name; |
| } |
| |
| get valueString() { |
| const mean = this.mean.toFixed(2); |
| if (!this.percentDelta || !this.delta) |
| return `${mean} ${this.unit}`; |
| return `${mean} ± ${this.deltaString} ${this.unit}`; |
| } |
| |
| get deltaString() { |
| if (!this.percentDelta || !this.delta) |
| return ""; |
| return `${this.delta.toFixed(2)} (${this.percentDelta.toFixed(1)}%)`; |
| } |
| |
| get length() { |
| return this.values.length; |
| } |
| |
| addChild(metric) { |
| if (metric.parent) |
| throw new Error("Cannot re-add sub metric"); |
| metric.parent = this; |
| this.children.push(metric); |
| } |
| |
| add(value) { |
| if (typeof value !== "number") |
| throw new Error(`Adding invalid value=${value} to metric=${this.name}`); |
| this.values.push(value); |
| } |
| |
| computeAggregatedMetrics() { |
| // Avoid the loss of significance for the sum. |
| const sortedValues = this.values.concat().sort((a, b) => a - b); |
| this.sum = Statistics.sum(sortedValues); |
| this.min = sortedValues[0]; |
| this.max = sortedValues[sortedValues.length - 1]; |
| this.mean = this.sum / this.values.length; |
| const product = Statistics.product(sortedValues); |
| this.geomean = Math.pow(product, 1 / this.values.length); |
| if (this.values.length > 1) { |
| const squareSum = Statistics.squareSum(sortedValues); |
| this.delta = Statistics.confidenceIntervalDelta(0.95, this.values.length, this.sum, squareSum); |
| this.percentDelta = isNaN(this.delta) ? undefined : (this.delta * 100) / this.mean; |
| } |
| } |
| } |