perf: switch to SMA and SMM in performance statistics
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 25 Aug 2023 10:25:06 +0000 (12:25 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 25 Aug 2023 10:25:06 +0000 (12:25 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/performance/PerformanceStatistics.ts
src/utils/CircularArray.ts
src/utils/Constants.ts
src/utils/StatisticUtils.ts
src/utils/index.ts
test/utils/CircularArray.test.ts
test/utils/StatisticUtils.test.ts

index c5748922bd3dbae68dc4a6c944d6ed7d6e7eb88c..281075c7eb95a0b3ccb3de85183ba9238d1c6cee 100644 (file)
@@ -21,6 +21,7 @@ import {
   Configuration,
   Constants,
   JSONStringifyWithMapSupport,
+  average,
   buildPerformanceStatisticsMessage,
   extractTimeSeriesValues,
   formatDurationSeconds,
@@ -230,9 +231,6 @@ export class PerformanceStatistics {
     );
     this.statistics.statisticsData.get(entryName)!.totalTimeMeasurement =
       (this.statistics.statisticsData.get(entryName)?.totalTimeMeasurement ?? 0) + entry.duration;
-    this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement =
-      this.statistics.statisticsData.get(entryName)!.totalTimeMeasurement! /
-      this.statistics.statisticsData.get(entryName)!.timeMeasurementCount!;
     this.statistics.statisticsData.get(entryName)?.measurementTimeSeries instanceof CircularArray
       ? this.statistics.statisticsData
           .get(entryName)
@@ -242,22 +240,28 @@ export class PerformanceStatistics {
             timestamp: entry.startTime,
             value: entry.duration,
           }));
+    this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement = average(
+      extractTimeSeriesValues(
+        this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!,
+      ),
+    );
     this.statistics.statisticsData.get(entryName)!.medTimeMeasurement = median(
       extractTimeSeriesValues(
-        this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[],
+        this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!,
       ),
     );
     this.statistics.statisticsData.get(entryName)!.ninetyFiveThPercentileTimeMeasurement =
       nthPercentile(
         extractTimeSeriesValues(
-          this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[],
+          this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!,
         ),
         95,
       );
     this.statistics.statisticsData.get(entryName)!.stdDevTimeMeasurement = stdDeviation(
       extractTimeSeriesValues(
-        this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[],
+        this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!,
       ),
+      this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement,
     );
     if (
       Configuration.getConfigurationSection<StorageConfiguration>(
index bd768673605021a31aaf498278f0d50db6a8fa41..4fafe5280a78cd849667f94e12e7e198e16f2dc7 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
-const DEFAULT_CIRCULAR_ARRAY_SIZE = 1024;
+export const DEFAULT_CIRCULAR_ARRAY_SIZE = 1024;
 
 /**
  * Array with a maximum length and shifting items when full.
index 8ab45e63e1293a9e0ccbd662a0f3e23deb0f004c..603793201743a8b2967ccd0c3069d9e15a41be90 100644 (file)
@@ -24,7 +24,7 @@ export class Constants {
   static readonly SEMVER_PATTERN =
     '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$';
 
-  static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 1024;
+  static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 4096;
 
   static readonly DEFAULT_HASH_ALGORITHM = 'sha384';
 
index a2d906eb06c5e06af6b44ac9b8fb607b13a7b69c..3f587caaaf46c753d080b1cccc13fab131dacaba 100644 (file)
@@ -1,5 +1,29 @@
 import { isEmptyArray, isNullOrUndefined } from './Utils';
 
+/**
+ * Computes the average of the given data set.
+ *
+ * @param dataSet - Data set.
+ * @returns The average of the given data set.
+ * @internal
+ */
+export const average = (dataSet: number[]): number => {
+  if (Array.isArray(dataSet) && dataSet.length === 0) {
+    return 0;
+  }
+  if (Array.isArray(dataSet) && dataSet.length === 1) {
+    return dataSet[0];
+  }
+  return dataSet.reduce((accumulator, nb) => accumulator + nb, 0) / dataSet.length;
+};
+
+/**
+ * Computes the median of the given data set.
+ *
+ * @param dataSet - Data set.
+ * @returns The median of the given data set.
+ * @internal
+ */
 export const median = (dataSet: number[]): number => {
   if (isEmptyArray(dataSet)) {
     return 0;
@@ -40,16 +64,11 @@ export const nthPercentile = (dataSet: number[], percentile: number): number =>
   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);
+export const stdDeviation = (dataSet: number[], dataSetAverage?: number): number => {
+  dataSetAverage = dataSetAverage ?? average(dataSet);
+  const geometricDeviation = dataSet.reduce((accumulator, nb) => {
+    const deviation = nb - dataSetAverage!;
+    return accumulator + deviation * deviation;
+  }, 0);
+  return Math.sqrt(geometricDeviation / dataSet.length);
 };
index 1cdd45ee37c41b0b47591f7bf4dc13c6bd16c793..5915ae774afb0bb58f928b7cd7b13cebbb5aff35 100644 (file)
@@ -55,5 +55,5 @@ export {
   sleep,
   validateUUID,
 } from './Utils';
-export { median, nthPercentile, stdDeviation } from './StatisticUtils';
+export { average, median, nthPercentile, stdDeviation } from './StatisticUtils';
 export { logger } from './Logger';
index 18910d8cd32392d811186dccfd430825f26e0349..34839dcb7b90b0dafe2c6837ebd092cdfb3858b7 100644 (file)
@@ -1,6 +1,6 @@
 import { expect } from 'expect';
 
-import { CircularArray } from '../../src/utils/CircularArray';
+import { CircularArray, DEFAULT_CIRCULAR_ARRAY_SIZE } from '../../src/utils/CircularArray';
 
 describe('CircularArray test suite', () => {
   it('Verify that circular array can be instantiated', () => {
@@ -10,7 +10,7 @@ describe('CircularArray test suite', () => {
 
   it('Verify circular array default size at instance creation', () => {
     const circularArray = new CircularArray();
-    expect(circularArray.size).toBe(1024);
+    expect(circularArray.size).toBe(DEFAULT_CIRCULAR_ARRAY_SIZE);
   });
 
   it('Verify that circular array size can be set at instance creation', () => {
index 2910c32df23212ded8217b76a4f0f78ab58e578d..6fb3048873608ae896cfb23ecace504f00f7c7b6 100644 (file)
@@ -1,8 +1,15 @@
 import { expect } from 'expect';
 
-import { median, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils';
+import { average, median, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils';
 
 describe('StatisticUtils test suite', () => {
+  it('Verify average()', () => {
+    expect(average([])).toBe(0);
+    expect(average([0.08])).toBe(0.08);
+    expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.1642857142857146);
+    expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.8533333333333335);
+  });
+
   it('Verify median()', () => {
     expect(median([])).toBe(0);
     expect(median([0.08])).toBe(0.08);