1 import { CircularArray
, DEFAULT_CIRCULAR_ARRAY_SIZE
} from
'./CircularArray';
2 import { IncomingRequestCommand
, RequestCommand
} from
'../types/ocpp/Requests';
3 import { PerformanceEntry
, PerformanceObserver
, performance
} from
'perf_hooks';
4 import Statistics
, { StatisticsData
} from
'../types/Statistics';
6 import Configuration from
'./Configuration';
7 import { MessageType
} from
'../types/ocpp/MessageType';
8 import Utils from
'./Utils';
9 import logger from
'./Logger';
11 export default class PerformanceStatistics
{
12 private objId
: string;
13 private performanceObserver
: PerformanceObserver
;
14 private statistics
: Statistics
;
15 private displayInterval
: NodeJS
.Timeout
;
17 public constructor(objId
: string) {
19 this.initializePerformanceObserver();
20 this.statistics
= { id
: this.objId
?? 'Object id not specified', statisticsData
: {} };
23 public static beginMeasure(id
: string): string {
24 const beginId
= 'begin' + id
.charAt(0).toUpperCase() + id
.slice(1);
25 performance
.mark(beginId
);
29 public static endMeasure(name
: string, beginId
: string): void {
30 performance
.measure(name
, beginId
);
33 public addRequestStatistic(command
: RequestCommand
| IncomingRequestCommand
, messageType
: MessageType
): void {
34 switch (messageType
) {
35 case MessageType
.CALL_MESSAGE
:
36 if (this.statistics
.statisticsData
[command
] && this.statistics
.statisticsData
[command
].countRequest
) {
37 this.statistics
.statisticsData
[command
].countRequest
++;
39 this.statistics
.statisticsData
[command
] = {} as StatisticsData
;
40 this.statistics
.statisticsData
[command
].countRequest
= 1;
43 case MessageType
.CALL_RESULT_MESSAGE
:
44 if (this.statistics
.statisticsData
[command
]) {
45 if (this.statistics
.statisticsData
[command
].countResponse
) {
46 this.statistics
.statisticsData
[command
].countResponse
++;
48 this.statistics
.statisticsData
[command
].countResponse
= 1;
51 this.statistics
.statisticsData
[command
] = {} as StatisticsData
;
52 this.statistics
.statisticsData
[command
].countResponse
= 1;
55 case MessageType
.CALL_ERROR_MESSAGE
:
56 if (this.statistics
.statisticsData
[command
]) {
57 if (this.statistics
.statisticsData
[command
].countError
) {
58 this.statistics
.statisticsData
[command
].countError
++;
60 this.statistics
.statisticsData
[command
].countError
= 1;
63 this.statistics
.statisticsData
[command
] = {} as StatisticsData
;
64 this.statistics
.statisticsData
[command
].countError
= 1;
68 logger
.error(`${this.logPrefix()} wrong message type ${messageType}`);
73 public start(): void {
74 this.startDisplayInterval();
78 if (this.displayInterval
) {
79 clearInterval(this.displayInterval
);
81 performance
.clearMarks();
82 this.performanceObserver
?.disconnect();
85 public restart(): void {
90 private initializePerformanceObserver(): void {
91 this.performanceObserver
= new PerformanceObserver((list
) => {
92 this.addPerformanceEntry(list
.getEntries()[0]);
94 this.performanceObserver
.observe({ entryTypes
: ['measure'] });
97 private addPerformanceEntry(entry
: PerformanceEntry
): void {
98 this.addPerformanceStatistic(entry
.name
, entry
.duration
);
99 logger
.debug(`${this.logPrefix()} '${entry.name}' performance entry: %j`, entry
);
102 private logStatistics(): void {
103 logger
.info(this.logPrefix() + ' %j', this.statistics
);
106 private startDisplayInterval(): void {
107 if (Configuration
.getStatisticsDisplayInterval() > 0) {
108 this.displayInterval
= setInterval(() => {
109 this.logStatistics();
110 }, Configuration
.getStatisticsDisplayInterval() * 1000);
111 logger
.info(this.logPrefix() + ' displayed every ' + Utils
.secondsToHHMMSS(Configuration
.getStatisticsDisplayInterval()));
113 logger
.info(this.logPrefix() + ' display interval is set to ' + Configuration
.getStatisticsDisplayInterval().toString() + '. Not displaying statistics');
117 private median(dataSet
: number[]): number {
118 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
121 const sortedDataSet
= dataSet
.slice().sort();
122 const middleIndex
= Math.floor(sortedDataSet
.length
/ 2);
123 if (sortedDataSet
.length
% 2) {
124 return sortedDataSet
[middleIndex
/ 2];
126 return (sortedDataSet
[(middleIndex
- 1)] + sortedDataSet
[middleIndex
]) / 2;
129 private stdDeviation(dataSet
: number[]): number {
130 let totalDataSet
= 0;
131 for (const data
of dataSet
) {
132 totalDataSet
+= data
;
134 const dataSetMean
= totalDataSet
/ dataSet
.length
;
135 let totalGeometricDeviation
= 0;
136 for (const data
of dataSet
) {
137 const deviation
= data
- dataSetMean
;
138 totalGeometricDeviation
+= deviation
* deviation
;
140 return Math.sqrt(totalGeometricDeviation
/ dataSet
.length
);
143 private addPerformanceStatistic(name
: string, duration
: number): void {
146 if (MAP_NAME
[name
]) {
147 name
= MAP_NAME
[name
] as string;
149 // Initialize command statistics
150 if (!this.statistics
.statisticsData
[name
]) {
151 this.statistics
.statisticsData
[name
] = {} as StatisticsData
;
153 // Update current statistics timers
154 this.statistics
.statisticsData
[name
].countTimeMeasurement
= this.statistics
.statisticsData
[name
].countTimeMeasurement
? this.statistics
.statisticsData
[name
].countTimeMeasurement
+ 1 : 1;
155 this.statistics
.statisticsData
[name
].currentTimeMeasurement
= duration
;
156 this.statistics
.statisticsData
[name
].minTimeMeasurement
= this.statistics
.statisticsData
[name
].minTimeMeasurement
? (this.statistics
.statisticsData
[name
].minTimeMeasurement
> duration
? duration
: this.statistics
.statisticsData
[name
].minTimeMeasurement
) : duration
;
157 this.statistics
.statisticsData
[name
].maxTimeMeasurement
= this.statistics
.statisticsData
[name
].maxTimeMeasurement
? (this.statistics
.statisticsData
[name
].maxTimeMeasurement
< duration
? duration
: this.statistics
.statisticsData
[name
].maxTimeMeasurement
) : duration
;
158 this.statistics
.statisticsData
[name
].totalTimeMeasurement
= this.statistics
.statisticsData
[name
].totalTimeMeasurement
? this.statistics
.statisticsData
[name
].totalTimeMeasurement
+ duration
: duration
;
159 this.statistics
.statisticsData
[name
].avgTimeMeasurement
= this.statistics
.statisticsData
[name
].totalTimeMeasurement
/ this.statistics
.statisticsData
[name
].countTimeMeasurement
;
160 Array.isArray(this.statistics
.statisticsData
[name
].timeMeasurementSeries
) ? this.statistics
.statisticsData
[name
].timeMeasurementSeries
.push(duration
) : this.statistics
.statisticsData
[name
].timeMeasurementSeries
= new CircularArray
<number>(DEFAULT_CIRCULAR_ARRAY_SIZE
, duration
);
161 this.statistics
.statisticsData
[name
].medTimeMeasurement
= this.median(this.statistics
.statisticsData
[name
].timeMeasurementSeries
);
162 this.statistics
.statisticsData
[name
].stdDevTimeMeasurement
= this.stdDeviation(this.statistics
.statisticsData
[name
].timeMeasurementSeries
);
165 private logPrefix(): string {
166 return Utils
.logPrefix(` ${this.objId} | Performance statistics`);