}
}
- private median(dataSet: number[]): number {
- if (Array.isArray(dataSet) === true && dataSet.length === 1) {
- return dataSet[0];
- }
- const sortedDataSet = dataSet.slice().sort((a, b) => a - b);
- const middleIndex = Math.floor(sortedDataSet.length / 2);
- if (sortedDataSet.length % 2 === 0) {
- return sortedDataSet[middleIndex];
- }
- return (sortedDataSet[middleIndex - 1] + sortedDataSet[middleIndex]) / 2;
- }
-
- // TODO: use order statistics tree https://en.wikipedia.org/wiki/Order_statistic_tree
- private percentile(dataSet: number[], percentile: number): number {
- if (percentile < 0 && percentile > 100) {
- throw new RangeError('Percentile is not between 0 and 100');
- }
- if (Utils.isEmptyArray(dataSet)) {
- return 0;
- }
- const sortedDataSet = dataSet.slice().sort((a, b) => a - b);
- if (percentile === 0) {
- return sortedDataSet[0];
- }
- if (percentile === 100) {
- return sortedDataSet[sortedDataSet.length - 1];
- }
- const percentileIndex = (percentile / 100) * sortedDataSet.length - 1;
- if (Number.isInteger(percentileIndex)) {
- return (sortedDataSet[percentileIndex] + sortedDataSet[percentileIndex + 1]) / 2;
- }
- return sortedDataSet[Math.floor(percentileIndex)];
- }
-
- private stdDeviation(dataSet: number[]): number {
- let totalDataSet = 0;
- for (const data of dataSet) {
- totalDataSet += data;
- }
- const dataSetMean = totalDataSet / dataSet.length;
- let totalGeometricDeviation = 0;
- for (const data of dataSet) {
- const deviation = data - dataSetMean;
- totalGeometricDeviation += deviation * deviation;
- }
- return Math.sqrt(totalGeometricDeviation / dataSet.length);
- }
-
private addPerformanceEntryToStatistics(entry: PerformanceEntry): void {
const entryName = entry.name;
// Initialize command statistics
timestamp: entry.startTime,
value: entry.duration,
}));
- this.statistics.statisticsData.get(entryName).medTimeMeasurement = this.median(
+ this.statistics.statisticsData.get(entryName).medTimeMeasurement = Utils.median(
this.extractTimeSeriesValues(
this.statistics.statisticsData.get(entryName).timeMeasurementSeries
)
);
this.statistics.statisticsData.get(entryName).ninetyFiveThPercentileTimeMeasurement =
- this.percentile(
+ Utils.percentile(
this.extractTimeSeriesValues(
this.statistics.statisticsData.get(entryName).timeMeasurementSeries
),
95
);
- this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = this.stdDeviation(
+ this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = Utils.stdDeviation(
this.extractTimeSeriesValues(
this.statistics.statisticsData.get(entryName).timeMeasurementSeries
)
}
return '(Unknown)';
}
+
+ public static median(dataSet: number[]): number {
+ if (Array.isArray(dataSet) === true && dataSet.length === 1) {
+ return dataSet[0];
+ }
+ dataSet = dataSet.slice().sort((a, b) => a - b);
+ return (dataSet[(dataSet.length - 1) >> 1] + dataSet[dataSet.length >> 1]) / 2;
+ }
+
+ // TODO: use order statistics tree https://en.wikipedia.org/wiki/Order_statistic_tree
+ public static percentile(dataSet: number[], percentile: number): number | undefined {
+ if (percentile < 0 && percentile > 100) {
+ throw new RangeError('Percentile is not between 0 and 100');
+ }
+ if (Utils.isEmptyArray(dataSet)) {
+ return undefined;
+ }
+ const sortedDataSet = dataSet.slice().sort((a, b) => a - b);
+ if (percentile === 0 || sortedDataSet.length === 1) {
+ return sortedDataSet[0];
+ }
+ if (percentile === 100) {
+ return sortedDataSet[sortedDataSet.length - 1];
+ }
+ const percentileIndexBase = (percentile / 100) * (sortedDataSet.length - 1);
+ const percentileIntegerIndex = Math.floor(percentileIndexBase);
+ if (!Utils.isNullOrUndefined(sortedDataSet[percentileIntegerIndex + 1])) {
+ return (
+ sortedDataSet[percentileIntegerIndex] +
+ (percentileIndexBase - percentileIntegerIndex) *
+ (sortedDataSet[percentileIntegerIndex + 1] - sortedDataSet[percentileIntegerIndex])
+ );
+ }
+ return sortedDataSet[percentileIntegerIndex];
+ }
+
+ public static stdDeviation(dataSet: number[]): number {
+ let totalDataSet = 0;
+ for (const data of dataSet) {
+ totalDataSet += data;
+ }
+ const dataSetMean = totalDataSet / dataSet.length;
+ let totalGeometricDeviation = 0;
+ for (const data of dataSet) {
+ const deviation = data - dataSetMean;
+ totalGeometricDeviation += deviation * deviation;
+ }
+ return Math.sqrt(totalGeometricDeviation / dataSet.length);
+ }
}
expect(Utils.isEmptyObject(new WeakMap())).toBe(false);
expect(Utils.isEmptyObject(new WeakSet())).toBe(false);
});
+
+ it('Verify median()', () => {
+ const array0 = [0.08];
+ expect(Utils.median(array0)).toBe(0.08);
+ const array1 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03];
+ expect(Utils.median(array1)).toBe(3.05);
+ });
+
+ it('Verify percentile()', () => {
+ expect(Utils.percentile([], 25)).toBe(undefined);
+ const array0 = [0.08];
+ expect(Utils.percentile(array0, 50)).toBe(0.08);
+ const array1 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03];
+ expect(Utils.percentile(array1, 0)).toBe(0.25);
+ expect(Utils.percentile(array1, 50)).toBe(3.05);
+ expect(Utils.percentile(array1, 80)).toBe(4.974);
+ expect(Utils.percentile(array1, 85)).toBe(5.131);
+ expect(Utils.percentile(array1, 90)).toBe(5.434);
+ expect(Utils.percentile(array1, 95)).toBe(5.736999999999999);
+ expect(Utils.percentile(array1, 100)).toBe(6.04);
+ });
+
+ it('Verify stdDeviation()', () => {
+ const array1 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03];
+ expect(Utils.stdDeviation(array1)).toBe(2.0256064851429216);
+ });
});