X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Futils%2FPerformanceStatistics.ts;h=8c536f85a669bd8b39f02b801884401631191d6d;hb=e71712800639b0aaebec9af189fdbbaa4f453b92;hp=c2d1527a1a7adcd1803c03e1e2fa18d64e1be860;hpb=a0ba4ced5cf822e9ff2ac192c17d1d954f32d139;p=e-mobility-charging-stations-simulator.git diff --git a/src/utils/PerformanceStatistics.ts b/src/utils/PerformanceStatistics.ts index c2d1527a..8c536f85 100644 --- a/src/utils/PerformanceStatistics.ts +++ b/src/utils/PerformanceStatistics.ts @@ -1,7 +1,9 @@ +// Partial Copyright Jerome Benoit. 2021. All Rights Reserved. + import { CircularArray, DEFAULT_CIRCULAR_ARRAY_SIZE } from './CircularArray'; -import CommandStatistics, { CommandStatisticsData, PerfEntry } from '../types/CommandStatistics'; import { IncomingRequestCommand, RequestCommand } from '../types/ocpp/Requests'; import { PerformanceEntry, PerformanceObserver, performance } from 'perf_hooks'; +import Statistics, { StatisticsData } from '../types/Statistics'; import Configuration from './Configuration'; import { MessageType } from '../types/ocpp/MessageType'; @@ -10,50 +12,59 @@ import logger from './Logger'; export default class PerformanceStatistics { private objId: string; - private commandsStatistics: CommandStatistics; + private performanceObserver: PerformanceObserver; + private statistics: Statistics; + private displayInterval: NodeJS.Timeout; public constructor(objId: string) { - this.initFunctionPerformanceObserver(); this.objId = objId; - this.commandsStatistics = { id: this.objId ? this.objId : 'Object id not specified', commandsStatisticsData: {} }; + this.initializePerformanceObserver(); + this.statistics = { id: this.objId ?? 'Object id not specified', statisticsData: {} }; + } + + public static beginMeasure(id: string): string { + const beginId = 'begin' + id.charAt(0).toUpperCase() + id.slice(1); + performance.mark(beginId); + return beginId; } - public static timedFunction(method: (...optionalParams: any[]) => any): (...optionalParams: any[]) => any { - return performance.timerify(method); + public static endMeasure(name: string, beginId: string): void { + performance.measure(name, beginId); + performance.clearMarks(beginId); } - public addMessage(command: RequestCommand | IncomingRequestCommand, messageType: MessageType): void { + public addRequestStatistic(command: RequestCommand | IncomingRequestCommand, messageType: MessageType): void { switch (messageType) { case MessageType.CALL_MESSAGE: - if (this.commandsStatistics.commandsStatisticsData[command] && this.commandsStatistics.commandsStatisticsData[command].countRequest) { - this.commandsStatistics.commandsStatisticsData[command].countRequest++; + if (this.statistics.statisticsData[command] && this.statistics.statisticsData[command].countRequest) { + this.statistics.statisticsData[command].countRequest++; } else { - this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; - this.commandsStatistics.commandsStatisticsData[command].countRequest = 1; + this.statistics.statisticsData[command] = {} as StatisticsData; + this.statistics.statisticsData[command].countRequest = 1; } break; case MessageType.CALL_RESULT_MESSAGE: - if (this.commandsStatistics.commandsStatisticsData[command]) { - if (this.commandsStatistics.commandsStatisticsData[command].countResponse) { - this.commandsStatistics.commandsStatisticsData[command].countResponse++; + if (this.statistics.statisticsData[command]) { + if (this.statistics.statisticsData[command].countResponse) { + this.statistics.statisticsData[command].countResponse++; } else { - this.commandsStatistics.commandsStatisticsData[command].countResponse = 1; + this.statistics.statisticsData[command].countResponse = 1; } } else { - this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; - this.commandsStatistics.commandsStatisticsData[command].countResponse = 1; + this.statistics.statisticsData[command] = {} as StatisticsData; + this.statistics.statisticsData[command].countResponse = 1; } break; case MessageType.CALL_ERROR_MESSAGE: - if (this.commandsStatistics.commandsStatisticsData[command]) { - if (this.commandsStatistics.commandsStatisticsData[command].countError) { - this.commandsStatistics.commandsStatisticsData[command].countError++; + if (this.statistics.statisticsData[command]) { + if (this.statistics.statisticsData[command].countError) { + this.statistics.statisticsData[command].countError++; } else { - this.commandsStatistics.commandsStatisticsData[command].countError = 1; + this.statistics.statisticsData[command].countError = 1; } } else { - this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; - this.commandsStatistics.commandsStatisticsData[command].countError = 1; + this.statistics.statisticsData[command] = {} as StatisticsData; + this.statistics.statisticsData[command].countError = 1; } break; default: @@ -62,39 +73,43 @@ export default class PerformanceStatistics { } } - public logPerformance(entry: PerformanceEntry): void { - this.addPerformanceTimer(entry.name, entry.duration); - const perfEntry: PerfEntry = { - name: entry.name, - entryType: entry.entryType, - startTime: entry.startTime, - duration: entry.duration - }; - logger.debug(`${this.logPrefix()} method or function '${entry.name}' performance entry: %j`, perfEntry); + public start(): void { + this.startDisplayInterval(); + } + + public stop(): void { + if (this.displayInterval) { + clearInterval(this.displayInterval); + } + performance.clearMarks(); + this.performanceObserver?.disconnect(); } - public start(): void { - this.displayInterval(); + public restart(): void { + this.stop(); + this.start(); } - private initFunctionPerformanceObserver(): void { - const performanceObserver = new PerformanceObserver((list, observer) => { - this.logPerformance(list.getEntries()[0]); - observer.disconnect(); + private initializePerformanceObserver(): void { + this.performanceObserver = new PerformanceObserver((list) => { + this.addPerformanceEntryToStatistics(list.getEntries()[0]); + logger.debug(`${this.logPrefix()} '${list.getEntries()[0].name}' performance entry: %j`, list.getEntries()[0]); }); - performanceObserver.observe({ entryTypes: ['function'] }); + this.performanceObserver.observe({ entryTypes: ['measure'] }); } - private display(): void { - logger.info(this.logPrefix() + ' %j', this.commandsStatistics); + private logStatistics(): void { + logger.info(this.logPrefix() + ' %j', this.statistics); } - private displayInterval(): void { + private startDisplayInterval(): void { if (Configuration.getStatisticsDisplayInterval() > 0) { - setInterval(() => { - this.display(); + this.displayInterval = setInterval(() => { + this.logStatistics(); }, Configuration.getStatisticsDisplayInterval() * 1000); logger.info(this.logPrefix() + ' displayed every ' + Utils.secondsToHHMMSS(Configuration.getStatisticsDisplayInterval())); + } else { + logger.info(this.logPrefix() + ' display interval is set to ' + Configuration.getStatisticsDisplayInterval().toString() + '. Not displaying statistics'); } } @@ -102,7 +117,7 @@ export default class PerformanceStatistics { if (Array.isArray(dataSet) && dataSet.length === 1) { return dataSet[0]; } - const sortedDataSet = dataSet.slice().sort(); + const sortedDataSet = dataSet.slice().sort((a, b) => (a - b)); const middleIndex = Math.floor(sortedDataSet.length / 2); if (sortedDataSet.length % 2) { return sortedDataSet[middleIndex / 2]; @@ -110,6 +125,28 @@ export default class PerformanceStatistics { 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) { @@ -124,29 +161,28 @@ export default class PerformanceStatistics { return Math.sqrt(totalGeometricDeviation / dataSet.length); } - private addPerformanceTimer(name: string, duration: number): void { + private addPerformanceEntryToStatistics(entry: PerformanceEntry): void { + let entryName = entry.name; // Rename entry name - const MAP_NAME = { - startATGTransaction: 'StartATGTransaction', - stopATGTransaction: 'StartATGTransaction' - }; - if (MAP_NAME[name]) { - name = MAP_NAME[name] as string; + const MAP_NAME: Record = {}; + if (MAP_NAME[entryName]) { + entryName = MAP_NAME[entryName]; } // Initialize command statistics - if (!this.commandsStatistics.commandsStatisticsData[name]) { - this.commandsStatistics.commandsStatisticsData[name] = {} as CommandStatisticsData; + if (!this.statistics.statisticsData[entryName]) { + this.statistics.statisticsData[entryName] = {} as StatisticsData; } - // Update current statistics timers - this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement + 1 : 1; - this.commandsStatistics.commandsStatisticsData[name].currentTimeMeasurement = duration; - this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement > duration ? duration : this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement) : duration; - this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement < duration ? duration : this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement) : duration; - this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement + duration : duration; - this.commandsStatistics.commandsStatisticsData[name].avgTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement / this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement; - Array.isArray(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries) ? this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries.push(duration) : this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries = new CircularArray(DEFAULT_CIRCULAR_ARRAY_SIZE, duration); - this.commandsStatistics.commandsStatisticsData[name].medTimeMeasurement = this.median(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries); - this.commandsStatistics.commandsStatisticsData[name].stdDevTimeMeasurement = this.stdDeviation(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries); + // Update current statistics + this.statistics.statisticsData[entryName].countTimeMeasurement = this.statistics.statisticsData[entryName].countTimeMeasurement ? this.statistics.statisticsData[entryName].countTimeMeasurement + 1 : 1; + this.statistics.statisticsData[entryName].currentTimeMeasurement = entry.duration; + this.statistics.statisticsData[entryName].minTimeMeasurement = this.statistics.statisticsData[entryName].minTimeMeasurement ? (this.statistics.statisticsData[entryName].minTimeMeasurement > entry.duration ? entry.duration : this.statistics.statisticsData[entryName].minTimeMeasurement) : entry.duration; + this.statistics.statisticsData[entryName].maxTimeMeasurement = this.statistics.statisticsData[entryName].maxTimeMeasurement ? (this.statistics.statisticsData[entryName].maxTimeMeasurement < entry.duration ? entry.duration : this.statistics.statisticsData[entryName].maxTimeMeasurement) : entry.duration; + this.statistics.statisticsData[entryName].totalTimeMeasurement = this.statistics.statisticsData[entryName].totalTimeMeasurement ? this.statistics.statisticsData[entryName].totalTimeMeasurement + entry.duration : entry.duration; + this.statistics.statisticsData[entryName].avgTimeMeasurement = this.statistics.statisticsData[entryName].totalTimeMeasurement / this.statistics.statisticsData[entryName].countTimeMeasurement; + Array.isArray(this.statistics.statisticsData[entryName].timeMeasurementSeries) ? this.statistics.statisticsData[entryName].timeMeasurementSeries.push(entry.duration) : this.statistics.statisticsData[entryName].timeMeasurementSeries = new CircularArray(DEFAULT_CIRCULAR_ARRAY_SIZE, entry.duration); + this.statistics.statisticsData[entryName].medTimeMeasurement = this.median(this.statistics.statisticsData[entryName].timeMeasurementSeries); + this.statistics.statisticsData[entryName].ninetyFiveThPercentileTimeMeasurement = this.percentile(this.statistics.statisticsData[entryName].timeMeasurementSeries, 95); + this.statistics.statisticsData[entryName].stdDevTimeMeasurement = this.stdDeviation(this.statistics.statisticsData[entryName].timeMeasurementSeries); } private logPrefix(): string {