refactor(simulator): factor out statistic helpers
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 29 May 2023 13:08:56 +0000 (15:08 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 29 May 2023 13:08:56 +0000 (15:08 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/performance/PerformanceStatistics.ts
src/utils/StatisticUtils.ts [new file with mode: 0644]
src/utils/Utils.ts
src/utils/index.ts
test/utils/CircularArrayTest.ts
test/utils/StatisticUtilsTest.ts [new file with mode: 0644]
test/utils/UtilsTest.ts

index cd0ff3dff6592e1709cc1655f253efe56fec1eb0..565ef6e7a2e4126a6fcc7e7e8e6c7c6f0f6cef8a 100644 (file)
@@ -18,6 +18,9 @@ import {
   Utils,
   buildPerformanceStatisticsMessage,
   logger,
+  median,
+  nthPercentile,
+  stdDeviation,
 } from '../utils';
 
 export class PerformanceStatistics {
@@ -231,19 +234,19 @@ export class PerformanceStatistics {
             timestamp: entry.startTime,
             value: entry.duration,
           }));
-    this.statistics.statisticsData.get(entryName).medTimeMeasurement = Utils.median(
+    this.statistics.statisticsData.get(entryName).medTimeMeasurement = median(
       this.extractTimeSeriesValues(
         this.statistics.statisticsData.get(entryName).timeMeasurementSeries
       )
     );
     this.statistics.statisticsData.get(entryName).ninetyFiveThPercentileTimeMeasurement =
-      Utils.percentile(
+      nthPercentile(
         this.extractTimeSeriesValues(
           this.statistics.statisticsData.get(entryName).timeMeasurementSeries
         ),
         95
       );
-    this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = Utils.stdDeviation(
+    this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = stdDeviation(
       this.extractTimeSeriesValues(
         this.statistics.statisticsData.get(entryName).timeMeasurementSeries
       )
diff --git a/src/utils/StatisticUtils.ts b/src/utils/StatisticUtils.ts
new file mode 100644 (file)
index 0000000..bb9a847
--- /dev/null
@@ -0,0 +1,55 @@
+import { Utils } from './Utils';
+
+export const median = (dataSet: number[]): number => {
+  if (Utils.isEmptyArray(dataSet)) {
+    return 0;
+  }
+  if (Array.isArray(dataSet) === true && dataSet.length === 1) {
+    return dataSet[0];
+  }
+  const sortedDataSet = dataSet.slice().sort((a, b) => a - b);
+  return (
+    (sortedDataSet[(sortedDataSet.length - 1) >> 1] + sortedDataSet[sortedDataSet.length >> 1]) / 2
+  );
+};
+
+// TODO: use order statistics tree https://en.wikipedia.org/wiki/Order_statistic_tree
+export const nthPercentile = (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 || sortedDataSet.length === 1) {
+    return sortedDataSet[0];
+  }
+  if (percentile === 100) {
+    return sortedDataSet[sortedDataSet.length - 1];
+  }
+  const percentileIndexBase = (percentile / 100) * (sortedDataSet.length - 1);
+  const percentileIndexInteger = Math.floor(percentileIndexBase);
+  if (!Utils.isNullOrUndefined(sortedDataSet[percentileIndexInteger + 1])) {
+    return (
+      sortedDataSet[percentileIndexInteger] +
+      (percentileIndexBase - percentileIndexInteger) *
+        (sortedDataSet[percentileIndexInteger + 1] - sortedDataSet[percentileIndexInteger])
+    );
+  }
+  return sortedDataSet[percentileIndexInteger];
+};
+
+export const 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);
+};
index 3d8c73bdf0cdbeec9516dd079832587a0696ebba..c86afecaf71984b36378c12e3f6c9b02b964c7dc 100644 (file)
@@ -334,59 +334,4 @@ export class Utils {
     }
     return '(Unknown)';
   }
-
-  public static median(dataSet: number[]): number {
-    if (Utils.isEmptyArray(dataSet)) {
-      return 0;
-    }
-    if (Array.isArray(dataSet) === true && dataSet.length === 1) {
-      return dataSet[0];
-    }
-    const sortedDataSet = dataSet.slice().sort((a, b) => a - b);
-    return (
-      (sortedDataSet[(sortedDataSet.length - 1) >> 1] + sortedDataSet[sortedDataSet.length >> 1]) /
-      2
-    );
-  }
-
-  // TODO: use order statistics tree https://en.wikipedia.org/wiki/Order_statistic_tree
-  public static 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 || sortedDataSet.length === 1) {
-      return sortedDataSet[0];
-    }
-    if (percentile === 100) {
-      return sortedDataSet[sortedDataSet.length - 1];
-    }
-    const percentileIndexBase = (percentile / 100) * (sortedDataSet.length - 1);
-    const percentileIndexInteger = Math.floor(percentileIndexBase);
-    if (!Utils.isNullOrUndefined(sortedDataSet[percentileIndexInteger + 1])) {
-      return (
-        sortedDataSet[percentileIndexInteger] +
-        (percentileIndexBase - percentileIndexInteger) *
-          (sortedDataSet[percentileIndexInteger + 1] - sortedDataSet[percentileIndexInteger])
-      );
-    }
-    return sortedDataSet[percentileIndexInteger];
-  }
-
-  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);
-  }
 }
index 2506da85375da705a69eb0eb516b22ec71fbaa8d..b2374d7361e6ee0c0d3d4f0a74edeb8259eea1e9 100644 (file)
@@ -24,4 +24,5 @@ export {
   buildStoppedMessage,
 } from './MessageChannelUtils';
 export { Utils } from './Utils';
+export { median, nthPercentile, stdDeviation } from './StatisticUtils';
 export { logger } from './Logger';
index 0a2609c0a2d19b65878647531b2a214e711a0b77..720ca68de3c4e006c7739ffed7f61f0f6523b70f 100644 (file)
@@ -2,7 +2,7 @@ import { expect } from 'expect';
 
 import { CircularArray } from '../../src/utils/CircularArray';
 
-describe('Circular array test suite', () => {
+describe('CircularArray test suite', () => {
   it('Verify that circular array can be instantiated', () => {
     const circularArray = new CircularArray();
     expect(circularArray).toBeInstanceOf(CircularArray);
diff --git a/test/utils/StatisticUtilsTest.ts b/test/utils/StatisticUtilsTest.ts
new file mode 100644 (file)
index 0000000..2910c32
--- /dev/null
@@ -0,0 +1,29 @@
+import { expect } from 'expect';
+
+import { median, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils';
+
+describe('StatisticUtils test suite', () => {
+  it('Verify median()', () => {
+    expect(median([])).toBe(0);
+    expect(median([0.08])).toBe(0.08);
+    expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.05);
+    expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.535);
+  });
+
+  it('Verify nthPercentile()', () => {
+    expect(nthPercentile([], 25)).toBe(0);
+    expect(nthPercentile([0.08], 50)).toBe(0.08);
+    const array0 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03];
+    expect(nthPercentile(array0, 0)).toBe(0.25);
+    expect(nthPercentile(array0, 50)).toBe(3.05);
+    expect(nthPercentile(array0, 80)).toBe(4.974);
+    expect(nthPercentile(array0, 85)).toBe(5.131);
+    expect(nthPercentile(array0, 90)).toBe(5.434);
+    expect(nthPercentile(array0, 95)).toBe(5.736999999999999);
+    expect(nthPercentile(array0, 100)).toBe(6.04);
+  });
+
+  it('Verify stdDeviation()', () => {
+    expect(stdDeviation([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.0256064851429216);
+  });
+});
index f5e1259db9a95200eafc188116977a2b4aca03b8..5732e57ed316673e1cbc538d9b853a0c33d90665 100644 (file)
@@ -329,28 +329,4 @@ describe('Utils test suite', () => {
     expect(Utils.isEmptyObject(new WeakMap())).toBe(false);
     expect(Utils.isEmptyObject(new WeakSet())).toBe(false);
   });
-
-  it('Verify median()', () => {
-    expect(Utils.median([])).toBe(0);
-    expect(Utils.median([0.08])).toBe(0.08);
-    expect(Utils.median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.05);
-    expect(Utils.median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.535);
-  });
-
-  it('Verify percentile()', () => {
-    expect(Utils.percentile([], 25)).toBe(0);
-    expect(Utils.percentile([0.08], 50)).toBe(0.08);
-    const array0 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03];
-    expect(Utils.percentile(array0, 0)).toBe(0.25);
-    expect(Utils.percentile(array0, 50)).toBe(3.05);
-    expect(Utils.percentile(array0, 80)).toBe(4.974);
-    expect(Utils.percentile(array0, 85)).toBe(5.131);
-    expect(Utils.percentile(array0, 90)).toBe(5.434);
-    expect(Utils.percentile(array0, 95)).toBe(5.736999999999999);
-    expect(Utils.percentile(array0, 100)).toBe(6.04);
-  });
-
-  it('Verify stdDeviation()', () => {
-    expect(Utils.stdDeviation([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.0256064851429216);
-  });
 });