perf(simulator): use content addressable cache for idtag distribution
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 24 Mar 2023 21:19:06 +0000 (22:19 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 24 Mar 2023 21:19:06 +0000 (22:19 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AuthorizedTagsCache.ts
src/charging-station/AutomaticTransactionGenerator.ts

index 48c72fd4bb2399d8bdd199bc016c0085c7a32711..b63387eda9afcc9859ad6147d674163e9d2440cf 100644 (file)
@@ -1,6 +1,7 @@
 import fs from 'node:fs';
 
-import { FileType } from '../types';
+import { type ChargingStation, ChargingStationUtils } from './internal';
+import { FileType, IdTagDistribution } from '../types';
 import { FileUtils, Utils, logger } from '../utils';
 
 type TagsCacheValueType = {
@@ -11,9 +12,11 @@ type TagsCacheValueType = {
 export class AuthorizedTagsCache {
   private static instance: AuthorizedTagsCache | null = null;
   private readonly tagsCaches: Map<string, TagsCacheValueType>;
+  private readonly tagsCachesAddressableIndexes: Map<string, number>;
 
   private constructor() {
     this.tagsCaches = new Map<string, TagsCacheValueType>();
+    this.tagsCachesAddressableIndexes = new Map<string, number>();
   }
 
   public static getInstance(): AuthorizedTagsCache {
@@ -23,6 +26,27 @@ export class AuthorizedTagsCache {
     return AuthorizedTagsCache.instance;
   }
 
+  public getIdTag(
+    distribution: IdTagDistribution,
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): string {
+    const hashId = chargingStation.stationInfo.hashId;
+    const authorizationFile = ChargingStationUtils.getAuthorizationFile(
+      chargingStation.stationInfo
+    );
+    switch (distribution) {
+      case IdTagDistribution.RANDOM:
+        return this.getRandomIdTag(hashId, authorizationFile);
+      case IdTagDistribution.ROUND_ROBIN:
+        return this.getRoundRobinIdTag(hashId, authorizationFile);
+      case IdTagDistribution.CONNECTOR_AFFINITY:
+        return this.getConnectorAffinityIdTag(chargingStation, connectorId);
+      default:
+        return this.getRoundRobinIdTag(hashId, authorizationFile);
+    }
+  }
+
   public getAuthorizedTags(file: string): string[] | undefined {
     if (this.hasTags(file) === false) {
       this.setTags(file, this.getAuthorizedTagsFromFile(file));
@@ -34,6 +58,37 @@ export class AuthorizedTagsCache {
     return this.deleteTags(file);
   }
 
+  private getRandomIdTag(hashId: string, file: string): string {
+    const tags = this.getAuthorizedTags(file);
+    this.tagsCachesAddressableIndexes.set(
+      file + hashId,
+      Math.floor(Utils.secureRandom() * tags.length)
+    );
+    return tags[this.tagsCachesAddressableIndexes.get(file + hashId)];
+  }
+
+  private getRoundRobinIdTag(hashId: string, file: string): string {
+    const tags = this.getAuthorizedTags(file);
+    const idTagIndex = this.tagsCachesAddressableIndexes.get(file + hashId) ?? 0;
+    const idTag = tags[idTagIndex];
+    this.tagsCachesAddressableIndexes.set(
+      file + hashId,
+      idTagIndex === tags.length - 1 ? 0 : idTagIndex + 1
+    );
+    return idTag;
+  }
+
+  private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string {
+    const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo);
+    const tags = this.getAuthorizedTags(file);
+    const hashId = chargingStation.stationInfo.hashId;
+    this.tagsCachesAddressableIndexes.set(
+      file + hashId,
+      (chargingStation.index - 1 + (connectorId - 1)) % tags.length
+    );
+    return tags[this.tagsCachesAddressableIndexes.get(file + hashId)];
+  }
+
   private hasTags(file: string): boolean {
     return this.tagsCaches.has(file);
   }
@@ -53,6 +108,7 @@ export class AuthorizedTagsCache {
                 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
               );
               this.deleteTags(file);
+              this.deleteTagsIndexes(file);
             } catch (error) {
               FileUtils.handleFileException(
                 file,
@@ -79,6 +135,14 @@ export class AuthorizedTagsCache {
     return this.tagsCaches.delete(file);
   }
 
+  private deleteTagsIndexes(file: string): void {
+    for (const [key] of this.tagsCachesAddressableIndexes) {
+      if (key.startsWith(file)) {
+        this.tagsCachesAddressableIndexes.delete(key);
+      }
+    }
+  }
+
   private getAuthorizedTagsFromFile(file: string): string[] {
     let authorizedTags: string[] = [];
     if (file) {
index c6056dd53af52a539e1ef1e7bc472d5184d6281e..aa2012dfac33eae26a49dc86b688bda0e6d26c28 100644 (file)
@@ -2,7 +2,7 @@
 
 import { AsyncResource } from 'node:async_hooks';
 
-import { type ChargingStation, ChargingStationUtils } from './internal';
+import { AuthorizedTagsCache, type ChargingStation, ChargingStationUtils } from './internal';
 import { BaseError } from '../exception';
 // import { PerformanceStatistics } from '../performance';
 import { PerformanceStatistics } from '../performance/PerformanceStatistics';
@@ -12,7 +12,6 @@ import {
   type AuthorizeResponse,
   type AutomaticTransactionGeneratorConfiguration,
   ConnectorStatusEnum,
-  IdTagDistribution,
   RequestCommand,
   type StartTransactionRequest,
   type StartTransactionResponse,
@@ -325,7 +324,11 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     const beginId = PerformanceStatistics.beginMeasure(measureId);
     let startResponse: StartTransactionResponse;
     if (this.chargingStation.hasAuthorizedTags()) {
-      const idTag = this.getIdTag(connectorId);
+      const idTag = AuthorizedTagsCache.getInstance().getIdTag(
+        this.configuration?.idTagDistribution,
+        this.chargingStation,
+        connectorId
+      );
       const startTransactionLogMsg = `${this.logPrefix(
         connectorId
       )} start transaction with an idTag '${idTag}'`;
@@ -413,41 +416,6 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     return this.configuration?.requireAuthorize ?? true;
   }
 
-  private getRandomIdTag(authorizationFile: string): string {
-    const tags = this.chargingStation.authorizedTagsCache.getAuthorizedTags(authorizationFile);
-    this.idTagIndex = Math.floor(Utils.secureRandom() * tags.length);
-    return tags[this.idTagIndex];
-  }
-
-  private getRoundRobinIdTag(authorizationFile: string): string {
-    const tags = this.chargingStation.authorizedTagsCache.getAuthorizedTags(authorizationFile);
-    const idTag = tags[this.idTagIndex];
-    this.idTagIndex = this.idTagIndex === tags.length - 1 ? 0 : this.idTagIndex + 1;
-    return idTag;
-  }
-
-  private getConnectorAffinityIdTag(authorizationFile: string, connectorId: number): string {
-    const tags = this.chargingStation.authorizedTagsCache.getAuthorizedTags(authorizationFile);
-    this.idTagIndex = (this.chargingStation.index - 1 + (connectorId - 1)) % tags.length;
-    return tags[this.idTagIndex];
-  }
-
-  private getIdTag(connectorId: number): string {
-    const authorizationFile = ChargingStationUtils.getAuthorizationFile(
-      this.chargingStation.stationInfo
-    );
-    switch (this.configuration?.idTagDistribution) {
-      case IdTagDistribution.RANDOM:
-        return this.getRandomIdTag(authorizationFile);
-      case IdTagDistribution.ROUND_ROBIN:
-        return this.getRoundRobinIdTag(authorizationFile);
-      case IdTagDistribution.CONNECTOR_AFFINITY:
-        return this.getConnectorAffinityIdTag(authorizationFile, connectorId);
-      default:
-        return this.getRoundRobinIdTag(authorizationFile);
-    }
-  }
-
   private logPrefix = (connectorId?: number): string => {
     return Utils.logPrefix(
       ` ${this.chargingStation.stationInfo.chargingStationId} | ATG${