Initial commit track perfs in DB
MODIFY
src/server/ocpp/json/JsonCentralSystemServer.ts
src/server/ocpp/json/JsonCentralSystemServer.ts
@@ -51,7 +51,7 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
// Get the Json Web Socket
const jsonWebSocket = this.jsonChargingStationClients.get(id);
if (!jsonWebSocket) {
- Logging.logError({
+ void Logging.logError({
tenantID: tenantID,
source: chargingStationID,
module: MODULE_NAME, method: 'getChargingStationClient',
@@ -73,6 +73,10 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
return this.jsonChargingStationClients.delete(wsConnection.getID());
}
+ public getNumberOfJsonConnections(): number {
+ return this.jsonChargingStationClients.size;
+ }
+
public removeRestConnection(wsConnection: JsonRestWSConnection): boolean {
// Remove from cache
return this.jsonRestClients.delete(wsConnection.getID());
@@ -104,7 +108,7 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
if (info.req.url.startsWith('/REST')) {
return true;
}
- Logging.logError({
+ void Logging.logError({
tenantID: Constants.DEFAULT_TENANT,
module: MODULE_NAME, method: 'verifyClient',
action: ServerAction.EXPRESS_SERVER,
@@ -128,7 +132,7 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
} else if (protocols === 'rest') {
return protocols;
}
- Logging.logError({
+ void Logging.logError({
tenantID: Constants.DEFAULT_TENANT,
module: MODULE_NAME, method: 'handleProtocols',
action: ServerAction.EXPRESS_SERVER,
@@ -162,8 +166,7 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
throw Error('Wrong WebSocket client connection URI path');
}
} catch (error) {
- // Log
- Logging.logException(
+ void Logging.logException(
error, ServerAction.WS_CONNECTION, '', MODULE_NAME, 'connection', Constants.DEFAULT_TENANT);
// Respond
ws.close(WebSocketCloseEventStatusCode.CLOSE_UNSUPPORTED, error.message);
@@ -174,8 +177,7 @@ export default class JsonCentralSystemServer extends CentralSystemServer {
this.keepAliveInterval = setInterval((): void => {
for (const jsonWSConnection of this.jsonChargingStationClients.values()) {
if (!jsonWSConnection.isConnectionAlive) {
- // Log
- Logging.logError({
+ void Logging.logError({
tenantID: jsonWSConnection.getTenantID(),
source: jsonWSConnection.getChargingStationID(),
action: ServerAction.WS_JSON_CONNECTION_CLOSED,

ADD
src/storage/mongodb/PerformanceStorage.ts
+import Constants from '../../utils/Constants';
+import Performance from '../../types/Performance';
+import Utils from '../../utils/Utils';
+import global from '../../types/GlobalType';
+
+export default class PerformanceStorage {
+ static async savePerformance(performance: Performance): Promise<void> {
+ // Set
+ const performanceMDB: any = {
+ tenantID: performance.tenantID && performance.tenantID !== Constants.DEFAULT_TENANT ? Utils.convertToObjectID(performance.tenantID) : Constants.DEFAULT_TENANT,
+ timestamp: Utils.convertToDate(performance.timestamp),
+ host: performance.host,
+ memoryTotalGb: performance.memoryTotalGb,
+ memoryFreeGb: performance.memoryFreeGb,
+ loadAverageLastMin: performance.loadAverageLastMin,
+ process: performance.process,
+ source: performance.source,
+ module: performance.module,
+ method: performance.method,
+ action: performance.action,
+ };
+ // Add user only if provided
+ if (performance.userID) {
+ performanceMDB.userID = Utils.convertToObjectID(performance.userID);
+ }
+ // Add parent only if provided
+ if (performance.parentID) {
+ performanceMDB.parentID = Utils.convertToObjectID(performance.parentID);
+ }
+ // Add nbr charging stations only if provided
+ if (Utils.convertToInt(performance.numberOfChargingStations) > 0) {
+ performanceMDB.numberOfChargingStations = Utils.convertToInt(performance.numberOfChargingStations);
+ }
+ // Add duration only if provided
+ if (Utils.convertToInt(performance.durationMs) > 0) {
+ performanceMDB.durationMs = Utils.convertToInt(performance.durationMs);
+ }
+ // Add size only if provided
+ if (Utils.convertToInt(performance.sizeKb) > 0) {
+ performanceMDB.sizeKb = Utils.convertToInt(performance.sizeKb);
+ }
+ // Add HTTP only when provided (httpMethod is always provided)
+ if (performance.httpMethod) {
+ performanceMDB.httpMethod = performance.httpMethod;
+ performanceMDB.httpCode = Utils.convertToInt(performance.httpCode);
+ performanceMDB.httpUrl = performance.httpUrl;
+ }
+ // Insert
+ await global.database.getCollection<any>(Constants.DEFAULT_TENANT, 'performances')
+ .insertOne(performanceMDB);
+ }
+}

ADD
src/types/Performance.ts
+import { ServerAction } from './Server';
+
+export default interface Performance {
+ id?: string;
+ tenantID: string;
+ timestamp?: Date;
+ durationMs?: number;
+ sizeKb?: number;
+ host: string;
+ process: string;
+ memoryTotalGb: number;
+ memoryFreeGb: number;
+ loadAverageLastMin: number,
+ numberOfChargingStations?: number;
+ source: string;
+ module: string;
+ method: string;
+ action: ServerAction|string;
+ httpUrl?: string;
+ httpMethod?: string;
+ httpCode?: number;
+ userID?: string;
+ parentID?: string;
+}

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -162,6 +162,10 @@ export default class Constants {
public static readonly CENTRAL_SERVER = 'Central Server';
public static readonly OCPI_SERVER = 'OCPI Server';
+ public static readonly OCPP_SERVER = 'OCPP Server';
+ public static readonly DATABASE_SERVER = 'Database Server';
+ public static readonly REST_SERVER = 'REST Server';
+ public static readonly AXIOS_CLIENT = 'Axios Client';
public static readonly WITH_CHARGING_STATIONS = true; // Not used
public static readonly WITHOUT_CHARGING_STATIONS = false; // Not used

MODIFY
src/utils/Logging.ts
src/utils/Logging.ts
@@ -14,6 +14,7 @@ import LoggingConfiguration from '../types/configuration/LoggingConfiguration';
import LoggingStorage from '../storage/mongodb/LoggingStorage';
import { OCPIResult } from '../types/ocpi/OCPIResult';
import { OCPPStatus } from '../types/ocpp/OCPPClient';
+import PerformanceStorage from '../storage/mongodb/PerformanceStorage';
import { ServerAction } from '../types/Server';
import User from '../types/User';
import UserToken from '../types/UserToken';
@@ -45,7 +46,7 @@ export default class Logging {
}
// Debug DB
- public static traceEnd(tenantID: string, module: string, method: string, key: string, data: any = {}): void {
+ public static async traceEnd(tenantID: string, module: string, method: string, key: string, data: any = {}): Promise<void> {
if (Utils.isDevelopmentEnv()) {
// Compute duration if provided
let executionDurationMillis: number;
@@ -71,6 +72,16 @@ export default class Logging {
console.error(chalk.red(message));
console.error(chalk.red('===================================='));
}
+ await PerformanceStorage.savePerformance(
+ Utils.buildPerformance({
+ tenantID,
+ durationMs: executionDurationMillis,
+ sizeKb: sizeOfDataKB,
+ source: Constants.DATABASE_SERVER,
+ module, method,
+ action: key,
+ })
+ );
}
}
@@ -291,6 +302,19 @@ export default class Logging {
headers: res.getHeaders(),
}
});
+ void PerformanceStorage.savePerformance(
+ Utils.buildPerformance({
+ tenantID,
+ httpUrl: req.url,
+ httpCode: res.statusCode,
+ httpMethod: req.method,
+ durationMs: executionDurationMillis,
+ sizeKb: sizeOfDataKB,
+ source: Constants.REST_SERVER,
+ module: MODULE_NAME, method: 'logExpressResponse',
+ action: ServerAction.HTTP_RESPONSE,
+ })
+ );
} finally {
next();
}
@@ -346,7 +370,7 @@ export default class Logging {
tenantID: tenantID,
action: ServerAction.HTTP_RESPONSE,
message: `Axios HTTP Response - ${(executionDurationMillis > 0) ? executionDurationMillis : '?'}ms - ${(sizeOfDataKB > 0) ? sizeOfDataKB : '?'}kB << ${response.config.method.toLocaleUpperCase()}/${response.status} '${response.config.url}'`,
- module: MODULE_NAME, method: 'interceptor',
+ module: MODULE_NAME, method: 'logAxiosResponse',
detailedMessages: {
status: response.status,
statusText: response.statusText,
@@ -355,6 +379,19 @@ export default class Logging {
response: Utils.cloneObject(response.data)
}
});
+ await PerformanceStorage.savePerformance(
+ Utils.buildPerformance({
+ tenantID,
+ httpUrl: response.config.url,
+ httpCode: response.status,
+ httpMethod: response.config.method.toLocaleUpperCase(),
+ durationMs: executionDurationMillis,
+ sizeKb: sizeOfDataKB,
+ source: Constants.AXIOS_CLIENT,
+ module: MODULE_NAME, method: 'logAxiosResponse',
+ action: ServerAction.HTTP_RESPONSE,
+ })
+ );
}
public static async logAxiosError(tenantID: string, error: AxiosError): Promise<void> {
@@ -886,5 +923,14 @@ export default class Logging {
detailedMessages
});
}
+ await PerformanceStorage.savePerformance(
+ Utils.buildPerformance({
+ tenantID,
+ durationMs: executionDurationMillis,
+ source: Constants.OCPP_SERVER,
+ module: module, method: 'traceChargingStationActionEnd',
+ action: action,
+ })
+ );
}
}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -13,6 +13,7 @@ import ConnectorStats from '../types/ConnectorStats';
import Constants from './Constants';
import Cypher from './Cypher';
import { ObjectID } from 'mongodb';
+import Performance from '../types/Performance';
import QRCode from 'qrcode';
import { Request } from 'express';
import { ServerAction } from '../types/Server';
@@ -24,8 +25,10 @@ import { WebSocketCloseEventStatusString } from '../types/WebSocket';
import _ from 'lodash';
import bcrypt from 'bcryptjs';
import cfenv from 'cfenv';
+import cluster from 'cluster';
import crypto from 'crypto';
import fs from 'fs';
+import global from '../types/GlobalType';
import http from 'http';
import moment from 'moment';
import os from 'os';
@@ -35,8 +38,6 @@ import tzlookup from 'tz-lookup';
import { v4 as uuid } from 'uuid';
import validator from 'validator';
-const MODULE_NAME = 'Utils';
-
export default class Utils {
public static getConnectorsFromChargePoint(chargingStation: ChargingStation, chargePoint: ChargePoint): Connector[] {
const connectors: Connector[] = [];
@@ -1364,6 +1365,32 @@ export default class Utils {
};
}
+ public static buildPerformance(params: {
+ tenantID: string; durationMs: number; sizeKb?: number;
+ source?: string; module: string; method: string; action: ServerAction|string;
+ httpUrl?: string; httpMethod?: string; httpCode?: number;
+ }): Performance {
+ return {
+ tenantID: params.tenantID,
+ timestamp: new Date(),
+ durationMs: params.durationMs,
+ sizeKb: params.sizeKb,
+ host: Utils.getHostname(),
+ process: cluster.isWorker ? 'worker ' + cluster.worker.id.toString() : 'master',
+ memoryTotalGb: Utils.truncTo(os.totalmem() / 1000000000, 3),
+ memoryFreeGb: Utils.truncTo(os.freemem() / 1000000000, 3),
+ loadAverageLastMin: os.loadavg()[0],
+ numberOfChargingStations: global.centralSystemJsonServer?.getNumberOfJsonConnections(),
+ source: params.source,
+ module: params.module,
+ method: params.method,
+ action: params.action,
+ httpUrl: params.httpUrl,
+ httpMethod: params.httpMethod,
+ httpCode: params.httpCode,
+ };
+ }
+
public static getHostname(): string {
return Configuration.isCloudFoundry() ? cfenv.getAppEnv().name : os.hostname();
}

Initial commit track perfs in DB