From d4c3e68a1a6321f2f43ef0521e121e827f3eb29b Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 9 May 2023 00:11:31 +0200 Subject: [PATCH] fix: move and fix statistic related helpers implementation MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/performance/PerformanceStatistics.ts | 54 ++---------------------- src/utils/Utils.ts | 49 +++++++++++++++++++++ test/utils/UtilsTest.ts | 26 ++++++++++++ 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index ecd6b3ab..c25cde0d 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -184,54 +184,6 @@ export class PerformanceStatistics { } } - 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 @@ -273,19 +225,19 @@ export class PerformanceStatistics { 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 ) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 4306d887..717a6e68 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -335,4 +335,53 @@ export class Utils { } 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); + } } diff --git a/test/utils/UtilsTest.ts b/test/utils/UtilsTest.ts index 5732e57e..35294d07 100644 --- a/test/utils/UtilsTest.ts +++ b/test/utils/UtilsTest.ts @@ -329,4 +329,30 @@ describe('Utils test suite', () => { 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); + }); }); -- 2.34.1