511a4d7a452afa6cd651bc3deae8f8426af830b9
[e-mobility-charging-stations-simulator.git] / src / charging-station / Bootstrap.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import { ChargingStationWorkerData, ChargingStationWorkerMessage, ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker';
4
5 import Configuration from '../utils/Configuration';
6 import { Storage } from '../performance/storage/Storage';
7 import { StorageFactory } from '../performance/storage/StorageFactory';
8 import Utils from '../utils/Utils';
9 import WorkerAbstract from '../worker/WorkerAbstract';
10 import WorkerFactory from '../worker/WorkerFactory';
11 import chalk from 'chalk';
12 import { isMainThread } from 'worker_threads';
13 import path from 'path';
14 import { version } from '../../package.json';
15
16 export default class Bootstrap {
17 private static instance: Bootstrap | null = null;
18 private static workerImplementation: WorkerAbstract | null = null;
19 private static storage: Storage;
20 private static numberOfChargingStations: number;
21 private version: string = version;
22 private started: boolean;
23 private workerScript: string;
24
25 private constructor() {
26 this.started = false;
27 this.workerScript = path.join(path.resolve(__dirname, '../'), 'charging-station', 'ChargingStationWorker.js');
28 this.initWorkerImplementation();
29 Bootstrap.storage = StorageFactory.getStorage(Configuration.getPerformanceStorage().type, Configuration.getPerformanceStorage().URI, this.logPrefix());
30 Configuration.setConfigurationChangeCallback(async () => Bootstrap.getInstance().restart());
31 }
32
33 public static getInstance(): Bootstrap {
34 if (!Bootstrap.instance) {
35 Bootstrap.instance = new Bootstrap();
36 }
37 return Bootstrap.instance;
38 }
39
40 public async start(): Promise<void> {
41 if (isMainThread && !this.started) {
42 try {
43 Bootstrap.numberOfChargingStations = 0;
44 await Bootstrap.storage.open();
45 await Bootstrap.workerImplementation.start();
46 // Start ChargingStation object in worker thread
47 if (Configuration.getStationTemplateURLs()) {
48 for (const stationURL of Configuration.getStationTemplateURLs()) {
49 try {
50 const nbStations = stationURL.numberOfStations ?? 0;
51 for (let index = 1; index <= nbStations; index++) {
52 const workerData: ChargingStationWorkerData = {
53 index,
54 templateFile: path.join(path.resolve(__dirname, '../'), 'assets', 'station-templates', path.basename(stationURL.file))
55 };
56 await Bootstrap.workerImplementation.addElement(workerData);
57 Bootstrap.numberOfChargingStations++;
58 }
59 } catch (error) {
60 console.error(chalk.red('Charging station start with template file ' + stationURL.file + ' error '), error);
61 }
62 }
63 } else {
64 console.warn(chalk.yellow('No stationTemplateURLs defined in configuration, exiting'));
65 }
66 if (Bootstrap.numberOfChargingStations === 0) {
67 console.warn(chalk.yellow('No charging station template enabled in configuration, exiting'));
68 } else {
69 console.log(chalk.green(`Charging stations simulator ${this.version} started with ${Bootstrap.numberOfChargingStations.toString()} charging station(s) and ${Utils.workerDynamicPoolInUse() ? `${Configuration.getWorkerPoolMinSize().toString()}/` : ''}${Bootstrap.workerImplementation.size}${Utils.workerPoolInUse() ? `/${Configuration.getWorkerPoolMaxSize().toString()}` : ''} worker(s) concurrently running in '${Configuration.getWorkerProcess()}' mode${Bootstrap.workerImplementation.maxElementsPerWorker ? ` (${Bootstrap.workerImplementation.maxElementsPerWorker} charging station(s) per worker)` : ''}`));
70 }
71 this.started = true;
72 } catch (error) {
73 console.error(chalk.red('Bootstrap start error '), error);
74 }
75 } else {
76 console.error(chalk.red('Cannot start an already started charging stations simulator'));
77 }
78 }
79
80 public async stop(): Promise<void> {
81 if (isMainThread && this.started) {
82 await Bootstrap.workerImplementation.stop();
83 await Bootstrap.storage.close();
84 } else {
85 console.error(chalk.red('Trying to stop the charging stations simulator while not started'));
86 }
87 this.started = false;
88 }
89
90 public async restart(): Promise<void> {
91 await this.stop();
92 this.initWorkerImplementation();
93 await this.start();
94 }
95
96 private initWorkerImplementation(): void {
97 Bootstrap.workerImplementation = WorkerFactory.getWorkerImplementation<ChargingStationWorkerData>(this.workerScript, Configuration.getWorkerProcess(),
98 {
99 startDelay: Configuration.getWorkerStartDelay(),
100 poolMaxSize: Configuration.getWorkerPoolMaxSize(),
101 poolMinSize: Configuration.getWorkerPoolMinSize(),
102 elementsPerWorker: Configuration.getChargingStationsPerWorker(),
103 poolOptions: {
104 workerChoiceStrategy: Configuration.getWorkerPoolStrategy()
105 },
106 messageHandler: async (msg: ChargingStationWorkerMessage) => {
107 if (msg.id === ChargingStationWorkerMessageEvents.PERFORMANCE_STATISTICS) {
108 await Bootstrap.storage.storePerformanceStatistics(msg.data);
109 }
110 }
111 });
112 }
113
114 private logPrefix(): string {
115 return Utils.logPrefix(' Bootstrap |');
116 }
117 }