X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fperformance%2FPerformanceStatistics.ts;h=265cb6c963918b1a3970a95e5822742f3202c0d4;hb=8a4f882a2486682b72195ad978ec1d81604b452b;hp=b7bc5d27f894c27c9984571e90a4b07187ed8fed;hpb=d71b025f891fc5f0dd3ef45d6bf371eb6d5900bd;p=e-mobility-charging-stations-simulator.git diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index b7bc5d27..265cb6c9 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -1,269 +1,325 @@ -// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. -import { type PerformanceEntry, PerformanceObserver, performance } from 'node:perf_hooks'; -import type { URL } from 'node:url'; -import { parentPort } from 'node:worker_threads'; +import { performance, type PerformanceEntry, PerformanceObserver } from 'node:perf_hooks' +import type { URL } from 'node:url' +import { parentPort } from 'node:worker_threads' -import { secondsToMilliseconds } from 'date-fns'; +import { secondsToMilliseconds } from 'date-fns' +import { is, mean, median } from 'rambda' +import { BaseError } from '../exception/index.js' import { ConfigurationSection, type IncomingRequestCommand, type LogConfiguration, + MapStringifyFormat, MessageType, type RequestCommand, type Statistics, + type StatisticsData, type StorageConfiguration, - type TimestampedData, -} from '../types'; + type TimestampedData +} from '../types/index.js' import { + buildPerformanceStatisticsMessage, CircularArray, Configuration, Constants, - JSONStringifyWithMapSupport, - average, - buildPerformanceStatisticsMessage, extractTimeSeriesValues, formatDurationSeconds, generateUUID, - logPrefix, + JSONStringify, logger, + logPrefix, max, - median, min, nthPercentile, - stdDeviation, -} from '../utils'; + stdDeviation +} from '../utils/index.js' export class PerformanceStatistics { private static readonly instances: Map = new Map< - string, - PerformanceStatistics - >(); + string, + PerformanceStatistics + >() - private readonly objId: string; - private readonly objName: string; - private performanceObserver!: PerformanceObserver; - private readonly statistics: Statistics; - private displayInterval?: NodeJS.Timeout; + private readonly objId: string | undefined + private readonly objName: string | undefined + private performanceObserver!: PerformanceObserver + private readonly statistics: Statistics + private displayInterval?: NodeJS.Timeout - private constructor(objId: string, objName: string, uri: URL) { - this.objId = objId; - this.objName = objName; - this.initializePerformanceObserver(); + private constructor (objId: string, objName: string, uri: URL) { + this.objId = objId + this.objName = objName + this.initializePerformanceObserver() this.statistics = { - id: this.objId ?? 'Object id not specified', - name: this.objName ?? 'Object name not specified', + id: this.objId, + name: this.objName, uri: uri.toString(), createdAt: new Date(), - statisticsData: new Map(), - }; + statisticsData: new Map() + } } - public static getInstance( - objId: string, - objName: string, - uri: URL, + public static getInstance ( + objId: string | undefined, + objName: string | undefined, + uri: URL | undefined ): PerformanceStatistics | undefined { + if (objId == null) { + const errMsg = 'Cannot get performance statistics instance without specifying object id' + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) + throw new BaseError(errMsg) + } + if (objName == null) { + const errMsg = 'Cannot get performance statistics instance without specifying object name' + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) + throw new BaseError(errMsg) + } + if (uri == null) { + const errMsg = 'Cannot get performance statistics instance without specifying object uri' + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) + throw new BaseError(errMsg) + } if (!PerformanceStatistics.instances.has(objId)) { - PerformanceStatistics.instances.set(objId, new PerformanceStatistics(objId, objName, uri)); + PerformanceStatistics.instances.set(objId, new PerformanceStatistics(objId, objName, uri)) } - return PerformanceStatistics.instances.get(objId); + return PerformanceStatistics.instances.get(objId) } - public static beginMeasure(id: string): string { - const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}`; - performance.mark(markId); - return markId; + public static deleteInstance (objId: string | undefined): boolean { + if (objId == null) { + const errMsg = 'Cannot delete performance statistics instance without specifying object id' + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) + throw new BaseError(errMsg) + } + return PerformanceStatistics.instances.delete(objId) + } + + public static beginMeasure (id: string): string { + const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}` + performance.mark(markId) + return markId } - public static endMeasure(name: string, markId: string): void { - performance.measure(name, markId); - performance.clearMarks(markId); - performance.clearMeasures(name); + public static endMeasure (name: string, markId: string): void { + try { + performance.measure(name, markId) + } catch (error) { + if (is(Error, error) && error.message.includes('performance mark has not been set')) { + /* Ignore */ + } else { + throw error + } + } + performance.clearMarks(markId) + performance.clearMeasures(name) } - public addRequestStatistic( + public addRequestStatistic ( command: RequestCommand | IncomingRequestCommand, - messageType: MessageType, + messageType: MessageType ): void { switch (messageType) { case MessageType.CALL_MESSAGE: if ( this.statistics.statisticsData.has(command) && - this.statistics.statisticsData.get(command)?.requestCount + this.statistics.statisticsData.get(command)?.requestCount != null ) { - ++this.statistics.statisticsData.get(command)!.requestCount!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ++this.statistics.statisticsData.get(command)!.requestCount! } else { this.statistics.statisticsData.set(command, { ...this.statistics.statisticsData.get(command), - requestCount: 1, - }); + requestCount: 1 + }) } - break; + break case MessageType.CALL_RESULT_MESSAGE: if ( this.statistics.statisticsData.has(command) && - this.statistics.statisticsData.get(command)?.responseCount + this.statistics.statisticsData.get(command)?.responseCount != null ) { - ++this.statistics.statisticsData.get(command)!.responseCount!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ++this.statistics.statisticsData.get(command)!.responseCount! } else { this.statistics.statisticsData.set(command, { ...this.statistics.statisticsData.get(command), - responseCount: 1, - }); + responseCount: 1 + }) } - break; + break case MessageType.CALL_ERROR_MESSAGE: if ( this.statistics.statisticsData.has(command) && - this.statistics.statisticsData.get(command)?.errorCount + this.statistics.statisticsData.get(command)?.errorCount != null ) { - ++this.statistics.statisticsData.get(command)!.errorCount!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ++this.statistics.statisticsData.get(command)!.errorCount! } else { this.statistics.statisticsData.set(command, { ...this.statistics.statisticsData.get(command), - errorCount: 1, - }); + errorCount: 1 + }) } - break; + break default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - logger.error(`${this.logPrefix()} wrong message type ${messageType}`); - break; + logger.error(`${this.logPrefix()} wrong message type ${messageType}`) + break } } - public start(): void { - this.startLogStatisticsInterval(); + public start (): void { + this.startLogStatisticsInterval() const performanceStorageConfiguration = Configuration.getConfigurationSection( - ConfigurationSection.performanceStorage, - ); - if (performanceStorageConfiguration.enabled) { + ConfigurationSection.performanceStorage + ) + if (performanceStorageConfiguration.enabled === true) { logger.info( `${this.logPrefix()} storage enabled: type ${performanceStorageConfiguration.type}, uri: ${ performanceStorageConfiguration.uri - }`, - ); + }` + ) } } - public stop(): void { - this.stopLogStatisticsInterval(); - performance.clearMarks(); - performance.clearMeasures(); - this.performanceObserver?.disconnect(); + public stop (): void { + this.stopLogStatisticsInterval() + performance.clearMarks() + performance.clearMeasures() + this.performanceObserver.disconnect() } - public restart(): void { - this.stop(); - this.start(); + public restart (): void { + this.stop() + this.start() } - private initializePerformanceObserver(): void { - this.performanceObserver = new PerformanceObserver((performanceObserverList) => { - const lastPerformanceEntry = performanceObserverList.getEntries()[0]; + private initializePerformanceObserver (): void { + this.performanceObserver = new PerformanceObserver(performanceObserverList => { + const lastPerformanceEntry = performanceObserverList.getEntries()[0] // logger.debug( // `${this.logPrefix()} '${lastPerformanceEntry.name}' performance entry: %j`, - // lastPerformanceEntry, - // ); - this.addPerformanceEntryToStatistics(lastPerformanceEntry); - }); - this.performanceObserver.observe({ entryTypes: ['measure'] }); + // lastPerformanceEntry + // ) + this.addPerformanceEntryToStatistics(lastPerformanceEntry) + }) + this.performanceObserver.observe({ entryTypes: ['measure'] }) } - private logStatistics(): void { - logger.info(`${this.logPrefix()}`, { + private logStatistics (): void { + logger.info(this.logPrefix(), { ...this.statistics, - statisticsData: JSONStringifyWithMapSupport(this.statistics.statisticsData), - }); + statisticsData: JSON.parse( + JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object) + ) as Map + }) } - private startLogStatisticsInterval(): void { + private startLogStatisticsInterval (): void { const logConfiguration = Configuration.getConfigurationSection( - ConfigurationSection.log, - ); - const logStatisticsInterval = logConfiguration.enabled - ? logConfiguration.statisticsInterval! - : 0; - if (logStatisticsInterval > 0 && !this.displayInterval) { + ConfigurationSection.log + ) + const logStatisticsInterval = + logConfiguration.enabled === true + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + logConfiguration.statisticsInterval! + : 0 + if (logStatisticsInterval > 0 && this.displayInterval == null) { this.displayInterval = setInterval(() => { - this.logStatistics(); - }, secondsToMilliseconds(logStatisticsInterval)); + this.logStatistics() + }, secondsToMilliseconds(logStatisticsInterval)) logger.info( - `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}`, - ); - } else if (this.displayInterval) { + `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}` + ) + } else if (this.displayInterval != null) { logger.info( - `${this.logPrefix()} already logged every ${formatDurationSeconds(logStatisticsInterval)}`, - ); - } else if (logConfiguration.enabled) { + `${this.logPrefix()} already logged every ${formatDurationSeconds(logStatisticsInterval)}` + ) + } else if (logConfiguration.enabled === true) { logger.info( - `${this.logPrefix()} log interval is set to ${logStatisticsInterval}. Not logging statistics`, - ); + `${this.logPrefix()} log interval is set to ${logStatisticsInterval}. Not logging statistics` + ) } } - private stopLogStatisticsInterval(): void { - if (this.displayInterval) { - clearInterval(this.displayInterval); - delete this.displayInterval; + private stopLogStatisticsInterval (): void { + if (this.displayInterval != null) { + clearInterval(this.displayInterval) + delete this.displayInterval } } - private addPerformanceEntryToStatistics(entry: PerformanceEntry): void { + private addPerformanceEntryToStatistics (entry: PerformanceEntry): void { // Initialize command statistics if (!this.statistics.statisticsData.has(entry.name)) { - this.statistics.statisticsData.set(entry.name, {}); + this.statistics.statisticsData.set(entry.name, {}) } // Update current statistics + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.timeMeasurementCount = - (this.statistics.statisticsData.get(entry.name)?.timeMeasurementCount ?? 0) + 1; - this.statistics.statisticsData.get(entry.name)!.currentTimeMeasurement = entry.duration; + (this.statistics.statisticsData.get(entry.name)?.timeMeasurementCount ?? 0) + 1 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.statistics.statisticsData.get(entry.name)!.currentTimeMeasurement = entry.duration + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.minTimeMeasurement = min( entry.duration, - this.statistics.statisticsData.get(entry.name)?.minTimeMeasurement ?? Infinity, - ); + this.statistics.statisticsData.get(entry.name)?.minTimeMeasurement ?? Infinity + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.maxTimeMeasurement = max( entry.duration, - this.statistics.statisticsData.get(entry.name)?.maxTimeMeasurement ?? -Infinity, - ); + this.statistics.statisticsData.get(entry.name)?.maxTimeMeasurement ?? -Infinity + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.totalTimeMeasurement = - (this.statistics.statisticsData.get(entry.name)?.totalTimeMeasurement ?? 0) + entry.duration; + (this.statistics.statisticsData.get(entry.name)?.totalTimeMeasurement ?? 0) + entry.duration this.statistics.statisticsData.get(entry.name)?.measurementTimeSeries instanceof CircularArray ? this.statistics.statisticsData - .get(entry.name) - ?.measurementTimeSeries?.push({ timestamp: entry.startTime, value: entry.duration }) - : (this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries = + .get(entry.name) + ?.measurementTimeSeries?.push({ timestamp: entry.startTime, value: entry.duration }) + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries = new CircularArray(Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY, { timestamp: entry.startTime, - value: entry.duration, - })); + value: entry.duration + })) const timeMeasurementValues = extractTimeSeriesValues( - this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries!, - ); - this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement = - average(timeMeasurementValues); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries! + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement = mean(timeMeasurementValues) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.medTimeMeasurement = - median(timeMeasurementValues); + median(timeMeasurementValues) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.ninetyFiveThPercentileTimeMeasurement = - nthPercentile(timeMeasurementValues, 95); + nthPercentile(timeMeasurementValues, 95) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.statistics.statisticsData.get(entry.name)!.stdDevTimeMeasurement = stdDeviation( timeMeasurementValues, - this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement, - ); - this.statistics.updatedAt = new Date(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement + ) + this.statistics.updatedAt = new Date() if ( Configuration.getConfigurationSection( - ConfigurationSection.performanceStorage, - ).enabled + ConfigurationSection.performanceStorage + ).enabled === true ) { - parentPort?.postMessage(buildPerformanceStatisticsMessage(this.statistics)); + parentPort?.postMessage(buildPerformanceStatisticsMessage(this.statistics)) } } - private logPrefix = (): string => { - return logPrefix(` ${this.objName} | Performance statistics`); - }; + private static readonly logPrefix = (): string => { + return logPrefix(' Performance statistics') + } + + private readonly logPrefix = (): string => { + return logPrefix(` ${this.objName} | Performance statistics`) + } }