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 clearInterval(this.displayInterval
);
79 performance
.clearMarks();
80 this.performanceObserver
.disconnect();
83 private initializePerformanceObserver(): void {
84 this.performanceObserver
= new PerformanceObserver((list
) => {
85 this.logPerformanceEntry(list
.getEntries()[0]);
87 this.performanceObserver
.observe({ entryTypes
: ['measure'] });
90 private logPerformanceEntry(entry
: PerformanceEntry
): void {
91 this.addPerformanceStatistic(entry
.name
, entry
.duration
);
92 logger
.debug(`${this.logPrefix()} '${entry.name}' performance entry: %j`, entry
);
95 private logStatistics(): void {
96 logger
.info(this.logPrefix() + ' %j', this.statistics
);
99 private startDisplayInterval(): void {
100 if (Configuration
.getStatisticsDisplayInterval() > 0) {
101 this.displayInterval
= setInterval(() => {
102 this.logStatistics();
103 }, Configuration
.getStatisticsDisplayInterval() * 1000);
104 logger
.info(this.logPrefix() + ' displayed every ' + Utils
.secondsToHHMMSS(Configuration
.getStatisticsDisplayInterval()));
106 logger
.info(this.logPrefix() + ' display interval is set to ' + Configuration
.getStatisticsDisplayInterval().toString() + '. Not displaying statistics');
110 private median(dataSet
: number[]): number {
111 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
114 const sortedDataSet
= dataSet
.slice().sort();
115 const middleIndex
= Math.floor(sortedDataSet
.length
/ 2);
116 if (sortedDataSet
.length
% 2) {
117 return sortedDataSet
[middleIndex
/ 2];
119 return (sortedDataSet
[(middleIndex
- 1)] + sortedDataSet
[middleIndex
]) / 2;
122 private stdDeviation(dataSet
: number[]): number {
123 let totalDataSet
= 0;
124 for (const data
of dataSet
) {
125 totalDataSet
+= data
;
127 const dataSetMean
= totalDataSet
/ dataSet
.length
;
128 let totalGeometricDeviation
= 0;
129 for (const data
of dataSet
) {
130 const deviation
= data
- dataSetMean
;
131 totalGeometricDeviation
+= deviation
* deviation
;
133 return Math.sqrt(totalGeometricDeviation
/ dataSet
.length
);
136 private addPerformanceStatistic(name
: string, duration
: number): void {
139 if (MAP_NAME
[name
]) {
140 name
= MAP_NAME
[name
] as string;
142 // Initialize command statistics
143 if (!this.statistics
.statisticsData
[name
]) {
144 this.statistics
.statisticsData
[name
] = {} as StatisticsData
;
146 // Update current statistics timers
147 this.statistics
.statisticsData
[name
].countTimeMeasurement
= this.statistics
.statisticsData
[name
].countTimeMeasurement
? this.statistics
.statisticsData
[name
].countTimeMeasurement
+ 1 : 1;
148 this.statistics
.statisticsData
[name
].currentTimeMeasurement
= duration
;
149 this.statistics
.statisticsData
[name
].minTimeMeasurement
= this.statistics
.statisticsData
[name
].minTimeMeasurement
? (this.statistics
.statisticsData
[name
].minTimeMeasurement
> duration
? duration
: this.statistics
.statisticsData
[name
].minTimeMeasurement
) : duration
;
150 this.statistics
.statisticsData
[name
].maxTimeMeasurement
= this.statistics
.statisticsData
[name
].maxTimeMeasurement
? (this.statistics
.statisticsData
[name
].maxTimeMeasurement
< duration
? duration
: this.statistics
.statisticsData
[name
].maxTimeMeasurement
) : duration
;
151 this.statistics
.statisticsData
[name
].totalTimeMeasurement
= this.statistics
.statisticsData
[name
].totalTimeMeasurement
? this.statistics
.statisticsData
[name
].totalTimeMeasurement
+ duration
: duration
;
152 this.statistics
.statisticsData
[name
].avgTimeMeasurement
= this.statistics
.statisticsData
[name
].totalTimeMeasurement
/ this.statistics
.statisticsData
[name
].countTimeMeasurement
;
153 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
);
154 this.statistics
.statisticsData
[name
].medTimeMeasurement
= this.median(this.statistics
.statisticsData
[name
].timeMeasurementSeries
);
155 this.statistics
.statisticsData
[name
].stdDevTimeMeasurement
= this.stdDeviation(this.statistics
.statisticsData
[name
].timeMeasurementSeries
);
158 private logPrefix(): string {
159 return Utils
.logPrefix(` ${this.objId} | Performance statistics`);