refactor: split Utils static methods class into functions
[e-mobility-charging-stations-simulator.git] / src / performance / PerformanceStatistics.ts
index 8cf0e1ef5d9f9541913f9c72cbf6a7310dee19cf..2733fd7430140a16a95166f1c40efae7287dcc97 100644 (file)
@@ -4,7 +4,6 @@ import { type PerformanceEntry, PerformanceObserver, performance } from 'node:pe
 import type { URL } from 'node:url';
 import { parentPort } from 'node:worker_threads';
 
-import { MessageChannelUtils } from '../charging-station';
 import {
   type IncomingRequestCommand,
   MessageType,
@@ -12,7 +11,20 @@ import {
   type Statistics,
   type TimeSeries,
 } from '../types';
-import { CircularArray, Configuration, Constants, Utils, logger } from '../utils';
+import {
+  CircularArray,
+  Configuration,
+  Constants,
+  JSONStringifyWithMapSupport,
+  buildPerformanceStatisticsMessage,
+  formatDurationSeconds,
+  generateUUID,
+  logPrefix,
+  logger,
+  median,
+  nthPercentile,
+  stdDeviation,
+} from '../utils';
 
 export class PerformanceStatistics {
   private static readonly instances: Map<string, PerformanceStatistics> = new Map<
@@ -51,7 +63,7 @@ export class PerformanceStatistics {
   }
 
   public static beginMeasure(id: string): string {
-    const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${Utils.generateUUID()}`;
+    const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}`;
     performance.mark(markId);
     return markId;
   }
@@ -72,7 +84,7 @@ export class PerformanceStatistics {
           this.statistics.statisticsData.has(command) &&
           this.statistics.statisticsData.get(command)?.countRequest
         ) {
-          this.statistics.statisticsData.get(command).countRequest++;
+          ++this.statistics.statisticsData.get(command).countRequest;
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
@@ -85,7 +97,7 @@ export class PerformanceStatistics {
           this.statistics.statisticsData.has(command) &&
           this.statistics.statisticsData.get(command)?.countResponse
         ) {
-          this.statistics.statisticsData.get(command).countResponse++;
+          ++this.statistics.statisticsData.get(command).countResponse;
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
@@ -98,7 +110,7 @@ export class PerformanceStatistics {
           this.statistics.statisticsData.has(command) &&
           this.statistics.statisticsData.get(command)?.countError
         ) {
-          this.statistics.statisticsData.get(command).countError++;
+          ++this.statistics.statisticsData.get(command).countError;
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
@@ -125,10 +137,7 @@ export class PerformanceStatistics {
   }
 
   public stop(): void {
-    if (this.displayInterval) {
-      clearInterval(this.displayInterval);
-      delete this.displayInterval;
-    }
+    this.stopLogStatisticsInterval();
     performance.clearMarks();
     performance.clearMeasures();
     this.performanceObserver?.disconnect();
@@ -154,79 +163,37 @@ export class PerformanceStatistics {
   private logStatistics(): void {
     logger.info(`${this.logPrefix()}`, {
       ...this.statistics,
-      statisticsData: Utils.JSONStringifyWithMapSupport(this.statistics.statisticsData),
+      statisticsData: JSONStringifyWithMapSupport(this.statistics.statisticsData),
     });
   }
 
   private startLogStatisticsInterval(): void {
-    if (Configuration.getLogStatisticsInterval() > 0 && !this.displayInterval) {
+    const logStatisticsInterval = Configuration.getLog().enabled
+      ? Configuration.getLog().statisticsInterval
+      : 0;
+    if (logStatisticsInterval > 0 && !this.displayInterval) {
       this.displayInterval = setInterval(() => {
         this.logStatistics();
-      }, Configuration.getLogStatisticsInterval() * 1000);
+      }, logStatisticsInterval * 1000);
       logger.info(
-        `${this.logPrefix()} logged every ${Utils.formatDurationSeconds(
-          Configuration.getLogStatisticsInterval()
-        )}`
+        `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}`
       );
     } else if (this.displayInterval) {
       logger.info(
-        `${this.logPrefix()} already logged every ${Utils.formatDurationSeconds(
-          Configuration.getLogStatisticsInterval()
-        )}`
+        `${this.logPrefix()} already logged every ${formatDurationSeconds(logStatisticsInterval)}`
       );
-    } else {
+    } else if (Configuration.getLog().enabled) {
       logger.info(
-        `${this.logPrefix()} log interval is set to ${Configuration.getLogStatisticsInterval()?.toString()}. Not logging statistics`
+        `${this.logPrefix()} log interval is set to ${logStatisticsInterval?.toString()}. Not logging statistics`
       );
     }
   }
 
-  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) {
-      return sortedDataSet[middleIndex / 2];
-    }
-    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.round(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;
+  private stopLogStatisticsInterval(): void {
+    if (this.displayInterval) {
+      clearInterval(this.displayInterval);
+      delete this.displayInterval;
     }
-    return Math.sqrt(totalGeometricDeviation / dataSet.length);
   }
 
   private addPerformanceEntryToStatistics(entry: PerformanceEntry): void {
@@ -238,26 +205,18 @@ export class PerformanceStatistics {
     // Update current statistics
     this.statistics.updatedAt = new Date();
     this.statistics.statisticsData.get(entryName).countTimeMeasurement =
-      this.statistics.statisticsData.get(entryName)?.countTimeMeasurement
-        ? this.statistics.statisticsData.get(entryName).countTimeMeasurement + 1
-        : 1;
+      (this.statistics.statisticsData.get(entryName)?.countTimeMeasurement ?? 0) + 1;
     this.statistics.statisticsData.get(entryName).currentTimeMeasurement = entry.duration;
-    this.statistics.statisticsData.get(entryName).minTimeMeasurement =
-      this.statistics.statisticsData.get(entryName)?.minTimeMeasurement
-        ? this.statistics.statisticsData.get(entryName).minTimeMeasurement > entry.duration
-          ? entry.duration
-          : this.statistics.statisticsData.get(entryName).minTimeMeasurement
-        : entry.duration;
-    this.statistics.statisticsData.get(entryName).maxTimeMeasurement =
-      this.statistics.statisticsData.get(entryName)?.maxTimeMeasurement
-        ? this.statistics.statisticsData.get(entryName).maxTimeMeasurement < entry.duration
-          ? entry.duration
-          : this.statistics.statisticsData.get(entryName).maxTimeMeasurement
-        : entry.duration;
+    this.statistics.statisticsData.get(entryName).minTimeMeasurement = Math.min(
+      entry.duration,
+      this.statistics.statisticsData.get(entryName)?.minTimeMeasurement ?? Infinity
+    );
+    this.statistics.statisticsData.get(entryName).maxTimeMeasurement = Math.max(
+      entry.duration,
+      this.statistics.statisticsData.get(entryName)?.maxTimeMeasurement ?? -Infinity
+    );
     this.statistics.statisticsData.get(entryName).totalTimeMeasurement =
-      this.statistics.statisticsData.get(entryName)?.totalTimeMeasurement
-        ? this.statistics.statisticsData.get(entryName).totalTimeMeasurement + entry.duration
-        : entry.duration;
+      (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).countTimeMeasurement;
@@ -270,27 +229,25 @@ export class PerformanceStatistics {
             timestamp: entry.startTime,
             value: entry.duration,
           }));
-    this.statistics.statisticsData.get(entryName).medTimeMeasurement = this.median(
+    this.statistics.statisticsData.get(entryName).medTimeMeasurement = median(
       this.extractTimeSeriesValues(
         this.statistics.statisticsData.get(entryName).timeMeasurementSeries
       )
     );
     this.statistics.statisticsData.get(entryName).ninetyFiveThPercentileTimeMeasurement =
-      this.percentile(
+      nthPercentile(
         this.extractTimeSeriesValues(
           this.statistics.statisticsData.get(entryName).timeMeasurementSeries
         ),
         95
       );
-    this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = this.stdDeviation(
+    this.statistics.statisticsData.get(entryName).stdDevTimeMeasurement = stdDeviation(
       this.extractTimeSeriesValues(
         this.statistics.statisticsData.get(entryName).timeMeasurementSeries
       )
     );
     if (Configuration.getPerformanceStorage().enabled) {
-      parentPort?.postMessage(
-        MessageChannelUtils.buildPerformanceStatisticsMessage(this.statistics)
-      );
+      parentPort?.postMessage(buildPerformanceStatisticsMessage(this.statistics));
     }
   }
 
@@ -299,6 +256,6 @@ export class PerformanceStatistics {
   }
 
   private logPrefix = (): string => {
-    return Utils.logPrefix(` ${this.objName} | Performance statistics`);
+    return logPrefix(` ${this.objName} | Performance statistics`);
   };
 }