From e82376450bef7a755869accf8f662826147832d8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 2 Mar 2024 14:12:28 +0100 Subject: [PATCH] feat: expose template stats to UI server simulatorState command MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/Bootstrap.ts | 56 ++++++++++------------- src/types/Statistics.ts | 7 +++ src/types/UIProtocol.ts | 13 ++++++ src/types/index.ts | 11 ++++- src/utils/MessageChannelUtils.ts | 15 +++++- src/utils/index.ts | 1 + ui/web/src/types/UIProtocol.ts | 13 ++++++ ui/web/src/types/index.ts | 3 +- ui/web/src/views/ChargingStationsView.vue | 7 +-- 9 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 9247ab3d..f1f214b5 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -25,7 +25,9 @@ import { type ChargingStationWorkerMessageData, ChargingStationWorkerMessageEvents, ConfigurationSection, + type InternalTemplateStatistics, ProcedureName, + type SimulatorState, type Statistics, type StorageConfiguration, type UIServerConfiguration, @@ -34,6 +36,7 @@ import { import { Configuration, Constants, + buildTemplateStatisticsPayload, formatDurationMilliSeconds, generateUUID, handleUncaughtException, @@ -55,19 +58,12 @@ enum exitCodes { gracefulShutdownError = 4 } -interface TemplateChargingStations { - configured: number - added: number - started: number - indexes: Set -} - export class Bootstrap extends EventEmitter { private static instance: Bootstrap | null = null private workerImplementation?: WorkerAbstract private readonly uiServer: AbstractUIServer private storage?: Storage - private readonly templatesChargingStations: Map + private readonly templateStatistics: Map private readonly version: string = version private initializedCounters: boolean private started: boolean @@ -90,7 +86,7 @@ export class Bootstrap extends EventEmitter { this.uiServer = UIServerFactory.getUIServerImplementation( Configuration.getConfigurationSection(ConfigurationSection.uiServer) ) - this.templatesChargingStations = new Map() + this.templateStatistics = new Map() this.initializedCounters = false this.initializeCounters() Configuration.configurationChangeCallback = async () => { @@ -108,25 +104,27 @@ export class Bootstrap extends EventEmitter { } public get numberOfChargingStationTemplates (): number { - return this.templatesChargingStations.size + return this.templateStatistics.size } public get numberOfConfiguredChargingStations (): number { - return [...this.templatesChargingStations.values()].reduce( + return [...this.templateStatistics.values()].reduce( (accumulator, value) => accumulator + value.configured, 0 ) } - public getState (): { started: boolean } { + public getState (): SimulatorState { return { - started: this.started + version: this.version, + started: this.started, + templateStatistics: buildTemplateStatisticsPayload(this.templateStatistics) } } public getLastIndex (templateName: string): number { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const indexes = [...this.templatesChargingStations.get(templateName)!.indexes] + const indexes = [...this.templateStatistics.get(templateName)!.indexes] .concat(0) .sort((a, b) => a - b) for (let i = 0; i < indexes.length - 1; i++) { @@ -142,14 +140,14 @@ export class Bootstrap extends EventEmitter { } private get numberOfAddedChargingStations (): number { - return [...this.templatesChargingStations.values()].reduce( + return [...this.templateStatistics.values()].reduce( (accumulator, value) => accumulator + value.added, 0 ) } private get numberOfStartedChargingStations (): number { - return [...this.templatesChargingStations.values()].reduce( + return [...this.templateStatistics.values()].reduce( (accumulator, value) => accumulator + value.started, 0 ) @@ -211,7 +209,7 @@ export class Bootstrap extends EventEmitter { for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) { try { const nbStations = - this.templatesChargingStations.get(parse(stationTemplateUrl.file).name)?.configured ?? + this.templateStatistics.get(parse(stationTemplateUrl.file).name)?.configured ?? stationTemplateUrl.numberOfStations for (let index = 1; index <= nbStations; index++) { await this.addChargingStation(index, stationTemplateUrl.file) @@ -434,11 +432,9 @@ export class Bootstrap extends EventEmitter { private readonly workerEventDeleted = (data: ChargingStationData): void => { this.uiServer.chargingStations.delete(data.stationInfo.hashId) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const templateChargingStations = this.templatesChargingStations.get( - data.stationInfo.templateName - )! - --templateChargingStations.added - templateChargingStations.indexes.delete(data.stationInfo.templateIndex) + const templateStatistics = this.templateStatistics.get(data.stationInfo.templateName)! + --templateStatistics.added + templateStatistics.indexes.delete(data.stationInfo.templateIndex) logger.info( `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${ data.stationInfo.chargingStationId @@ -451,7 +447,7 @@ export class Bootstrap extends EventEmitter { private readonly workerEventStarted = (data: ChargingStationData): void => { this.uiServer.chargingStations.set(data.stationInfo.hashId, data) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ++this.templatesChargingStations.get(data.stationInfo.templateName)!.started + ++this.templateStatistics.get(data.stationInfo.templateName)!.started logger.info( `${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${ data.stationInfo.chargingStationId @@ -464,7 +460,7 @@ export class Bootstrap extends EventEmitter { private readonly workerEventStopped = (data: ChargingStationData): void => { this.uiServer.chargingStations.set(data.stationInfo.hashId, data) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - --this.templatesChargingStations.get(data.stationInfo.templateName)!.started + --this.templateStatistics.get(data.stationInfo.templateName)!.started logger.info( `${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${ data.stationInfo.chargingStationId @@ -500,7 +496,7 @@ export class Bootstrap extends EventEmitter { if (isNotEmptyArray(stationTemplateUrls)) { for (const stationTemplateUrl of stationTemplateUrls) { const templateName = parse(stationTemplateUrl.file).name - this.templatesChargingStations.set(templateName, { + this.templateStatistics.set(templateName, { configured: stationTemplateUrl.numberOfStations, added: 0, started: 0, @@ -508,7 +504,7 @@ export class Bootstrap extends EventEmitter { }) this.uiServer.chargingStationTemplates.add(templateName) } - if (this.templatesChargingStations.size !== stationTemplateUrls.length) { + if (this.templateStatistics.size !== stationTemplateUrls.length) { console.error( chalk.red( "'stationTemplateUrls' contains duplicate entries, please check your configuration" @@ -554,11 +550,9 @@ export class Bootstrap extends EventEmitter { options }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const templateChargingStations = this.templatesChargingStations.get( - parse(stationTemplateFile).name - )! - ++templateChargingStations.added - templateChargingStations.indexes.add(index) + const templateStatistics = this.templateStatistics.get(parse(stationTemplateFile).name)! + ++templateStatistics.added + templateStatistics.indexes.add(index) } private gracefulShutdown (): void { diff --git a/src/types/Statistics.ts b/src/types/Statistics.ts index 9bb614ee..0c7375d9 100644 --- a/src/types/Statistics.ts +++ b/src/types/Statistics.ts @@ -31,3 +31,10 @@ export type Statistics = { updatedAt?: Date statisticsData: Map } & WorkerData + +export interface InternalTemplateStatistics { + configured: number + added: number + started: number + indexes: Set +} diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index 37ec8ba9..b1b71b73 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -72,3 +72,16 @@ export interface ResponsePayload extends JsonObject { hashIdsFailed?: string[] responsesFailed?: BroadcastChannelResponsePayload[] } + +export interface TemplateStatistics extends JsonObject { + configured: number + added: number + started: number + indexes: number[] +} + +export interface SimulatorState extends JsonObject { + version: string + started: boolean + templateStatistics: Record +} diff --git a/src/types/index.ts b/src/types/index.ts index d5f887f8..f312480a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -9,7 +9,9 @@ export { ProtocolVersion, type RequestPayload, type ResponsePayload, - ResponseStatus + ResponseStatus, + type SimulatorState, + type TemplateStatistics } from './UIProtocol.js' export { type AutomaticTransactionGeneratorConfiguration, @@ -263,7 +265,12 @@ export type { export { OCPP20OptionalVariableName } from './ocpp/2.0/Variables.js' export { OCPPVersion } from './ocpp/OCPPVersion.js' export { PerformanceRecord } from './orm/entities/PerformanceRecord.js' -export type { Statistics, StatisticsData, TimestampedData } from './Statistics.js' +export type { + InternalTemplateStatistics, + Statistics, + StatisticsData, + TimestampedData +} from './Statistics.js' export { type WSError, WebSocketCloseEventStatusCode, diff --git a/src/utils/MessageChannelUtils.ts b/src/utils/MessageChannelUtils.ts index 36f797f5..65dcc9a8 100644 --- a/src/utils/MessageChannelUtils.ts +++ b/src/utils/MessageChannelUtils.ts @@ -4,12 +4,15 @@ import { buildConnectorsStatus, buildEvsesStatus } from './ChargingStationConfigurationUtils.js' +import { clone } from './Utils.js' import type { ChargingStation } from '../charging-station/index.js' import { type ChargingStationData, type ChargingStationWorkerMessage, ChargingStationWorkerMessageEvents, - type Statistics + type InternalTemplateStatistics, + type Statistics, + type TemplateStatistics } from '../types/index.js' export const buildAddedMessage = ( @@ -86,3 +89,13 @@ export const buildChargingStationDataPayload = ( }) } } + +export const buildTemplateStatisticsPayload = ( + map: Map +): Record => { + map = clone(map) + for (const value of map.values()) { + (value as unknown as TemplateStatistics).indexes = [...value.indexes] + } + return Object.fromEntries(map.entries() as unknown as Array<[string, TemplateStatistics]>) +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 21a650ef..1bc4fe7e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -24,6 +24,7 @@ export { buildPerformanceStatisticsMessage, buildStartedMessage, buildStoppedMessage, + buildTemplateStatisticsPayload, buildUpdatedMessage } from './MessageChannelUtils.js' export { diff --git a/ui/web/src/types/UIProtocol.ts b/ui/web/src/types/UIProtocol.ts index 18309895..5b3efcdc 100644 --- a/ui/web/src/types/UIProtocol.ts +++ b/ui/web/src/types/UIProtocol.ts @@ -57,3 +57,16 @@ export interface ResponsePayload extends JsonObject { status: ResponseStatus hashIds?: string[] } + +interface TemplateStatistics extends JsonObject { + configured: number + added: number + started: number + indexes: number[] +} + +export interface SimulatorState extends JsonObject { + version: string + started: boolean + templateStatistics: Record +} diff --git a/ui/web/src/types/index.ts b/ui/web/src/types/index.ts index a201ca7b..d730f0d6 100644 --- a/ui/web/src/types/index.ts +++ b/ui/web/src/types/index.ts @@ -15,5 +15,6 @@ export { ProtocolVersion, type RequestPayload, type ResponsePayload, - ResponseStatus + ResponseStatus, + type SimulatorState } from './UIProtocol' diff --git a/ui/web/src/views/ChargingStationsView.vue b/ui/web/src/views/ChargingStationsView.vue index b15ce3f5..2b9695e4 100644 --- a/ui/web/src/views/ChargingStationsView.vue +++ b/ui/web/src/views/ChargingStationsView.vue @@ -72,6 +72,7 @@ " > {{ state.simulatorState?.started === true ? 'Stop' : 'Start' }} Simulator + {{ state.simulatorState?.version != null ? ` (${state.simulatorState?.version})` : '' }} ({ renderSimulator: randomUUID(), @@ -170,7 +171,7 @@ const getSimulatorState = (): void => { uiClient .simulatorState() .then((response: ResponsePayload) => { - state.value.simulatorState = response.state as { started: boolean } + state.value.simulatorState = response.state as SimulatorState }) .catch((error: Error) => { $toast.error('Error at fetching simulator state') -- 2.34.1