From cfa3a2615d106411fb3deabd0ffbc4c8ad8f09dd Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Fri, 9 Aug 2024 15:49:00 +0100 Subject: [PATCH 1/6] Updated class name --- .../harness/ff/examples/EventExampleWithFeatureSnapshot.java | 4 ++-- src/main/java/io/harness/cf/client/api/InnerClient.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java b/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java index 261b1704..3c04728c 100644 --- a/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java +++ b/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java @@ -16,8 +16,8 @@ import java.util.concurrent.TimeUnit; @Slf4j -public class EventExamplePoC { - private static final String SDK_KEY = ""; +public class EventExampleWithFeatureSnapshot { + private static final String SDK_KEY = "44a8523b-ecf8-44f7-9134-91a42ba7afda"; private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private static CfClient client; diff --git a/src/main/java/io/harness/cf/client/api/InnerClient.java b/src/main/java/io/harness/cf/client/api/InnerClient.java index 55192fdb..32862344 100644 --- a/src/main/java/io/harness/cf/client/api/InnerClient.java +++ b/src/main/java/io/harness/cf/client/api/InnerClient.java @@ -319,7 +319,7 @@ public List getFeatureSnapshots() { } public List getFeatureSnapshots(String prefix) { - if (!options.isEnableFeatureSnapshot()){ + if (!options.isEnableFeatureSnapshot()) { log.debug("FeatureSnapshot disabled, snapshot will contain only current version."); } List identifiers = repository.getAllFeatureIdentifiers(prefix); @@ -334,7 +334,7 @@ public List getFeatureSnapshots(String prefix) { } public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) { - if (!options.isEnableFeatureSnapshot()){ + if (!options.isEnableFeatureSnapshot()) { log.debug("FeatureSnapshot disabled, snapshot will contain only current version."); } Optional ofc = repository.getCurrentAndPreviousFeatureConfig(identifier); From 4a3b4cd266155ecbf16650285c06b91c123bad73 Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Mon, 12 Aug 2024 16:12:59 +0100 Subject: [PATCH 2/6] Attempted to add benchmark test wip --- build.gradle | 5 + .../EventExampleWithFeatureSnapshot.java | 2 +- .../cf/client/api/StorageRepositoryTest.java | 16 ++- .../client/api/StoreRepositoryBenchmark.java | 107 ++++++++++++++++++ .../io/harness/cf/client/api/TestUtils.java | 39 +++++++ 5 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java diff --git a/build.gradle b/build.gradle index 8f6296a8..87aa10de 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ plugins { id 'jacoco-report-aggregation' id "com.github.spotbugs" version libs.versions.spotbugs id "org.owasp.dependencycheck" version libs.versions.depcheck + id 'me.champeau.jmh' version '0.6.8' // Added JMH plugin } @@ -55,6 +56,10 @@ dependencies { compileOnly libs.lombok annotationProcessor libs.lombok + + // JMH dependencies + implementation 'org.openjdk.jmh:jmh-core:1.37' + annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' } group = 'io.harness' diff --git a/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java b/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java index 3c04728c..dcf25ed3 100644 --- a/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java +++ b/examples/src/main/java/io/harness/ff/examples/EventExampleWithFeatureSnapshot.java @@ -17,7 +17,7 @@ @Slf4j public class EventExampleWithFeatureSnapshot { - private static final String SDK_KEY = "44a8523b-ecf8-44f7-9134-91a42ba7afda"; + private static final String SDK_KEY = ""; private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private static CfClient client; diff --git a/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java b/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java index 0cbd3ed2..10a7f419 100644 --- a/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java +++ b/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java @@ -20,9 +20,6 @@ class StorageRepositoryTest { private final Gson gson = new Gson(); - // initialize the config and load the flags. - // load the test - @Test void shouldInitialiseRepo() { final Repository repository = new StorageRepository(new CaffeineCache(10000), null, false); @@ -269,4 +266,17 @@ private FeatureConfig GetFeatureConfigFromFile() throws Exception { } return null; } + + private List createBenchmarkData(int flagNumber, int version) throws Exception { + FeatureConfig fg = GetFeatureConfigFromFile(); + List list = new LinkedList(); + for (int i = 1; i <= flagNumber; i++) { + FeatureConfig f = fg; + f.setFeature("simpleBool" + i); + f.setVersion(new Long(version)); + list.add(f); + } + // System.out.println(list); + return list; + } } diff --git a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java new file mode 100644 index 00000000..ad0497bf --- /dev/null +++ b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java @@ -0,0 +1,107 @@ +package io.harness.cf.client.api; + +import com.google.gson.Gson; +import io.harness.cf.model.FeatureConfig; +import java.util.List; +import org.openjdk.jmh.annotations.*; + +/* + How to run it. + ./gradlew clean build + ./gradlew jmh -Pjmh.include=BenchmarkClassName.BenchmarkMethodName + + e.g. + ./gradlew jmh -Pjmh.include=StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly + ./gradlew jmh -Pjmh.include=StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent +*/ + +@State(Scope.Thread) +public class StoreRepositoryBenchmark { + + private Repository currentOnlyRepo; + private Repository previousAndCurrentRepo; + private List featureConfigs; + private List updatedFeatureConfigs; + private final Gson gson = new Gson(); + + @Setup + public void setup() throws Exception { + setupRepoWithCurrentOnlyRepository(); + setupRepoWithCurrentAndPreviousRepository(); + } + + @Setup + public void setupRepoWithCurrentOnlyRepository() throws Exception { + + TestUtils tu = new TestUtils(); + currentOnlyRepo = new StorageRepository(new CaffeineCache(10000), null, false); + + // Define the sample size for benchmarking + int sampleSize = 1000; + + // Create benchmark data + featureConfigs = tu.CreateBenchmarkData(sampleSize, 1); + updatedFeatureConfigs = tu.CreateBenchmarkData(sampleSize, 2); + + // Ensure data is created correctly + if (featureConfigs == null || featureConfigs.size() != sampleSize) { + throw new IllegalStateException("Feature configs setup failed."); + } + if (updatedFeatureConfigs == null || updatedFeatureConfigs.size() != sampleSize) { + throw new IllegalStateException("Updated feature configs setup failed."); + } + + // Initial loading of feature configurations + loadFlags(currentOnlyRepo, featureConfigs); + } + + @Setup + public void setupRepoWithCurrentAndPreviousRepository() throws Exception { + + TestUtils tu = new TestUtils(); + previousAndCurrentRepo = new StorageRepository(new CaffeineCache(10000), null, true); + + // Define the sample size for benchmarking + int sampleSize = 1000; + + // Create benchmark data + featureConfigs = tu.CreateBenchmarkData(sampleSize, 1); + updatedFeatureConfigs = tu.CreateBenchmarkData(sampleSize, 2); + + // Ensure data is created correctly + if (featureConfigs == null || featureConfigs.size() != sampleSize) { + throw new IllegalStateException("Feature configs setup failed."); + } + if (updatedFeatureConfigs == null || updatedFeatureConfigs.size() != sampleSize) { + throw new IllegalStateException("Updated feature configs setup failed."); + } + + // Initial loading of feature configurations + loadFlags(previousAndCurrentRepo, featureConfigs); + } + + @Fork(value = 1, warmups = 1) + @Benchmark + public void BenchmarkLoadFeatureConfigCurrentOnly() { + // Measure time taken to load the updated feature configurations while storing only current + // snapshot + loadFlags(currentOnlyRepo, updatedFeatureConfigs); + } + + + @Fork(value = 1, warmups = 1) + @Benchmark + public void BenchmarkLoadFeatureConfigPreviousAndCurrent() { + // Measure time taken to load the updated feature configurations while storing only current + // snapshot as well as keeping previous snapshot. + loadFlags(previousAndCurrentRepo, updatedFeatureConfigs); + } + + private void loadFlags(Repository repository, List flags) { + if (flags != null) { + for (FeatureConfig nextFlag : flags) { + repository.setFlag(nextFlag.getFeature(), nextFlag); + } + } + } +} diff --git a/src/test/java/io/harness/cf/client/api/TestUtils.java b/src/test/java/io/harness/cf/client/api/TestUtils.java index 98894632..c1a5bcfd 100644 --- a/src/test/java/io/harness/cf/client/api/TestUtils.java +++ b/src/test/java/io/harness/cf/client/api/TestUtils.java @@ -1,10 +1,15 @@ package io.harness.cf.client.api; +import com.google.gson.Gson; +import io.harness.cf.model.FeatureConfig; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.LinkedList; +import java.util.List; public class TestUtils { @@ -28,4 +33,38 @@ public static String getJsonResource(String location) throws IOException, URISyn final Path path = Paths.get(EvaluatorTest.class.getClassLoader().getResource(location).toURI()); return new String(Files.readAllBytes(path)); } + + private final Gson gson = new Gson(); + + public List CreateBenchmarkData(int size, int version) throws Exception { + FeatureConfig fg = GetFeatureConfigFromFile(); + List list = new LinkedList(); + for (int i = 1; i <= size; i++) { + FeatureConfig f = fg; + f.setFeature("simpleBool" + i); + f.setVersion(new Long(version)); + list.add(f); + } + // System.out.println(list); + return list; + } + + public FeatureConfig GetFeatureConfigFromFile() throws Exception { + try { + String relativePath = + "./src/test/resources/local-test-cases/basic_bool_string_for_repository.json"; + // Resolve the absolute path + String filePath = new File(relativePath).getCanonicalPath(); + // Read the content of the file into a String + String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); + + final FeatureConfig featureConfig = gson.fromJson(jsonString, FeatureConfig.class); + return featureConfig; + + } catch (IOException e) { + // Handle exceptions like file not found or read errors + e.printStackTrace(); + } + return null; + } } From 74babcbb0991e2801cfb21138683576761da2e39 Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Tue, 13 Aug 2024 09:32:34 +0100 Subject: [PATCH 3/6] Further twicking --- .../client/api/StoreRepositoryBenchmark.java | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java index ad0497bf..8373f5fa 100644 --- a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java +++ b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import io.harness.cf.model.FeatureConfig; import java.util.List; +import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; /* @@ -23,9 +24,15 @@ public class StoreRepositoryBenchmark { private List featureConfigs; private List updatedFeatureConfigs; private final Gson gson = new Gson(); + private final int FeatureConfigSize = 10000; + private final int CacheSize = FeatureConfigSize * 2; @Setup public void setup() throws Exception { + TestUtils tu = new TestUtils(); + featureConfigs = tu.CreateBenchmarkData(FeatureConfigSize, 1); + updatedFeatureConfigs = tu.CreateBenchmarkData(FeatureConfigSize, 2); + setupRepoWithCurrentOnlyRepository(); setupRepoWithCurrentAndPreviousRepository(); } @@ -34,23 +41,7 @@ public void setup() throws Exception { public void setupRepoWithCurrentOnlyRepository() throws Exception { TestUtils tu = new TestUtils(); - currentOnlyRepo = new StorageRepository(new CaffeineCache(10000), null, false); - - // Define the sample size for benchmarking - int sampleSize = 1000; - - // Create benchmark data - featureConfigs = tu.CreateBenchmarkData(sampleSize, 1); - updatedFeatureConfigs = tu.CreateBenchmarkData(sampleSize, 2); - - // Ensure data is created correctly - if (featureConfigs == null || featureConfigs.size() != sampleSize) { - throw new IllegalStateException("Feature configs setup failed."); - } - if (updatedFeatureConfigs == null || updatedFeatureConfigs.size() != sampleSize) { - throw new IllegalStateException("Updated feature configs setup failed."); - } - + currentOnlyRepo = new StorageRepository(new CaffeineCache(CacheSize), null, false); // Initial loading of feature configurations loadFlags(currentOnlyRepo, featureConfigs); } @@ -59,41 +50,28 @@ public void setupRepoWithCurrentOnlyRepository() throws Exception { public void setupRepoWithCurrentAndPreviousRepository() throws Exception { TestUtils tu = new TestUtils(); - previousAndCurrentRepo = new StorageRepository(new CaffeineCache(10000), null, true); - - // Define the sample size for benchmarking - int sampleSize = 1000; - - // Create benchmark data - featureConfigs = tu.CreateBenchmarkData(sampleSize, 1); - updatedFeatureConfigs = tu.CreateBenchmarkData(sampleSize, 2); - - // Ensure data is created correctly - if (featureConfigs == null || featureConfigs.size() != sampleSize) { - throw new IllegalStateException("Feature configs setup failed."); - } - if (updatedFeatureConfigs == null || updatedFeatureConfigs.size() != sampleSize) { - throw new IllegalStateException("Updated feature configs setup failed."); - } - + previousAndCurrentRepo = new StorageRepository(new CaffeineCache(CacheSize), null, true); // Initial loading of feature configurations loadFlags(previousAndCurrentRepo, featureConfigs); } @Fork(value = 1, warmups = 1) + @OutputTimeUnit(TimeUnit.MILLISECONDS) @Benchmark + @BenchmarkMode(Mode.AverageTime) public void BenchmarkLoadFeatureConfigCurrentOnly() { - // Measure time taken to load the updated feature configurations while storing only current - // snapshot + // Measure average time taken to load the updated feature configurations while storing only + // current snapshot loadFlags(currentOnlyRepo, updatedFeatureConfigs); } - @Fork(value = 1, warmups = 1) + @OutputTimeUnit(TimeUnit.MILLISECONDS) @Benchmark + @BenchmarkMode(Mode.AverageTime) public void BenchmarkLoadFeatureConfigPreviousAndCurrent() { - // Measure time taken to load the updated feature configurations while storing only current - // snapshot as well as keeping previous snapshot. + // Measure average time taken to load the updated feature configurations while storing current + // config as well as keeping previous one. loadFlags(previousAndCurrentRepo, updatedFeatureConfigs); } From 4ff30512fd9809d4db920e642397571a255b13ec Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Tue, 13 Aug 2024 09:40:38 +0100 Subject: [PATCH 4/6] Added example results in comments. --- .../cf/client/api/StoreRepositoryBenchmark.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java index 8373f5fa..253b0d68 100644 --- a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java +++ b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java @@ -9,11 +9,13 @@ /* How to run it. ./gradlew clean build - ./gradlew jmh -Pjmh.include=BenchmarkClassName.BenchmarkMethodName + ./gradlew jmh - e.g. - ./gradlew jmh -Pjmh.include=StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly - ./gradlew jmh -Pjmh.include=StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent + some results: + Benchmark Mode Cnt Score Error Units + StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly avgt 5 1.160 ± 0.059 ms/op + StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent avgt 5 1.224 ± 0.025 ms/op + */ @State(Scope.Thread) From aea7e1b14566508b9bed0a442cebb8ff4c73d50e Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Tue, 13 Aug 2024 14:51:40 +0100 Subject: [PATCH 5/6] Code and test refactoring. --- .../io/harness/cf/client/api/InnerClient.java | 15 +-- .../java/io/harness/cf/client/api/Query.java | 3 +- .../cf/client/api/StorageRepository.java | 32 ++++++ .../cf/client/api/StorageRepositoryTest.java | 47 +++----- .../client/api/StoreRepositoryBenchmark.java | 107 ++++++++++++++++-- .../io/harness/cf/client/api/TestUtils.java | 4 +- 6 files changed, 154 insertions(+), 54 deletions(-) diff --git a/src/main/java/io/harness/cf/client/api/InnerClient.java b/src/main/java/io/harness/cf/client/api/InnerClient.java index 32862344..bec1ed88 100644 --- a/src/main/java/io/harness/cf/client/api/InnerClient.java +++ b/src/main/java/io/harness/cf/client/api/InnerClient.java @@ -1,6 +1,5 @@ package io.harness.cf.client.api; -import com.google.gson.Gson; import com.google.gson.JsonObject; import io.harness.cf.client.common.SdkCodes; import io.harness.cf.client.connector.*; @@ -11,7 +10,6 @@ import io.harness.cf.model.Variation; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -337,18 +335,7 @@ public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) { if (!options.isEnableFeatureSnapshot()) { log.debug("FeatureSnapshot disabled, snapshot will contain only current version."); } - Optional ofc = repository.getCurrentAndPreviousFeatureConfig(identifier); - FeatureSnapshot result = new FeatureSnapshot(); - if (ofc.isPresent()) { - FeatureConfig[] fc = ofc.get(); - result.setPrevious(fc[0]); - result.setCurrent(fc[1]); - } - // this is here to create a deep copy of the object before its returned. - // this way we protect the cache. - Gson gson = new Gson(); - FeatureSnapshot deepCopySnapshot = gson.fromJson(gson.toJson(result), FeatureSnapshot.class); - return deepCopySnapshot; + return repository.getFeatureSnapshot(identifier); } public void on(@NonNull final Event event, @NonNull final Consumer consumer) { diff --git a/src/main/java/io/harness/cf/client/api/Query.java b/src/main/java/io/harness/cf/client/api/Query.java index ca59d369..b8a09f4d 100644 --- a/src/main/java/io/harness/cf/client/api/Query.java +++ b/src/main/java/io/harness/cf/client/api/Query.java @@ -1,6 +1,7 @@ package io.harness.cf.client.api; import io.harness.cf.model.FeatureConfig; +import io.harness.cf.model.FeatureSnapshot; import io.harness.cf.model.Segment; import java.util.List; import java.util.Optional; @@ -14,7 +15,7 @@ public interface Query { List findFlagsBySegment(@NonNull String identifier); - Optional getCurrentAndPreviousFeatureConfig(@NonNull String identifier); + FeatureSnapshot getFeatureSnapshot(@NonNull String identifier); List getAllFeatureIdentifiers(String prefix); } diff --git a/src/main/java/io/harness/cf/client/api/StorageRepository.java b/src/main/java/io/harness/cf/client/api/StorageRepository.java index cf1a1ece..0b819b1e 100644 --- a/src/main/java/io/harness/cf/client/api/StorageRepository.java +++ b/src/main/java/io/harness/cf/client/api/StorageRepository.java @@ -1,5 +1,6 @@ package io.harness.cf.client.api; +import com.google.gson.Gson; import io.harness.cf.client.common.Cache; import io.harness.cf.client.common.Storage; import io.harness.cf.client.common.Utils; @@ -98,6 +99,37 @@ public Optional getCurrentAndPreviousFeatureConfig(@NonNull Str return Optional.empty(); } + public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) { + Gson gson = new Gson(); + final String flagKey = formatFlagKey(identifier); + final String pFlagKey = formatPrevFlagKey(identifier); + + FeatureConfig pFlag = (FeatureConfig) cache.get(pFlagKey); + FeatureConfig cFlag = (FeatureConfig) cache.get(flagKey); + + if (cFlag != null) { + FeatureSnapshot deepCopySnapshot = + gson.fromJson(gson.toJson(new FeatureSnapshot(cFlag, pFlag)), FeatureSnapshot.class); + return deepCopySnapshot; + } + // if we don't have it in cache we check the file + if (this.store != null) { + pFlag = (FeatureConfig) store.get(pFlagKey); + cFlag = (FeatureConfig) store.get(flagKey); + if (pFlag != null) { + cache.set(pFlagKey, pFlag); + } + if (cFlag != null) { + cache.set(flagKey, cFlag); + } + + FeatureSnapshot deepCopySnapshot = + gson.fromJson(gson.toJson(new FeatureSnapshot(cFlag, pFlag)), FeatureSnapshot.class); + return deepCopySnapshot; + } + return null; + } + public Optional getSegment(@NonNull String identifier, boolean cacheable) { final String segmentKey = formatSegmentKey(identifier); Segment segment = (Segment) cache.get(segmentKey); diff --git a/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java b/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java index 10a7f419..27c77aa3 100644 --- a/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java +++ b/src/test/java/io/harness/cf/client/api/StorageRepositoryTest.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import io.harness.cf.model.FeatureConfig; +import io.harness.cf.model.FeatureSnapshot; import java.awt.*; import java.io.File; import java.io.IOException; @@ -12,7 +13,6 @@ import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.stream.Stream; import lombok.NonNull; import org.junit.jupiter.api.Test; @@ -40,12 +40,10 @@ void shouldStoreCurrentConfig() throws Exception { loadFlags(repository, makeFeatureList(featureConfig)); loadFlags(repository, makeFeatureList(featureConfigUpdated)); - Optional res = - repository.getCurrentAndPreviousFeatureConfig(featureConfigUpdated.getFeature()); - FeatureConfig[] resFc = res.get(); + FeatureSnapshot res = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - FeatureConfig previous = resFc[0]; - FeatureConfig current = resFc[1]; + FeatureConfig previous = res.getPrevious(); + FeatureConfig current = res.getCurrent(); // check if previous version is null assertNull(previous); @@ -76,12 +74,10 @@ void shouldStoreCurrentConfigWithFileStore() throws Exception { loadFlags(repository, makeFeatureList(featureConfig)); loadFlags(repository, makeFeatureList(featureConfigUpdated)); - Optional res = - repository.getCurrentAndPreviousFeatureConfig(featureConfigUpdated.getFeature()); - FeatureConfig[] resFc = res.get(); + FeatureSnapshot res = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - FeatureConfig previous = resFc[0]; - FeatureConfig current = resFc[1]; + FeatureConfig previous = res.getPrevious(); + FeatureConfig current = res.getCurrent(); // check if previous version is null assertNull(previous); @@ -112,12 +108,10 @@ void shouldStorePreviousAndCurrentConfigWithFileStore() throws Exception { loadFlags(repository, makeFeatureList(featureConfig)); loadFlags(repository, makeFeatureList(featureConfigUpdated)); - Optional res = - repository.getCurrentAndPreviousFeatureConfig(featureConfigUpdated.getFeature()); - FeatureConfig[] resFc = res.get(); + FeatureSnapshot res = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - FeatureConfig previous = resFc[0]; - FeatureConfig current = resFc[1]; + FeatureConfig previous = res.getPrevious(); + FeatureConfig current = res.getCurrent(); // check if previous version is null assertNotNull(previous); @@ -142,12 +136,10 @@ void shouldStorePreviousAndCurrentConfig() throws Exception { loadFlags(repository, makeFeatureList(featureConfig)); loadFlags(repository, makeFeatureList(featureConfigUpdated)); - Optional res = - repository.getCurrentAndPreviousFeatureConfig(featureConfigUpdated.getFeature()); - FeatureConfig[] resFc = res.get(); + FeatureSnapshot res = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - FeatureConfig previous = resFc[0]; - FeatureConfig current = resFc[1]; + FeatureConfig previous = res.getPrevious(); + FeatureConfig current = res.getCurrent(); // check if previous version is null assertNotNull(previous); @@ -174,12 +166,10 @@ void shouldDeletePreviousAndCurrentConfig() throws Exception { String featureIdentifier = featureConfig.getFeature(); - Optional res = - repository.getCurrentAndPreviousFeatureConfig(featureIdentifier); - FeatureConfig[] resFc = res.get(); + FeatureSnapshot res = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - FeatureConfig previous = resFc[0]; - FeatureConfig current = resFc[1]; + FeatureConfig previous = res.getPrevious(); + FeatureConfig current = res.getCurrent(); // check if previous version is null assertNotNull(previous); @@ -191,10 +181,9 @@ void shouldDeletePreviousAndCurrentConfig() throws Exception { // delete config repository.deleteFlag(featureIdentifier); - Optional result = - repository.getCurrentAndPreviousFeatureConfig(featureIdentifier); + FeatureSnapshot result = repository.getFeatureSnapshot(featureConfigUpdated.getFeature()); - assertFalse(result.isPresent(), "The Optional should be empty"); + assertNull(result, "The Optional should be empty"); } @Test diff --git a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java index 253b0d68..ef01d96a 100644 --- a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java +++ b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java @@ -2,6 +2,8 @@ import com.google.gson.Gson; import io.harness.cf.model.FeatureConfig; +import io.harness.cf.model.FeatureSnapshot; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; @@ -15,14 +17,17 @@ Benchmark Mode Cnt Score Error Units StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly avgt 5 1.160 ± 0.059 ms/op StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent avgt 5 1.224 ± 0.025 ms/op - + */ @State(Scope.Thread) public class StoreRepositoryBenchmark { - private Repository currentOnlyRepo; - private Repository previousAndCurrentRepo; + private Repository setCurrentOnlyRepository; + private Repository setCurrentAndPreviousRepository; + + private Repository getSnapshotCurrentOnlyRepository; + private Repository getSnapshotCurrentAndPreviousRepository; private List featureConfigs; private List updatedFeatureConfigs; private final Gson gson = new Gson(); @@ -37,24 +42,48 @@ public void setup() throws Exception { setupRepoWithCurrentOnlyRepository(); setupRepoWithCurrentAndPreviousRepository(); + setupRepoWithCurrentOnlyRepositoryForGetSnapshot(); + setupRepoWithCurrentAndPreviousRepositoryForGetSnapshot(); } @Setup public void setupRepoWithCurrentOnlyRepository() throws Exception { TestUtils tu = new TestUtils(); - currentOnlyRepo = new StorageRepository(new CaffeineCache(CacheSize), null, false); + setCurrentOnlyRepository = new StorageRepository(new CaffeineCache(CacheSize), null, false); // Initial loading of feature configurations - loadFlags(currentOnlyRepo, featureConfigs); + loadFlags(setCurrentOnlyRepository, featureConfigs); } @Setup public void setupRepoWithCurrentAndPreviousRepository() throws Exception { TestUtils tu = new TestUtils(); - previousAndCurrentRepo = new StorageRepository(new CaffeineCache(CacheSize), null, true); + setCurrentAndPreviousRepository = + new StorageRepository(new CaffeineCache(CacheSize), null, true); + // Initial loading of feature configurations + loadFlags(setCurrentAndPreviousRepository, featureConfigs); + } + + @Setup + public void setupRepoWithCurrentOnlyRepositoryForGetSnapshot() throws Exception { + + TestUtils tu = new TestUtils(); + getSnapshotCurrentOnlyRepository = + new StorageRepository(new CaffeineCache(CacheSize), null, false); + // Initial loading of feature configurations + loadFlags(getSnapshotCurrentOnlyRepository, featureConfigs); + loadFlags(getSnapshotCurrentOnlyRepository, updatedFeatureConfigs); + } + + @Setup + public void setupRepoWithCurrentAndPreviousRepositoryForGetSnapshot() throws Exception { + TestUtils tu = new TestUtils(); + getSnapshotCurrentAndPreviousRepository = + new StorageRepository(new CaffeineCache(CacheSize), null, true); // Initial loading of feature configurations - loadFlags(previousAndCurrentRepo, featureConfigs); + loadFlags(getSnapshotCurrentAndPreviousRepository, featureConfigs); + loadFlags(getSnapshotCurrentAndPreviousRepository, updatedFeatureConfigs); } @Fork(value = 1, warmups = 1) @@ -64,7 +93,7 @@ public void setupRepoWithCurrentAndPreviousRepository() throws Exception { public void BenchmarkLoadFeatureConfigCurrentOnly() { // Measure average time taken to load the updated feature configurations while storing only // current snapshot - loadFlags(currentOnlyRepo, updatedFeatureConfigs); + loadFlags(setCurrentOnlyRepository, updatedFeatureConfigs); } @Fork(value = 1, warmups = 1) @@ -74,7 +103,67 @@ public void BenchmarkLoadFeatureConfigCurrentOnly() { public void BenchmarkLoadFeatureConfigPreviousAndCurrent() { // Measure average time taken to load the updated feature configurations while storing current // config as well as keeping previous one. - loadFlags(previousAndCurrentRepo, updatedFeatureConfigs); + loadFlags(setCurrentAndPreviousRepository, updatedFeatureConfigs); + } + + @Fork(value = 1, warmups = 1) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void BenchmarkGetFeatureSnapshotsCurrentOnly() { + // Measures time taken to get snapshot + List snapshots = getFeatureSnapshots(getSnapshotCurrentOnlyRepository); + if (snapshots == null) { + throw new IllegalStateException("Snapshots are null"); + } + if (snapshots.size() != FeatureConfigSize) { + throw new IllegalStateException("Snapshots not equal"); + } + + for (int i = 0; i < FeatureConfigSize; i++) { + FeatureSnapshot fs = snapshots.get(i); + if (fs.getPrevious() != null) { + throw new IllegalStateException("Snapshots contains previous"); + } + if (fs.getCurrent() == null) { + throw new IllegalStateException("Snapshots does not contain current"); + } + } + } + + @Fork(value = 1, warmups = 1) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void BenchmarkGetFeatureSnapshotsPreviousAndCurrent() { + // Measures time taken to get snapshot + List snapshots = getFeatureSnapshots(getSnapshotCurrentAndPreviousRepository); + if (snapshots == null) { + throw new IllegalStateException("Snapshots are null"); + } + if (snapshots.size() != FeatureConfigSize) { + throw new IllegalStateException("Snapshots not equal"); + } + + for (int i = 0; i < FeatureConfigSize; i++) { + FeatureSnapshot fs = snapshots.get(i); + if (fs.getPrevious() == null) { + throw new IllegalStateException("Snapshots does not contain previous"); + } + if (fs.getCurrent() == null) { + throw new IllegalStateException("Snapshots does not contain current"); + } + } + } + + private List getFeatureSnapshots(Repository repository) { + List identifiers = repository.getAllFeatureIdentifiers(""); + List snapshots = new LinkedList<>(); + for (String identifier : identifiers) { + FeatureSnapshot snapshot = repository.getFeatureSnapshot(identifier); + snapshots.add(snapshot); + } + return snapshots; } private void loadFlags(Repository repository, List flags) { diff --git a/src/test/java/io/harness/cf/client/api/TestUtils.java b/src/test/java/io/harness/cf/client/api/TestUtils.java index c1a5bcfd..8f406e8c 100644 --- a/src/test/java/io/harness/cf/client/api/TestUtils.java +++ b/src/test/java/io/harness/cf/client/api/TestUtils.java @@ -43,7 +43,9 @@ public List CreateBenchmarkData(int size, int version) throws Exc FeatureConfig f = fg; f.setFeature("simpleBool" + i); f.setVersion(new Long(version)); - list.add(f); + // we are copying objects + FeatureConfig df = gson.fromJson(gson.toJson(f), FeatureConfig.class); + list.add(df); } // System.out.println(list); return list; From f0b1722467e3563b9eec1f3d48d8af8dd57db7d1 Mon Sep 17 00:00:00 2001 From: Arek Szostak Date: Wed, 14 Aug 2024 10:10:29 +0100 Subject: [PATCH 6/6] Added results --- .../cf/client/api/StoreRepositoryBenchmark.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java index ef01d96a..f34ccc63 100644 --- a/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java +++ b/src/test/java/io/harness/cf/client/api/StoreRepositoryBenchmark.java @@ -14,9 +14,11 @@ ./gradlew jmh some results: - Benchmark Mode Cnt Score Error Units - StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly avgt 5 1.160 ± 0.059 ms/op - StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent avgt 5 1.224 ± 0.025 ms/op + Benchmark Mode Cnt Score Error Units + StoreRepositoryBenchmark.BenchmarkGetFeatureSnapshotsCurrentOnly avgt 5 805.511 ± 105.154 ms/op + StoreRepositoryBenchmark.BenchmarkGetFeatureSnapshotsPreviousAndCurrent avgt 5 860.745 ± 61.876 ms/op + StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigCurrentOnly avgt 5 1.502 ± 0.198 ms/op + StoreRepositoryBenchmark.BenchmarkLoadFeatureConfigPreviousAndCurrent avgt 5 2.071 ± 0.650 ms/op */ @@ -111,7 +113,7 @@ public void BenchmarkLoadFeatureConfigPreviousAndCurrent() { @Benchmark @BenchmarkMode(Mode.AverageTime) public void BenchmarkGetFeatureSnapshotsCurrentOnly() { - // Measures time taken to get snapshot + // Measures time taken to get snapshots List snapshots = getFeatureSnapshots(getSnapshotCurrentOnlyRepository); if (snapshots == null) { throw new IllegalStateException("Snapshots are null"); @@ -136,7 +138,7 @@ public void BenchmarkGetFeatureSnapshotsCurrentOnly() { @Benchmark @BenchmarkMode(Mode.AverageTime) public void BenchmarkGetFeatureSnapshotsPreviousAndCurrent() { - // Measures time taken to get snapshot + // Measures time taken to get snapshots List snapshots = getFeatureSnapshots(getSnapshotCurrentAndPreviousRepository); if (snapshots == null) { throw new IllegalStateException("Snapshots are null");