And various assorted fixes.
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
],
"distributeStationsToTenantsEqually": true,
"statisticsDisplayInterval": 60,
+ "chargingStationsPerWorker": 1,
"useWorkerPool": false,
"workerPoolMaxSize": 16,
- "chargingStationsPerWorker": 1,
"stationTemplateURLs": [
{
"file": "./src/assets/station-templates/siemens.station-template.json",
{
"authorizationFile": "./src/assets/authorization-tags.json",
"baseName": "CS-ABB",
- "nameSuffix": "Roaming",
+ "nameSuffix": "-Roaming",
"chargePointModel": "MD_TERRA_53",
"chargePointVendor": "ABB",
"firmwareVersion": "4.0.4.22",
if (Utils.convertToInt(lastConnector) === 0 && this._getUseConnectorId0() && this.stationInfo.Connectors[lastConnector]) {
this.connectors[lastConnector] = Utils.cloneObject<Connector>(this.stationInfo.Connectors[lastConnector]);
this.connectors[lastConnector].availability = AvailabilityType.OPERATIVE;
+ if (Utils.isUndefined(this.connectors[lastConnector]?.chargingProfiles)) {
+ this.connectors[lastConnector].chargingProfiles = [];
+ }
}
}
// Generate all connectors
const randConnectorID = this.stationInfo.randomConnectors ? Utils.getRandomInt(Utils.convertToInt(lastConnector), 1) : index;
this.connectors[index] = Utils.cloneObject<Connector>(this.stationInfo.Connectors[randConnectorID]);
this.connectors[index].availability = AvailabilityType.OPERATIVE;
+ if (Utils.isUndefined(this.connectors[lastConnector]?.chargingProfiles)) {
+ this.connectors[index].chargingProfiles = [];
+ }
}
}
}
}
this.stationInfo.powerDivider = this._getPowerDivider();
if (this.getEnableStatistics()) {
- this.statistics = Statistics.getInstance();
- this.statistics.objName = this.stationInfo.chargingStationId;
+ this.statistics = new Statistics(this.stationInfo.chargingStationId);
this.performanceObserver = new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
this.statistics.logPerformance(entry, Constants.ENTITY_CHARGING_STATION);
logger.debug(this._logPrefix() + ' Heartbeat response received: %j to Heartbeat request: %j', payload, requestPayload);
}
+ handleResponseAuthorize(payload: AuthorizeResponse, requestPayload: AuthorizeRequest): void {
+ logger.debug(this._logPrefix() + ' Authorize response received: %j to Authorize request: %j', payload, requestPayload);
+ }
+
async handleRequest(messageId: string, commandName: IncomingRequestCommand, commandPayload: Record<string, unknown>): Promise<void> {
let response;
// Call
-import Configuration from '../utils/Configuration';
-import Constants from '../utils/Constants';
-import { Worker } from 'worker_threads';
import WorkerData from '../types/WorkerData';
-import WorkerPool from './WorkerPool';
-export default class Wrk {
- private workerScript: string;
- private workerData: WorkerData;
- private worker: Worker;
- private maxWorkerElements: number;
- private numWorkerElements: number;
+export default abstract class Wrk {
+ protected workerScript: string;
+ public abstract size: number;
/**
* Create a new `Wrk`.
*
* @param {string} workerScript
- * @param {WorkerData} workerData
- * @param {number} maxWorkerElements
*/
- constructor(workerScript: string, workerData: WorkerData, maxWorkerElements = 1) {
- this.workerData = workerData;
+ constructor(workerScript: string) {
this.workerScript = workerScript;
- if (Configuration.useWorkerPool()) {
- WorkerPool.maxConcurrentWorkers = Configuration.getWorkerPoolMaxSize();
- } else {
- this.maxWorkerElements = maxWorkerElements;
- this.numWorkerElements = 0;
- }
}
- /**
- *
- * @return {Promise}
- * @public
- */
- async start(): Promise<Worker> {
- if (Configuration.useWorkerPool()) {
- await this.startWorkerPool();
- } else {
- await this.startWorker();
- }
- return this.worker;
- }
-
- /**
- *
- * @return {void}
- * @public
- */
- addWorkerElement(workerData: WorkerData): void {
- if (Configuration.useWorkerPool()) {
- throw Error('Cannot add Wrk element if the worker pool is enabled');
- }
- if (this.numWorkerElements >= this.maxWorkerElements) {
- throw Error('Cannot add Wrk element: max number of elements per worker reached');
- }
- this.workerData = workerData;
- this.worker.postMessage({ id: Constants.START_WORKER_ELEMENT, workerData: workerData });
- this.numWorkerElements++;
- }
-
- /**
- *
- * @return {number}
- * @public
- */
- public getWorkerPoolSize(): number {
- if (Configuration.useWorkerPool()) {
- return WorkerPool.getPoolSize();
- }
- }
-
- /**
- *
- * @return {Promise}
- * @private
- */
- private async startWorkerPool() {
- return new Promise((resolve, reject) => {
- WorkerPool.acquire(this.workerScript, { workerData: this.workerData }, (err, worker) => {
- if (err) {
- return reject(err);
- }
- worker.once('message', resolve);
- worker.once('error', reject);
- this.worker = worker;
- });
- });
- }
-
- /**
- *
- * @return {Promise}
- * @private
- */
- private async startWorker() {
- return new Promise((resolve, reject) => {
- const worker = new Worker(this.workerScript, { workerData: this.workerData });
- worker.on('message', resolve);
- worker.on('error', reject);
- worker.on('exit', (code) => {
- if (code !== 0) {
- reject(new Error(`Worker stopped with exit code ${code}`));
- }
- });
- this.numWorkerElements++;
- this.worker = worker;
- });
- }
+ public abstract start(): Promise<void>;
+ public abstract addElement(elementData: WorkerData): void;
}
--- /dev/null
+import Configuration from '../utils/Configuration';
+import Constants from '../utils/Constants';
+import { Worker } from 'worker_threads';
+import WorkerData from '../types/WorkerData';
+import Wrk from './Worker';
+
+export default class WorkerGroup extends Wrk {
+ private worker: Worker;
+ private lastElementData: WorkerData;
+ private maxWorkerElements: number;
+ private numWorkerElements: number;
+
+ /**
+ * Create a new `WorkerGroup`.
+ *
+ * @param {string} workerScript
+ * @param {WorkerData} workerData
+ * @param {number} maxWorkerElements
+ */
+ constructor(workerScript: string, initialElementData: WorkerData, maxWorkerElements = 1) {
+ super(workerScript);
+ this.lastElementData = initialElementData;
+ this.maxWorkerElements = maxWorkerElements;
+ this.numWorkerElements = 0;
+ }
+
+ get size(): number {
+ return this.numWorkerElements;
+ }
+
+ /**
+ *
+ * @return {void}
+ * @public
+ */
+ public addElement(elementData: WorkerData): void {
+ if (Configuration.useWorkerPool()) {
+ throw Error('Cannot add a WorkerGroup element: the worker pool is enabled in configuration');
+ }
+ if (!this.worker) {
+ throw Error('Cannot add a WorkerGroup element: worker does not exist');
+ }
+ if (this.numWorkerElements >= this.maxWorkerElements) {
+ throw Error('Cannot add a WorkerGroup element: max number of elements per worker reached');
+ }
+ this.lastElementData = elementData;
+ this.worker.postMessage({ id: Constants.START_WORKER_ELEMENT, workerData: this.lastElementData });
+ this.numWorkerElements++;
+ }
+
+ /**
+ *
+ * @return {Promise<Worker>}
+ * @public
+ */
+ public async start(): Promise<void> {
+ await this.startWorker();
+ }
+
+ /**
+ *
+ * @return {Promise}
+ * @private
+ */
+ private async startWorker() {
+ return new Promise((resolve, reject) => {
+ const worker = new Worker(this.workerScript, { workerData: this.lastElementData });
+ worker.on('message', resolve);
+ worker.on('error', reject);
+ worker.on('exit', (code) => {
+ if (code !== 0) {
+ reject(new Error(`Worker stopped with exit code ${code}`));
+ }
+ });
+ this.numWorkerElements++;
+ this.worker = worker;
+ });
+ }
+}
-import { Worker, WorkerOptions } from 'worker_threads';
-
+import Configuration from '../utils/Configuration';
import Pool from 'worker-threads-pool';
+import WorkerData from '../types/WorkerData';
+import Wrk from './Worker';
-export default class WorkerPool {
- public static maxConcurrentWorkers: number;
- private static instance: Pool;
+export default class WorkerPool extends Wrk {
+ private pool: Pool;
- private constructor() { }
+ /**
+ * Create a new `WorkerPool`.
+ *
+ * @param {string} workerScript
+ */
+ constructor(workerScript: string) {
+ super(workerScript);
+ this.pool = UniquePool.getInstance();
+ }
- public static getInstance(): Pool {
- if (!WorkerPool.instance) {
- WorkerPool.instance = new Pool({ max: WorkerPool.maxConcurrentWorkers });
- }
- return WorkerPool.instance;
+ get size(): number {
+ return this.pool.size;
}
- public static acquire(filename: string, options: WorkerOptions, callback: (error: Error | null, worker: Worker) => void): void {
- WorkerPool.getInstance().acquire(filename, options, callback);
+ /**
+ *
+ * @return {Promise<void>}
+ * @public
+ */
+ public async start(): Promise<void> { }
+
+ /**
+ *
+ * @return {Promise}
+ * @public
+ */
+ public async addElement(elementData: WorkerData): Promise<void> {
+ return new Promise((resolve, reject) => {
+ this.pool.acquire(this.workerScript, { workerData: elementData }, (err, worker) => {
+ if (err) {
+ return reject(err);
+ }
+ worker.once('message', resolve);
+ worker.once('error', reject);
+ });
+ });
}
+}
+
+class UniquePool {
+ private static instance: Pool;
- public static getPoolSize(): number {
- return WorkerPool.getInstance().size;
+ private constructor() { }
+
+ public static getInstance(): Pool {
+ if (!UniquePool.instance) {
+ UniquePool.instance = new Pool({ max: Configuration.getWorkerPoolMaxSize() });
+ }
+ return UniquePool.instance;
}
}
import Constants from './utils/Constants';
import Utils from './utils/Utils';
import WorkerData from './types/WorkerData';
-import Wrk from './charging-station/Worker';
+import WorkerGroup from './charging-station/WorkerGroup';
+import WorkerPool from './charging-station/WorkerPool';
class Bootstrap {
static async start() {
let numConcurrentWorkers = 0;
const chargingStationsPerWorker = Configuration.getChargingStationsPerWorker();
let chargingStationsPerWorkerCounter = 0;
- let worker: Wrk;
+ let workerImplementation: WorkerGroup | WorkerPool;
+ if (Configuration.useWorkerPool()) {
+ workerImplementation = new WorkerPool('./dist/charging-station/StationWorker.js');
+ void workerImplementation.start();
+ }
// Start each ChargingStation object in a worker thread
if (Configuration.getStationTemplateURLs()) {
for (const stationURL of Configuration.getStationTemplateURLs()) {
templateFile: stationURL.file
};
if (Configuration.useWorkerPool()) {
- worker = new Wrk('./dist/charging-station/StationWorker.js', workerData);
- worker.start().catch(() => { });
- numConcurrentWorkers = worker.getWorkerPoolSize();
- numStationsTotal = numConcurrentWorkers;
- // Start Wrk sequentially to optimize memory at start time
- await Utils.sleep(Constants.START_WORKER_DELAY);
- } else if (!Configuration.useWorkerPool() && (chargingStationsPerWorkerCounter === 0 || chargingStationsPerWorkerCounter >= chargingStationsPerWorker)) {
- // Start new Wrk with one charging station
- worker = new Wrk('./dist/charging-station/StationWorker.js', workerData, chargingStationsPerWorker);
- worker.start().catch(() => { });
- numConcurrentWorkers++;
- chargingStationsPerWorkerCounter = 1;
- numStationsTotal++;
- // Start Wrk sequentially to optimize memory at start time
+ void workerImplementation.addElement(workerData);
+ numConcurrentWorkers = workerImplementation.size;
+ numStationsTotal = workerImplementation.size;
+ // Start worker sequentially to optimize memory at start time
await Utils.sleep(Constants.START_WORKER_DELAY);
- } else if (!Configuration.useWorkerPool()) {
- // Add charging station to existing Wrk
- worker.addWorkerElement(workerData);
- chargingStationsPerWorkerCounter++;
- numStationsTotal++;
+ } else {
+ // eslint-disable-next-line no-lonely-if
+ if (chargingStationsPerWorkerCounter === 0 || chargingStationsPerWorkerCounter >= chargingStationsPerWorker) {
+ // Start new WorkerGroup with one charging station
+ workerImplementation = new WorkerGroup('./dist/charging-station/StationWorker.js', workerData, chargingStationsPerWorker);
+ void workerImplementation.start();
+ numConcurrentWorkers++;
+ chargingStationsPerWorkerCounter = 1;
+ numStationsTotal++;
+ // Start worker sequentially to optimize memory at start time
+ await Utils.sleep(Constants.START_WORKER_DELAY);
+ } else {
+ // Add charging station to existing WorkerGroup
+ void workerImplementation.addElement(workerData);
+ chargingStationsPerWorkerCounter++;
+ numStationsTotal++;
+ }
}
}
} catch (error) {
}
export default interface CommandStatistics {
- [command: string]: CommandStatisticsData;
+ id: string;
+ commandsStatisticsData: Record<string, CommandStatisticsData>;
}
import logger from './Logger';
export default class Statistics {
- private static instance: Statistics;
- public objName: string;
+ private objId: string;
private commandsStatistics: CommandStatistics;
- private constructor() {
- this.commandsStatistics = {} as CommandStatistics;
+ public constructor(objName: string) {
+ this.objId = objName;
+ this.commandsStatistics = { id: this.objId ? this.objId : ' Object id not specified', commandsStatisticsData: {} } as CommandStatistics;
}
- static getInstance(): Statistics {
- if (!Statistics.instance) {
- Statistics.instance = new Statistics();
- }
- return Statistics.instance;
- }
-
- addMessage(command: RequestCommand | IncomingRequestCommand, messageType: MessageType): void {
+ public addMessage(command: RequestCommand | IncomingRequestCommand, messageType: MessageType): void {
switch (messageType) {
case MessageType.CALL_MESSAGE:
- if (this.commandsStatistics[command] && this.commandsStatistics[command].countRequest) {
- this.commandsStatistics[command].countRequest++;
+ if (this.commandsStatistics.commandsStatisticsData[command] && this.commandsStatistics.commandsStatisticsData[command].countRequest) {
+ this.commandsStatistics.commandsStatisticsData[command].countRequest++;
} else {
- this.commandsStatistics[command] = {} as CommandStatisticsData;
- this.commandsStatistics[command].countRequest = 1;
+ this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData;
+ this.commandsStatistics.commandsStatisticsData[command].countRequest = 1;
}
break;
case MessageType.CALL_RESULT_MESSAGE:
- if (this.commandsStatistics[command]) {
- if (this.commandsStatistics[command].countResponse) {
- this.commandsStatistics[command].countResponse++;
+ if (this.commandsStatistics.commandsStatisticsData[command]) {
+ if (this.commandsStatistics.commandsStatisticsData[command].countResponse) {
+ this.commandsStatistics.commandsStatisticsData[command].countResponse++;
} else {
- this.commandsStatistics[command].countResponse = 1;
+ this.commandsStatistics.commandsStatisticsData[command].countResponse = 1;
}
} else {
- this.commandsStatistics[command] = {} as CommandStatisticsData;
- this.commandsStatistics[command].countResponse = 1;
+ this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData;
+ this.commandsStatistics.commandsStatisticsData[command].countResponse = 1;
}
break;
case MessageType.CALL_ERROR_MESSAGE:
- if (this.commandsStatistics[command]) {
- if (this.commandsStatistics[command].countError) {
- this.commandsStatistics[command].countError++;
+ if (this.commandsStatistics.commandsStatisticsData[command]) {
+ if (this.commandsStatistics.commandsStatisticsData[command].countError) {
+ this.commandsStatistics.commandsStatisticsData[command].countError++;
} else {
- this.commandsStatistics[command].countError = 1;
+ this.commandsStatistics.commandsStatisticsData[command].countError = 1;
}
} else {
- this.commandsStatistics[command] = {} as CommandStatisticsData;
- this.commandsStatistics[command].countError = 1;
+ this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData;
+ this.commandsStatistics.commandsStatisticsData[command].countError = 1;
}
break;
default:
}
}
- logPerformance(entry: PerformanceEntry, className: string): void {
+ public logPerformance(entry: PerformanceEntry, className: string): void {
this.addPerformanceTimer(entry.name as RequestCommand | IncomingRequestCommand, entry.duration);
const perfEntry: PerfEntry = {} as PerfEntry;
perfEntry.name = entry.name;
logger.info(`${this._logPrefix()} object ${className} method(s) performance entry: %j`, perfEntry);
}
- start(): void {
+ public start(): void {
this._displayInterval();
}
command = MAPCOMMAND[command] as RequestCommand | IncomingRequestCommand;
}
// Initialize command statistics
- if (!this.commandsStatistics[command]) {
- this.commandsStatistics[command] = {} as CommandStatisticsData;
+ if (!this.commandsStatistics.commandsStatisticsData[command]) {
+ this.commandsStatistics.commandsStatisticsData[command] = {} as CommandStatisticsData;
}
// Update current statistics timers
- this.commandsStatistics[command].countTimeMeasurement = this.commandsStatistics[command].countTimeMeasurement ? this.commandsStatistics[command].countTimeMeasurement + 1 : 1;
- this.commandsStatistics[command].currentTimeMeasurement = duration;
- this.commandsStatistics[command].minTimeMeasurement = this.commandsStatistics[command].minTimeMeasurement ? (this.commandsStatistics[command].minTimeMeasurement > duration ? duration : this.commandsStatistics[command].minTimeMeasurement) : duration;
- this.commandsStatistics[command].maxTimeMeasurement = this.commandsStatistics[command].maxTimeMeasurement ? (this.commandsStatistics[command].maxTimeMeasurement < duration ? duration : this.commandsStatistics[command].maxTimeMeasurement) : duration;
- this.commandsStatistics[command].totalTimeMeasurement = this.commandsStatistics[command].totalTimeMeasurement ? this.commandsStatistics[command].totalTimeMeasurement + duration : duration;
- this.commandsStatistics[command].avgTimeMeasurement = this.commandsStatistics[command].totalTimeMeasurement / this.commandsStatistics[command].countTimeMeasurement;
- Array.isArray(this.commandsStatistics[command].timeMeasurementSeries) ? this.commandsStatistics[command].timeMeasurementSeries.push(duration) : this.commandsStatistics[command].timeMeasurementSeries = [duration] as CircularArray<number>;
- this.commandsStatistics[command].medTimeMeasurement = this.median(this.commandsStatistics[command].timeMeasurementSeries);
+ this.commandsStatistics.commandsStatisticsData[command].countTimeMeasurement = this.commandsStatistics.commandsStatisticsData[command].countTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[command].countTimeMeasurement + 1 : 1;
+ this.commandsStatistics.commandsStatisticsData[command].currentTimeMeasurement = duration;
+ this.commandsStatistics.commandsStatisticsData[command].minTimeMeasurement = this.commandsStatistics.commandsStatisticsData[command].minTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[command].minTimeMeasurement > duration ? duration : this.commandsStatistics.commandsStatisticsData[command].minTimeMeasurement) : duration;
+ this.commandsStatistics.commandsStatisticsData[command].maxTimeMeasurement = this.commandsStatistics.commandsStatisticsData[command].maxTimeMeasurement ? (this.commandsStatistics.commandsStatisticsData[command].maxTimeMeasurement < duration ? duration : this.commandsStatistics.commandsStatisticsData[command].maxTimeMeasurement) : duration;
+ this.commandsStatistics.commandsStatisticsData[command].totalTimeMeasurement = this.commandsStatistics.commandsStatisticsData[command].totalTimeMeasurement ? this.commandsStatistics.commandsStatisticsData[command].totalTimeMeasurement + duration : duration;
+ this.commandsStatistics.commandsStatisticsData[command].avgTimeMeasurement = this.commandsStatistics.commandsStatisticsData[command].totalTimeMeasurement / this.commandsStatistics.commandsStatisticsData[command].countTimeMeasurement;
+ Array.isArray(this.commandsStatistics.commandsStatisticsData[command].timeMeasurementSeries) ? this.commandsStatistics.commandsStatisticsData[command].timeMeasurementSeries.push(duration) : this.commandsStatistics.commandsStatisticsData[command].timeMeasurementSeries = [duration] as CircularArray<number>;
+ this.commandsStatistics.commandsStatisticsData[command].medTimeMeasurement = this.median(this.commandsStatistics.commandsStatisticsData[command].timeMeasurementSeries);
}
private _logPrefix(): string {
- return Utils.logPrefix(` ${this.objName} Statistics:`);
+ return Utils.logPrefix(` ${this.objId} Statistics:`);
}
}