Commit | Line | Data |
---|---|---|
ef72d3f5 | 1 | import { CircularArray, DEFAULT_CIRCULAR_ARRAY_SIZE } from './CircularArray'; |
6bf6769e | 2 | import CommandStatistics, { CommandStatisticsData, PerfEntry } from '../types/CommandStatistics'; |
c0560973 | 3 | import { IncomingRequestCommand, RequestCommand } from '../types/ocpp/Requests'; |
57939a9d | 4 | import { PerformanceEntry, PerformanceObserver, performance } from 'perf_hooks'; |
63b48f77 | 5 | |
6af9012e | 6 | import Configuration from './Configuration'; |
d2a64eb5 | 7 | import { MessageType } from '../types/ocpp/MessageType'; |
6af9012e JB |
8 | import Utils from './Utils'; |
9 | import logger from './Logger'; | |
7dde0b73 | 10 | |
54b1efe0 | 11 | export default class PerformanceStatistics { |
418106c8 | 12 | private objId: string; |
ad2f27c3 | 13 | private commandsStatistics: CommandStatistics; |
560bcf5b | 14 | |
c0560973 | 15 | public constructor(objId: string) { |
a0ba4ced | 16 | this.initFunctionPerformanceObserver(); |
c0560973 | 17 | this.objId = objId; |
8434025b | 18 | this.commandsStatistics = { id: this.objId ? this.objId : 'Object id not specified', commandsStatisticsData: {} }; |
560bcf5b JB |
19 | } |
20 | ||
e9017bfc | 21 | public static timedFunction(method: (...optionalParams: any[]) => any): (...optionalParams: any[]) => any { |
57939a9d JB |
22 | return performance.timerify(method); |
23 | } | |
24 | ||
418106c8 | 25 | public addMessage(command: RequestCommand | IncomingRequestCommand, messageType: MessageType): void { |
7f134aca | 26 | switch (messageType) { |
d2a64eb5 | 27 | case MessageType.CALL_MESSAGE: |
418106c8 JB |
28 | if (this.commandsStatistics.commandsStatisticsData[command] && this.commandsStatistics.commandsStatisticsData[command].countRequest) { |
29 | this.commandsStatistics.commandsStatisticsData[command].countRequest++; | |
7dde0b73 | 30 | } else { |
418106c8 JB |
31 | this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; |
32 | this.commandsStatistics.commandsStatisticsData[command].countRequest = 1; | |
7f134aca JB |
33 | } |
34 | break; | |
d2a64eb5 | 35 | case MessageType.CALL_RESULT_MESSAGE: |
418106c8 JB |
36 | if (this.commandsStatistics.commandsStatisticsData[command]) { |
37 | if (this.commandsStatistics.commandsStatisticsData[command].countResponse) { | |
38 | this.commandsStatistics.commandsStatisticsData[command].countResponse++; | |
7f134aca | 39 | } else { |
418106c8 | 40 | this.commandsStatistics.commandsStatisticsData[command].countResponse = 1; |
7f134aca JB |
41 | } |
42 | } else { | |
418106c8 JB |
43 | this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; |
44 | this.commandsStatistics.commandsStatisticsData[command].countResponse = 1; | |
7dde0b73 | 45 | } |
7f134aca | 46 | break; |
d2a64eb5 | 47 | case MessageType.CALL_ERROR_MESSAGE: |
418106c8 JB |
48 | if (this.commandsStatistics.commandsStatisticsData[command]) { |
49 | if (this.commandsStatistics.commandsStatisticsData[command].countError) { | |
50 | this.commandsStatistics.commandsStatisticsData[command].countError++; | |
7f134aca | 51 | } else { |
418106c8 | 52 | this.commandsStatistics.commandsStatisticsData[command].countError = 1; |
7f134aca JB |
53 | } |
54 | } else { | |
418106c8 JB |
55 | this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData; |
56 | this.commandsStatistics.commandsStatisticsData[command].countError = 1; | |
7f134aca JB |
57 | } |
58 | break; | |
59 | default: | |
54b1efe0 | 60 | logger.error(`${this.logPrefix()} wrong message type ${messageType}`); |
7f134aca | 61 | break; |
7dde0b73 JB |
62 | } |
63 | } | |
64 | ||
e9017bfc JB |
65 | public logPerformance(entry: PerformanceEntry): void { |
66 | this.addPerformanceTimer(entry.name, entry.duration); | |
9fbc92f7 JB |
67 | const perfEntry: PerfEntry = { |
68 | name: entry.name, | |
69 | entryType: entry.entryType, | |
70 | startTime: entry.startTime, | |
71 | duration: entry.duration | |
57939a9d | 72 | }; |
a0ba4ced | 73 | logger.debug(`${this.logPrefix()} method or function '${entry.name}' performance entry: %j`, perfEntry); |
7dde0b73 JB |
74 | } |
75 | ||
418106c8 | 76 | public start(): void { |
c0560973 | 77 | this.displayInterval(); |
136c90ba JB |
78 | } |
79 | ||
a0ba4ced JB |
80 | private initFunctionPerformanceObserver(): void { |
81 | const performanceObserver = new PerformanceObserver((list, observer) => { | |
82 | this.logPerformance(list.getEntries()[0]); | |
83 | observer.disconnect(); | |
84 | }); | |
85 | performanceObserver.observe({ entryTypes: ['function'] }); | |
86 | } | |
87 | ||
c0560973 JB |
88 | private display(): void { |
89 | logger.info(this.logPrefix() + ' %j', this.commandsStatistics); | |
7dde0b73 JB |
90 | } |
91 | ||
c0560973 | 92 | private displayInterval(): void { |
c6b89400 | 93 | if (Configuration.getStatisticsDisplayInterval() > 0) { |
7dde0b73 | 94 | setInterval(() => { |
c0560973 | 95 | this.display(); |
7dde0b73 | 96 | }, Configuration.getStatisticsDisplayInterval() * 1000); |
c0560973 | 97 | logger.info(this.logPrefix() + ' displayed every ' + Utils.secondsToHHMMSS(Configuration.getStatisticsDisplayInterval())); |
7dde0b73 JB |
98 | } |
99 | } | |
100 | ||
6bf6769e JB |
101 | private median(dataSet: number[]): number { |
102 | if (Array.isArray(dataSet) && dataSet.length === 1) { | |
103 | return dataSet[0]; | |
104 | } | |
105 | const sortedDataSet = dataSet.slice().sort(); | |
106 | const middleIndex = Math.floor(sortedDataSet.length / 2); | |
107 | if (sortedDataSet.length % 2) { | |
108 | return sortedDataSet[middleIndex / 2]; | |
109 | } | |
110 | return (sortedDataSet[(middleIndex - 1)] + sortedDataSet[middleIndex]) / 2; | |
111 | } | |
112 | ||
aeada1fa JB |
113 | private stdDeviation(dataSet: number[]): number { |
114 | let totalDataSet = 0; | |
115 | for (const data of dataSet) { | |
116 | totalDataSet += data; | |
117 | } | |
118 | const dataSetMean = totalDataSet / dataSet.length; | |
119 | let totalGeometricDeviation = 0; | |
120 | for (const data of dataSet) { | |
121 | const deviation = data - dataSetMean; | |
122 | totalGeometricDeviation += deviation * deviation; | |
123 | } | |
124 | return Math.sqrt(totalGeometricDeviation / dataSet.length); | |
125 | } | |
126 | ||
e9017bfc JB |
127 | private addPerformanceTimer(name: string, duration: number): void { |
128 | // Rename entry name | |
129 | const MAP_NAME = { | |
130 | startATGTransaction: 'StartATGTransaction', | |
131 | stopATGTransaction: 'StartATGTransaction' | |
7ec46a9a | 132 | }; |
e9017bfc JB |
133 | if (MAP_NAME[name]) { |
134 | name = MAP_NAME[name] as string; | |
7ec46a9a JB |
135 | } |
136 | // Initialize command statistics | |
e9017bfc JB |
137 | if (!this.commandsStatistics.commandsStatisticsData[name]) { |
138 | this.commandsStatistics.commandsStatisticsData[name] = {} as CommandStatisticsData; | |
7ec46a9a JB |
139 | } |
140 | // Update current statistics timers | |
e9017bfc JB |
141 | this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement + 1 : 1; |
142 | this.commandsStatistics.commandsStatisticsData[name].currentTimeMeasurement = duration; | |
143 | this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement > duration ? duration : this.commandsStatistics.commandsStatisticsData[name].minTimeMeasurement) : duration; | |
144 | this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement < duration ? duration : this.commandsStatistics.commandsStatisticsData[name].maxTimeMeasurement) : duration; | |
145 | this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement + duration : duration; | |
146 | this.commandsStatistics.commandsStatisticsData[name].avgTimeMeasurement = this.commandsStatistics.commandsStatisticsData[name].totalTimeMeasurement / this.commandsStatistics.commandsStatisticsData[name].countTimeMeasurement; | |
147 | Array.isArray(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries) ? this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries.push(duration) : this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries = new CircularArray<number>(DEFAULT_CIRCULAR_ARRAY_SIZE, duration); | |
148 | this.commandsStatistics.commandsStatisticsData[name].medTimeMeasurement = this.median(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries); | |
149 | this.commandsStatistics.commandsStatisticsData[name].stdDevTimeMeasurement = this.stdDeviation(this.commandsStatistics.commandsStatisticsData[name].timeMeasurementSeries); | |
7ec46a9a JB |
150 | } |
151 | ||
c0560973 | 152 | private logPrefix(): string { |
54b1efe0 | 153 | return Utils.logPrefix(` ${this.objId} | Performance statistics`); |
6af9012e | 154 | } |
7dde0b73 | 155 | } |