From 2a370053f45f2d58e90ab7b292294c521a262ca1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 28 Aug 2021 09:14:11 +0200 Subject: [PATCH] Add MongDB support to storage for performance records. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Cleanup the storage API Signed-off-by: Jérôme Benoit --- package-lock.json | 210 ++++-------------- package.json | 2 +- rollup.config.js | 2 +- src/charging-station/Bootstrap.ts | 13 +- src/charging-station/ChargingStation.ts | 4 +- src/charging-station/ChargingStationWorker.ts | 4 +- src/types/Statistics.ts | 2 + src/types/Storage.ts | 4 + src/types/Worker.ts | 4 +- src/utils/Constants.ts | 4 + src/utils/PerformanceStatistics.ts | 9 +- .../performance-storage/JSONFileStorage.ts | 42 +++- .../performance-storage/MongoDBStorage.ts | 46 +++- src/utils/performance-storage/Storage.ts | 15 +- src/worker/WorkerSet.ts | 4 +- 15 files changed, 159 insertions(+), 206 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8a19cf5..72215921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1193,9 +1193,9 @@ "dev": true }, "@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.0.tgz", + "integrity": "sha512-pRP5cLIzN7I7Vp6mHKRSaZD7YpBTK7hawx5si8trMKqk4+WOdK8NEKOTO2G8PKWD1HbKMVckVB6/XHh/olhf2g==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -1203,42 +1203,6 @@ "@types/node": "*", "@types/yargs": "^16.0.0", "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@nearform/bubbleprof": { @@ -1907,9 +1871,9 @@ "dev": true }, "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, "@types/tar": { @@ -1964,18 +1928,18 @@ } }, "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -6319,16 +6283,16 @@ } }, "expect": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.6.tgz", - "integrity": "sha512-psNLt8j2kwg42jGBDSfAlU49CEZxejN1f1PlANWDZqIhBOVU/c2Pm888FcjWJzFewhIsNWfZJeLjUjtKGiPuSw==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.1.0.tgz", + "integrity": "sha512-9kJngV5hOJgkFil4F/uXm3hVBubUK2nERVfvqNNwxxuW8ZOUwSTTSysgfzckYtv/LBzj/LJXbiAF7okHCXgdug==", "dev": true, "requires": { - "@jest/types": "^27.0.6", + "@jest/types": "^27.1.0", "ansi-styles": "^5.0.0", "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.0.6", - "jest-message-util": "^27.0.6", + "jest-matcher-utils": "^27.1.0", + "jest-message-util": "^27.1.0", "jest-regex-util": "^27.0.6" }, "dependencies": { @@ -8259,51 +8223,15 @@ } }, "jest-diff": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", - "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.1.0.tgz", + "integrity": "sha512-rjfopEYl58g/SZTsQFmspBODvMSytL16I+cirnScWTLkQVXYVZfxm78DFfdIIXc05RCYuGjxJqrdyG4PIFzcJg==", "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^27.0.6", "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } + "pretty-format": "^27.1.0" } }, "jest-get-type": { @@ -8313,66 +8241,30 @@ "dev": true }, "jest-matcher-utils": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.6.tgz", - "integrity": "sha512-OFgF2VCQx9vdPSYTHWJ9MzFCehs20TsyFi6bIHbk5V1u52zJOnvF0Y/65z3GLZHKRuTgVPY4Z6LVePNahaQ+tA==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.1.0.tgz", + "integrity": "sha512-VmAudus2P6Yt/JVBRdTPFhUzlIN8DYJd+et5Rd9QDsO/Z82Z4iwGjo43U8Z+PTiz8CBvKvlb6Fh3oKy39hykkQ==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.0.6", + "jest-diff": "^27.1.0", "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } + "pretty-format": "^27.1.0" } }, "jest-message-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.6.tgz", - "integrity": "sha512-rBxIs2XK7rGy+zGxgi+UJKP6WqQ+KrBbD1YMj517HYN3v2BG66t3Xan3FWqYHKZwjdB700KiAJ+iES9a0M+ixw==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.1.0.tgz", + "integrity": "sha512-Eck8NFnJ5Sg36R9XguD65cf2D5+McC+NF5GIdEninoabcuoOfWrID5qJhufq5FB0DRKoiyxB61hS7MKoMD0trQ==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.0.6", + "@jest/types": "^27.1.0", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.4", - "pretty-format": "^27.0.6", + "pretty-format": "^27.1.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -8387,9 +8279,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", "dev": true }, "@babel/highlight": { @@ -8416,30 +8308,12 @@ } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -11455,12 +11329,12 @@ "dev": true }, "pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.1.0.tgz", + "integrity": "sha512-4aGaud3w3rxAO6OXmK3fwBFQ0bctIOG3/if+jYEFGNGIs0EvuidQm3bZ9mlP2/t9epLNC/12czabfy7TZNSwVA==", "dev": true, "requires": { - "@jest/types": "^27.0.6", + "@jest/types": "^27.1.0", "ansi-regex": "^5.0.0", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" diff --git a/package.json b/package.json index 0286220d..c7306ec1 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "eslint-plugin-import": "^2.24.2", "eslint-plugin-jsdoc": "^36.0.8", "eslint-plugin-node": "^11.1.0", - "expect": "^27.0.6", + "expect": "^27.1.0", "mocha": "^9.1.0", "mochawesome": "^6.2.2", "npm-check": "^5.9.2", diff --git a/rollup.config.js b/rollup.config.js index e6b710e8..14acfddd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -20,7 +20,7 @@ export default { preserveModulesRoot: 'src', ...!isDevelopmentBuild && { plugins: [terser({ numWorkers: 2 })] } }, - external: ['basic-ftp', 'chalk', 'crypto', 'perf_hooks', 'fs', 'path', 'poolifier', 'tar', 'url', 'uuid', 'ws', 'winston-daily-rotate-file', 'winston/lib/winston/transports', 'winston', 'worker_threads'], + external: ['basic-ftp', 'chalk', 'crypto', 'mongodb', 'perf_hooks', 'fs', 'path', 'poolifier', 'tar', 'url', 'uuid', 'ws', 'winston-daily-rotate-file', 'winston/lib/winston/transports', 'winston', 'worker_threads'], plugins: [ json(), typescript({ diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 89f81e75..6bcc8511 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -1,4 +1,4 @@ -import { ChargingStationWorkerData, WorkerEvents, WorkerMessage } from '../types/Worker'; +import { ChargingStationWorkerData, WorkerMessage, WorkerMessageEvents } from '../types/Worker'; import Configuration from '../utils/Configuration'; import { Storage } from '../utils/performance-storage/Storage'; @@ -38,6 +38,7 @@ export default class Bootstrap { if (isMainThread && !this.started) { try { let numStationsTotal = 0; + await Bootstrap.storage.open(); await Bootstrap.workerImplementation.start(); // Start ChargingStation object in worker thread if (Configuration.getStationTemplateURLs()) { @@ -74,6 +75,7 @@ export default class Bootstrap { public async stop(): Promise { if (isMainThread && this.started) { await Bootstrap.workerImplementation.stop(); + await Bootstrap.storage.close(); } this.started = false; } @@ -84,7 +86,7 @@ export default class Bootstrap { await this.start(); } - private initWorkerImplementation() { + private initWorkerImplementation(): void { Bootstrap.workerImplementation = WorkerFactory.getWorkerImplementation(this.workerScript, Configuration.getWorkerProcess(), { startDelay: Configuration.getWorkerStartDelay(), @@ -94,9 +96,10 @@ export default class Bootstrap { poolOptions: { workerChoiceStrategy: Configuration.getWorkerPoolStrategy() } - }, (msg: WorkerMessage) => { - if (msg.id === WorkerEvents.PERFORMANCE_STATISTICS) { - Bootstrap.storage.storePerformanceStatistics(msg.data); + // eslint-disable-next-line @typescript-eslint/no-misused-promises + }, async (msg: WorkerMessage) => { + if (msg.id === WorkerMessageEvents.PERFORMANCE_STATISTICS) { + await Bootstrap.storage.storePerformanceStatistics(msg.data); } }); } diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index dbabf1e5..ebb53a9f 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -555,7 +555,7 @@ export default class ChargingStation { } this.stationInfo.powerDivider = this.getPowerDivider(); if (this.getEnableStatistics()) { - this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId); + this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId, this.wsConnectionUrl); } } @@ -1021,7 +1021,7 @@ export default class ChargingStation { return !Utils.isUndefined(this.stationInfo.reconnectExponentialDelay) ? this.stationInfo.reconnectExponentialDelay : false; } - private async reconnect(error: any): Promise { + private async reconnect(error: unknown): Promise { // Stop WebSocket ping this.stopWebSocketPing(); // Stop heartbeat diff --git a/src/charging-station/ChargingStationWorker.ts b/src/charging-station/ChargingStationWorker.ts index 20a812e4..81e0296d 100644 --- a/src/charging-station/ChargingStationWorker.ts +++ b/src/charging-station/ChargingStationWorker.ts @@ -1,6 +1,6 @@ // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. -import { ChargingStationWorkerData, WorkerEvents, WorkerMessage } from '../types/Worker'; +import { ChargingStationWorkerData, WorkerMessage, WorkerMessageEvents } from '../types/Worker'; import { parentPort, workerData } from 'worker_threads'; import ChargingStation from './ChargingStation'; @@ -25,7 +25,7 @@ if (Utils.workerPoolInUse()) { */ function addMessageListener(): void { parentPort?.on('message', (message: WorkerMessage) => { - if (message.id === WorkerEvents.START_WORKER_ELEMENT) { + if (message.id === WorkerMessageEvents.START_WORKER_ELEMENT) { startChargingStation(message.data); } }); diff --git a/src/types/Statistics.ts b/src/types/Statistics.ts index 7999e14a..86a86758 100644 --- a/src/types/Statistics.ts +++ b/src/types/Statistics.ts @@ -1,4 +1,5 @@ import { CircularArray } from '../utils/CircularArray'; +import { URL } from 'url'; export interface StatisticsData { countRequest: number; @@ -18,6 +19,7 @@ export interface StatisticsData { export default interface Statistics { id: string; + URI: string; createdAt: Date; lastUpdatedAt?: Date; statisticsData: Record; diff --git a/src/types/Storage.ts b/src/types/Storage.ts index 32a6b22f..c20bef20 100644 --- a/src/types/Storage.ts +++ b/src/types/Storage.ts @@ -3,3 +3,7 @@ export enum StorageType { MONGO_DB = 'mongodb' } +export enum DBType { + MONGO_DB = 'MongoDB' +} + diff --git a/src/types/Worker.ts b/src/types/Worker.ts index b0fe6e76..c4ad7052 100644 --- a/src/types/Worker.ts +++ b/src/types/Worker.ts @@ -29,11 +29,11 @@ export interface WorkerSetElement { } export interface WorkerMessage { - id: WorkerEvents; + id: WorkerMessageEvents; data: any; } -export enum WorkerEvents { +export enum WorkerMessageEvents { START_WORKER_ELEMENT = 'startWorkerElement', STOP_WORKER_ELEMENT = 'stopWorkerElement', PERFORMANCE_STATISTICS = 'performanceStatistics' diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index d497628a..8323da34 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -45,4 +45,8 @@ export default class Constants { MeterValueMeasurand.POWER_ACTIVE_IMPORT, MeterValueMeasurand.CURRENT_IMPORT, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER]); static readonly DEFAULT_FLUCTUATION_PERCENT = 5; + + static readonly PERFORMANCE_RECORDS_FILETYPE = 'Performance records'; + static readonly DEFAULT_PERFORMANCE_RECORDS_DB_NAME = 'charging-stations-simulator'; + static readonly PERFORMANCE_RECORDS_TABLE = 'performanceRecords'; } diff --git a/src/utils/PerformanceStatistics.ts b/src/utils/PerformanceStatistics.ts index 4049197a..5d55cfcf 100644 --- a/src/utils/PerformanceStatistics.ts +++ b/src/utils/PerformanceStatistics.ts @@ -7,8 +7,9 @@ import Statistics, { StatisticsData } from '../types/Statistics'; import Configuration from './Configuration'; import { MessageType } from '../types/ocpp/MessageType'; +import { URL } from 'url'; import Utils from './Utils'; -import { WorkerEvents } from '../types/Worker'; +import { WorkerMessageEvents } from '../types/Worker'; import logger from './Logger'; import { parentPort } from 'worker_threads'; @@ -18,10 +19,10 @@ export default class PerformanceStatistics { private statistics: Statistics; private displayInterval: NodeJS.Timeout; - public constructor(objId: string) { + public constructor(objId: string, URI: URL) { this.objId = objId; this.initializePerformanceObserver(); - this.statistics = { id: this.objId ?? 'Object id not specified', createdAt: new Date(), statisticsData: {} }; + this.statistics = { id: this.objId ?? 'Object id not specified', URI: URI.toString(), createdAt: new Date(), statisticsData: {} }; } public static beginMeasure(id: string): string { @@ -190,7 +191,7 @@ export default class PerformanceStatistics { this.statistics.statisticsData[entryName].ninetyFiveThPercentileTimeMeasurement = this.percentile(this.statistics.statisticsData[entryName].timeMeasurementSeries, 95); this.statistics.statisticsData[entryName].stdDevTimeMeasurement = this.stdDeviation(this.statistics.statisticsData[entryName].timeMeasurementSeries); if (Configuration.getPerformanceStorage().enabled) { - parentPort.postMessage({ id: WorkerEvents.PERFORMANCE_STATISTICS, data: this.statistics }); + parentPort.postMessage({ id: WorkerMessageEvents.PERFORMANCE_STATISTICS, data: this.statistics }); } } diff --git a/src/utils/performance-storage/JSONFileStorage.ts b/src/utils/performance-storage/JSONFileStorage.ts index 60e54437..ed1b122c 100644 --- a/src/utils/performance-storage/JSONFileStorage.ts +++ b/src/utils/performance-storage/JSONFileStorage.ts @@ -1,3 +1,4 @@ +import Constants from '../Constants'; import FileUtils from '../FileUtils'; import Statistics from '../../types/Statistics'; import { Storage } from './Storage'; @@ -5,35 +6,52 @@ import fs from 'fs'; import path from 'path'; export class JSONFileStorage extends Storage { + private fd: number | null = null; + constructor(storageURI: string, logPrefix: string) { super(storageURI, logPrefix); + this.dbName = path.join(path.resolve(__dirname, '../../../'), this.storageURI.pathname.replace(/(?:^\/)|(?:\/$)/g, '')); } public storePerformanceStatistics(performanceStatistics: Statistics): void { - const performanceJSONFilePath = path.join(path.resolve(__dirname, '../../../'), this.storageURI.pathname.replace(/(?:^\/)|(?:\/$)/g, '')); - if (!fs.existsSync(performanceJSONFilePath)) { - this.open(performanceJSONFilePath); - } - fs.readFile(performanceJSONFilePath, 'utf-8', (error, data) => { + this.checkPerformanceRecordsFile(); + fs.readFile(this.dbName, 'utf-8', (error, data) => { if (error) { - FileUtils.handleFileException(this.logPrefix, 'Performance measurements', performanceJSONFilePath, error); + FileUtils.handleFileException(this.logPrefix, Constants.PERFORMANCE_RECORDS_FILETYPE, this.dbName, error); } else { - const performanceRecords: Statistics[] = data ? JSON.parse(data.toString()) as Statistics[] : []; + const performanceRecords: Statistics[] = data ? JSON.parse(data) as Statistics[] : []; performanceRecords.push(performanceStatistics); - fs.writeFile(performanceJSONFilePath, JSON.stringify(performanceRecords, null, 2), 'utf-8', (err) => { + fs.writeFile(this.dbName, JSON.stringify(performanceRecords, null, 2), 'utf-8', (err) => { if (err) { - FileUtils.handleFileException(this.logPrefix, 'Performance measurements', performanceJSONFilePath, err); + FileUtils.handleFileException(this.logPrefix, Constants.PERFORMANCE_RECORDS_FILETYPE, this.dbName, err); } }); } }); } - private open(filePath: string): void { + public open(): void { try { - fs.openSync(filePath, 'w+'); + this.fd = fs.openSync(this.dbName, 'a+'); } catch (error) { - FileUtils.handleFileException(this.logPrefix, 'Performance measurements', filePath, error); + FileUtils.handleFileException(this.logPrefix, Constants.PERFORMANCE_RECORDS_FILETYPE, this.dbName, error); + } + } + + public close(): void { + try { + if (this.fd) { + fs.closeSync(this.fd); + this.fd = null; + } + } catch (error) { + FileUtils.handleFileException(this.logPrefix, Constants.PERFORMANCE_RECORDS_FILETYPE, this.dbName, error); + } + } + + private checkPerformanceRecordsFile(): void { + if (!this.fd) { + throw new Error(`${this.logPrefix} Performance records '${this.dbName}' file descriptor not found`); } } } diff --git a/src/utils/performance-storage/MongoDBStorage.ts b/src/utils/performance-storage/MongoDBStorage.ts index a47984c1..56689a56 100644 --- a/src/utils/performance-storage/MongoDBStorage.ts +++ b/src/utils/performance-storage/MongoDBStorage.ts @@ -1,16 +1,54 @@ +import Constants from '../Constants'; +import { DBType } from '../../types/Storage'; +import { MongoClient } from 'mongodb'; import Statistics from '../../types/Statistics'; import { Storage } from './Storage'; export class MongoDBStorage extends Storage { + private client: MongoClient; + private connected: boolean; + constructor(storageURI: string, logPrefix: string) { super(storageURI, logPrefix); + this.client = new MongoClient(this.storageURI.toString()); + this.connected = false; + this.dbName = this.storageURI.pathname.replace(/(?:^\/)|(?:\/$)/g, '') ?? Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME; } - public storePerformanceStatistics(performanceStatistics: Statistics): void { - throw new Error('Method not yet implemented'); + public async storePerformanceStatistics(performanceStatistics: Statistics): Promise { + try { + this.checkDBConnection(); + await this.client.db(this.dbName).collection(Constants.PERFORMANCE_RECORDS_TABLE).insertOne(performanceStatistics); + } catch (error) { + this.handleDBError(DBType.MONGO_DB, error, Constants.PERFORMANCE_RECORDS_TABLE); + } } - private open(): void {} + public async open(): Promise { + try { + if (!this.connected) { + await this.client.connect(); + this.connected = true; + } + } catch (error) { + this.handleDBError(DBType.MONGO_DB, error); + } + } - private close(): void {} + public async close(): Promise { + try { + if (this.connected) { + await this.client.close(); + this.connected = false; + } + } catch (error) { + this.handleDBError(DBType.MONGO_DB, error); + } + } + + private checkDBConnection() { + if (!this.connected) { + throw new Error(`${this.logPrefix} ${DBType.MONGO_DB} connection not opened while trying to issue a request`); + } + } } diff --git a/src/utils/performance-storage/Storage.ts b/src/utils/performance-storage/Storage.ts index 890dce7a..b8922375 100644 --- a/src/utils/performance-storage/Storage.ts +++ b/src/utils/performance-storage/Storage.ts @@ -1,14 +1,23 @@ +import { DBType } from '../../types/Storage'; import Statistics from '../../types/Statistics'; import { URL } from 'url'; +import logger from '../Logger'; export abstract class Storage { - protected storageURI: URL; - protected logPrefix: string; + protected readonly storageURI: URL; + protected readonly logPrefix: string; + protected dbName: string; constructor(storageURI: string, logPrefix: string) { this.storageURI = new URL(storageURI); this.logPrefix = logPrefix; } - public abstract storePerformanceStatistics(performanceStatistics: Statistics): void; + protected handleDBError(DBEngine: DBType, error: Error, table?: string): void { + logger.error(`${this.logPrefix} ${DBEngine} error${table && ` in table or collection ${table}`} %j`, error); + } + + public abstract open(): void | Promise; + public abstract close(): void | Promise; + public abstract storePerformanceStatistics(performanceStatistics: Statistics): void | Promise; } diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index 1b82f1d7..9b9ad332 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -1,6 +1,6 @@ // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. -import { WorkerEvents, WorkerSetElement } from '../types/Worker'; +import { WorkerMessageEvents, WorkerSetElement } from '../types/Worker'; import Utils from '../utils/Utils'; import { Worker } from 'worker_threads'; @@ -44,7 +44,7 @@ export default class WorkerSet extends WorkerAbstract { // Start worker sequentially to optimize memory at startup await Utils.sleep(this.workerStartDelay); } - this.getLastWorker().postMessage({ id: WorkerEvents.START_WORKER_ELEMENT, data: elementData }); + this.getLastWorker().postMessage({ id: WorkerMessageEvents.START_WORKER_ELEMENT, data: elementData }); this.getLastWorkerSetElement().numberOfWorkerElements++; } -- 2.34.1