diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5c05c305..2aafb314f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,10 +35,13 @@ mvn test -P e2e There is a small JMH benchmark suite for testing allocations that can be run with: ```sh -mvn -P benchmark test-compile jmh:benchmark -Djmh.f=1 -Djmh.prof='dev.openfeature.sdk.benchmark.AllocationProfiler' +mvn -P benchmark clean compile test-compile jmh:benchmark -Djmh.f=1 -Djmh.prof='dev.openfeature.sdk.benchmark.AllocationProfiler' ``` If you are concerned about the repercussions of a change on memory usage, run this an compare the results to the committed. `benchmark.txt` file. +Note that the ONLY MEANINGFUL RESULTS of this benchmark are the `totalAllocatedBytes` and the `totalAllocatedInstances`. +The `run` score, and maven task time are not relevant since this benchmark is purely memory-related and has nothing to do with speed. +You can also view the heap breakdown to see which objects are taking up the most memory. ## Releasing diff --git a/benchmark.txt b/benchmark.txt index d028a3f87..e43e684d0 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -36,9 +36,9 @@ Audit done. processing is enabled explicitly (-proc:only, -proc:full). Use -Xlint:-options to suppress this message. Use -proc:none to disable annotation processing. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. [WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/MutableStructure.java:[19,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. [WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java:[27,26] finalize() in java.lang.Object has been deprecated and marked for removal [INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Some input files use or override a deprecated API. [INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Recompile with -Xlint:deprecation for details. @@ -129,139 +129,139 @@ Audit done. [0.001s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups Iteration 1: num #instances #bytes class name (module) ------------------------------------------------------- - 1: 1407606 67565088 java.util.HashMap (java.base@21.0.4) - 2: 780500 45970160 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 3: 1152020 36864640 java.util.HashMap$Node (java.base@21.0.4) - 4: 860065 13761040 java.util.HashMap$EntrySet (java.base@21.0.4) - 5: 690006 11040096 dev.openfeature.sdk.Value - 6: 47842 9731792 [B (java.base@21.0.4) - 7: 305994 8105936 [Ljava.lang.Object; (java.base@21.0.4) - 8: 360370 5765920 dev.openfeature.sdk.ImmutableStructure - 9: 350370 5605920 dev.openfeature.sdk.ImmutableContext - 10: 100000 4000000 dev.openfeature.sdk.HookContext - 11: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 12: 116586 2798064 java.util.ArrayList (java.base@21.0.4) - 13: 174 2171368 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 14: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 15: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 16: 62056 1985792 java.util.HashMap$EntryIterator (java.base@21.0.4) - 17: 118262 1892192 java.util.Optional (java.base@21.0.4) - 18: 41643 1665720 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 19: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 20: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000070b4d802fa78 - 21: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 22: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000070b4d8082a18 - 23: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 24: 29392 940544 java.util.ArrayList$Itr (java.base@21.0.4) - 25: 57139 914224 dev.openfeature.sdk.ImmutableMetadata - 26: 34240 821760 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 27: 48782 780512 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 28: 4491 721464 [I (java.base@21.0.4) - 29: 26608 638592 java.lang.String (java.base@21.0.4) - 30: 1461 390008 [J (java.base@21.0.4) - 31: 7139 342672 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 32: 18300 292800 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000070b4d802eae8 - 33: 2357 288328 java.lang.Class (java.base@21.0.4) - 34: 4651 260456 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 35: 10001 240024 java.lang.Double (java.base@21.0.4) - 36: 9315 223560 dev.openfeature.sdk.HookSupport$$Lambda/0x000070b4d8081bb8 - 37: 7921 190104 dev.openfeature.sdk.HookSupport$$Lambda/0x000070b4d8081988 - 38: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) - 39: 6011 144264 java.lang.StringBuilder (java.base@21.0.4) - 40: 181 142008 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 41: 3829 122528 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) - 42: 48 122168 [C (java.base@21.0.4) - 43: 1440 113512 [S (java.base@21.0.4) - 44: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 45: 3035 79672 [Ljava.lang.Class; (java.base@21.0.4) - 46: 1353 75768 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 47: 1570 75360 java.lang.invoke.MemberName (java.base@21.0.4) - 48: 336 75264 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 49: 1807 72280 java.lang.invoke.MethodType (java.base@21.0.4) - 50: 1089 69696 java.net.URL (java.base@21.0.4) - 51: 3159 50544 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) - 52: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) - 53: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) - 54: 1225 39200 java.io.File (java.base@21.0.4) - 55: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) - 56: 795 25272 [Ljava.lang.String; (java.base@21.0.4) - 57: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) - 58: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) - 59: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) - 60: 475 22800 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) - 61: 692 22144 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) - 62: 832 19968 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) - 63: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) - 64: 119 18088 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) - 65: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) - 66: 625 15000 java.lang.Long (java.base@21.0.4) - 67: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) - 68: 904 14464 java.lang.Object (java.base@21.0.4) - 69: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) - 70: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) - 71: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) - 72: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) - 73: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) - 74: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) - 75: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) - 76: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) - 77: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) - 78: 467 11208 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) - 79: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) - 80: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) - 81: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) - 82: 223 10704 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) - 83: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) - 84: 266 10640 java.security.CodeSource (java.base@21.0.4) - 85: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) - 86: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) - 87: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) - 88: 118 9440 jdk.internal.event.DeserializationEvent (java.base@21.0.4) - 89: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) - 90: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) - 91: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) - 92: 146 8176 java.io.FileCleanable (java.base@21.0.4) - 93: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) - 94: 122 7808 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) - 95: 324 7776 java.util.ImmutableCollections$Set12 (java.base@21.0.4) - 96: 71 7384 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) - 97: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) - 98: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) - 99: 202 6464 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) - 100: 156 6240 java.util.StringJoiner (java.base@21.0.4) - 101: 153 6120 java.io.FileDescriptor (java.base@21.0.4) - 102: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) - 103: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) - 104: 375 6000 java.lang.Byte (java.base@21.0.4) - 105: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) - 106: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) - 107: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) - 108: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) - 109: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) - 110: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) - 111: 98 5488 java.lang.Module (java.base@21.0.4) - 112: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) - 113: 65 5200 java.net.URI (java.base@21.0.4) - 114: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) - 115: 158 5056 java.lang.invoke.MethodTypeForm (java.base@21.0.4) - 116: 152 4864 java.nio.file.attribute.FileTime (java.base@21.0.4) - 117: 301 4816 java.util.HashSet (java.base@21.0.4) - 118: 75 4800 java.util.zip.Inflater (java.base@21.0.4) + 1: 480234 23051232 java.util.HashMap (java.base@21.0.4) + 2: 150497 12050088 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 3: 332017 10624544 java.util.HashMap$Node (java.base@21.0.4) + 4: 47815 9732480 [B (java.base@21.0.4) + 5: 305991 8105872 [Ljava.lang.Object; (java.base@21.0.4) + 6: 366682 5866912 java.util.Optional (java.base@21.0.4) + 7: 183332 5866624 java.util.HashMap$EntryIterator (java.base@21.0.4) + 8: 172970 5535040 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 9: 100000 4000000 dev.openfeature.sdk.HookContext + 10: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 11: 230006 3680096 dev.openfeature.sdk.Value + 12: 200062 3200992 java.util.HashMap$EntrySet (java.base@21.0.4) + 13: 132870 3188880 java.util.ArrayList (java.base@21.0.4) + 14: 192292 3076672 dev.openfeature.sdk.ImmutableStructure + 15: 182292 2916672 dev.openfeature.sdk.ImmutableContext + 16: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 17: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 18: 122968 1967488 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) + 19: 149 1884376 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 20: 56476 1807232 java.util.ArrayList$Itr (java.base@21.0.4) + 21: 37481 1799088 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 22: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000076e79c02fa78 + 23: 50000 1600000 [Ldev.openfeature.sdk.EvaluationContext; + 24: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 25: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000076e79c082800 + 26: 36720 1468800 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 27: 87481 1399696 dev.openfeature.sdk.ImmutableMetadata + 28: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 29: 74201 1187216 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 30: 73235 1171760 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) + 31: 45869 1100856 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) + 32: 43776 1050624 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 33: 40016 960384 dev.openfeature.sdk.HookSupport$$Lambda/0x000076e79c081b78 + 34: 39967 959208 dev.openfeature.sdk.HookSupport$$Lambda/0x000076e79c081da8 + 35: 57783 924528 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000076e79c02eae8 + 36: 4490 721440 [I (java.base@21.0.4) + 37: 26594 638256 java.lang.String (java.base@21.0.4) + 38: 1461 390008 [J (java.base@21.0.4) + 39: 2361 288784 java.lang.Class (java.base@21.0.4) + 40: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 41: 10001 240024 java.lang.Double (java.base@21.0.4) + 42: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 43: 6007 144168 java.lang.StringBuilder (java.base@21.0.4) + 44: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 45: 3827 122464 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 46: 48 122168 [C (java.base@21.0.4) + 47: 1440 113512 [S (java.base@21.0.4) + 48: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 49: 3031 79600 [Ljava.lang.Class; (java.base@21.0.4) + 50: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 51: 1561 74928 java.lang.invoke.MemberName (java.base@21.0.4) + 52: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 53: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) + 54: 1089 69696 java.net.URL (java.base@21.0.4) + 55: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) + 56: 3147 50352 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 57: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) + 58: 1225 39200 java.io.File (java.base@21.0.4) + 59: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) + 60: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) + 61: 794 25248 [Ljava.lang.String; (java.base@21.0.4) + 62: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) + 63: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) + 64: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) + 65: 690 22080 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 66: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) + 67: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) + 68: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) + 69: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) + 70: 625 15000 java.lang.Long (java.base@21.0.4) + 71: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) + 72: 904 14464 java.lang.Object (java.base@21.0.4) + 73: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) + 74: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) + 75: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) + 76: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) + 77: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) + 78: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) + 79: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) + 80: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) + 81: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) + 82: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) + 83: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) + 84: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) + 85: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) + 86: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) + 87: 266 10640 java.security.CodeSource (java.base@21.0.4) + 88: 221 10608 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) + 89: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) + 90: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) + 91: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 92: 118 9440 jdk.internal.event.DeserializationEvent (java.base@21.0.4) + 93: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) + 94: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) + 95: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) + 96: 146 8176 java.io.FileCleanable (java.base@21.0.4) + 97: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) + 98: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 99: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 100: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 101: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 102: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 103: 199 6368 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 104: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 105: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 106: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 107: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) + 108: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) + 109: 373 5968 java.lang.Byte (java.base@21.0.4) + 110: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 111: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 112: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 113: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 114: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 115: 98 5488 java.lang.Module (java.base@21.0.4) + 116: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) + 117: 65 5200 java.net.URI (java.base@21.0.4) + 118: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) truncated... -Total 7264791 244216640 +Total 4452140 139359040 -0.166 s/op - +totalAllocatedBytes: 244216640.000 bytes - +totalAllocatedInstances: 7264791.000 instances +0.186 s/op + +totalAllocatedBytes: 139359040.000 bytes + +totalAllocatedInstances: 4452140.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 244216640.000 bytes + 139359040.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 7264791.000 instances + 4452140.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -282,13 +282,13 @@ different JVMs are already problematic, the performance difference caused by dif modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. Benchmark Mode Cnt Score Error Units -AllocationBenchmark.run ss 0.166 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 244216640.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 7264791.000 instances +AllocationBenchmark.run ss 0.186 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 139359040.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 4452140.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 8.760 s -[INFO] Finished at: 2024-10-23T12:22:21-04:00 +[INFO] Total time: 8.280 s +[INFO] Finished at: 2024-10-23T12:37:24-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index 13a6cf6cb..86fdde41a 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Collections; @SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) abstract class AbstractStructure implements Structure { @@ -18,7 +19,15 @@ public boolean isEmpty() { } AbstractStructure(Map attributes) { - this.attributes = new HashMap<>(attributes); + this.attributes = attributes; + } + + /** + * Returns an unmodifiable representation of the internal attribute map. + * @return immutable map + */ + public Map asUnmodifiableMap() { + return Collections.unmodifiableMap(attributes); } /** diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index b95ea454d..5b2a33113 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -1,5 +1,9 @@ package dev.openfeature.sdk; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + /** * The EvaluationContext is a container for arbitrary contextual data * that can be used as a basis for dynamic evaluation. @@ -19,4 +23,39 @@ public interface EvaluationContext extends Structure { * @return resulting merged context */ EvaluationContext merge(EvaluationContext overridingContext); + + /** + * Recursively merges the overriding map into the base Value map. + * The base map is mutated, the overriding map is not. + * Null maps will cause no-op. + * + * @param newStructure function to create the right structure(s) for Values + * @param base base map to merge + * @param overriding overriding map to merge + */ + static void mergeMaps(Function, Structure> newStructure, + Map base, + Map overriding) { + + if (base == null) { + return; + } + if (overriding == null || overriding.isEmpty()) { + return; + } + + for (Entry overridingEntry : overriding.entrySet()) { + String key = overridingEntry.getKey(); + if (overridingEntry.getValue().isStructure() && base.containsKey(key) && base.get(key).isStructure()) { + Structure mergedValue = base.get(key).asStructure(); + Structure overridingValue = overridingEntry.getValue().asStructure(); + Map newMap = mergedValue.asMap(); + mergeMaps(newStructure, newMap, + overridingValue.asUnmodifiableMap()); + base.put(key, new Value(newStructure.apply(newMap))); + } else { + base.put(key, overridingEntry.getValue()); + } + } + } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index 9b27cdd59..d0dae6051 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; + import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import lombok.ToString; import lombok.experimental.Delegate; @@ -10,7 +11,8 @@ /** * The EvaluationContext is a container for arbitrary contextual data * that can be used as a basis for dynamic evaluation. - * The ImmutableContext is an EvaluationContext implementation which is threadsafe, and whose attributes can + * The ImmutableContext is an EvaluationContext implementation which is + * threadsafe, and whose attributes can * not be modified after instantiation. */ @ToString @@ -21,7 +23,8 @@ public final class ImmutableContext implements EvaluationContext { private final ImmutableStructure structure; /** - * Create an immutable context with an empty targeting_key and attributes provided. + * Create an immutable context with an empty targeting_key and attributes + * provided. */ public ImmutableContext() { this(new HashMap<>()); @@ -42,7 +45,7 @@ public ImmutableContext(String targetingKey) { * @param attributes evaluation context attributes */ public ImmutableContext(Map attributes) { - this("", attributes); + this(null, attributes); } /** @@ -53,9 +56,7 @@ public ImmutableContext(Map attributes) { */ public ImmutableContext(String targetingKey, Map attributes) { if (targetingKey != null && !targetingKey.trim().isEmpty()) { - final Map actualAttribs = new HashMap<>(attributes); - actualAttribs.put(TARGETING_KEY, new Value(targetingKey)); - this.structure = new ImmutableStructure(actualAttribs); + this.structure = new ImmutableStructure(targetingKey, attributes); } else { this.structure = new ImmutableStructure(attributes); } @@ -71,7 +72,8 @@ public String getTargetingKey() { } /** - * Merges this EvaluationContext object with the passed EvaluationContext, overriding in case of conflict. + * Merges this EvaluationContext object with the passed EvaluationContext, + * overriding in case of conflict. * * @param overridingContext overriding context * @return new, resulting merged context @@ -79,23 +81,24 @@ public String getTargetingKey() { @Override public EvaluationContext merge(EvaluationContext overridingContext) { if (overridingContext == null || overridingContext.isEmpty()) { - return new ImmutableContext(this.asMap()); + return new ImmutableContext(this.asUnmodifiableMap()); } if (this.isEmpty()) { - return new ImmutableContext(overridingContext.asMap()); + return new ImmutableContext(overridingContext.asUnmodifiableMap()); } - return new ImmutableContext( - this.merge(ImmutableStructure::new, this.asMap(), overridingContext.asMap())); + Map attributes = this.asMap(); + EvaluationContext.mergeMaps(ImmutableStructure::new, attributes, + overridingContext.asUnmodifiableMap()); + return new ImmutableContext(attributes); } @SuppressWarnings("all") private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, + public Map merge(Function, Structure> newStructure, Map base, Map overriding) { - return null; } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java index 170602000..06c2551ff 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java @@ -36,7 +36,11 @@ public ImmutableStructure() { * @param attributes attributes. */ public ImmutableStructure(Map attributes) { - super(copyAttributes(attributes)); + super(copyAttributes(attributes, null)); + } + + protected ImmutableStructure(String targetingKey, Map attributes) { + super(copyAttributes(attributes, targetingKey)); } @Override @@ -62,11 +66,18 @@ public Map asMap() { } private static Map copyAttributes(Map in) { + return copyAttributes(in, null); + } + + private static Map copyAttributes(Map in, String targetingKey) { Map copy = new HashMap<>(); for (Entry entry : in.entrySet()) { copy.put(entry.getKey(), Optional.ofNullable(entry.getValue()).map((Value val) -> val.clone()).orElse(null)); } + if (targetingKey != null) { + copy.put(EvaluationContext.TARGETING_KEY, new Value(targetingKey)); + } return copy; } diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 6a47c83ef..ffab28af2 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -33,7 +33,7 @@ public MutableContext(String targetingKey) { } public MutableContext(Map attributes) { - this("", attributes); + this(null, new HashMap<>(attributes)); } /** @@ -44,7 +44,7 @@ public MutableContext(Map attributes) { * @param attributes evaluation context attributes */ public MutableContext(String targetingKey, Map attributes) { - this.structure = new MutableStructure(attributes); + this.structure = new MutableStructure(new HashMap<>(attributes)); if (targetingKey != null && !targetingKey.trim().isEmpty()) { this.structure.attributes.put(TARGETING_KEY, new Value(targetingKey)); } @@ -121,9 +121,10 @@ public EvaluationContext merge(EvaluationContext overridingContext) { return overridingContext; } - Map merged = this.merge( - MutableStructure::new, this.asMap(), overridingContext.asMap()); - return new MutableContext(merged); + Map attributes = this.asMap(); + EvaluationContext.mergeMaps( + MutableStructure::new, attributes, overridingContext.asUnmodifiableMap()); + return new MutableContext(attributes); } /** diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 2162f4130..f56df15a0 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -1,15 +1,24 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.exceptions.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.function.Consumer; + +import dev.openfeature.sdk.exceptions.ExceptionUtils; +import dev.openfeature.sdk.exceptions.FatalError; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import dev.openfeature.sdk.internal.AutoCloseableLock; import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; import dev.openfeature.sdk.internal.ObjectUtils; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.*; -import java.util.function.Consumer; - /** * OpenFeature Client implementation. * You should not instantiate this or reference this class. @@ -19,8 +28,8 @@ * @deprecated // TODO: eventually we will make this non-public. See issue #872 */ @Slf4j -@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable", - "unchecked", "rawtypes"}) +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable", + "unchecked", "rawtypes" }) @Deprecated() // TODO: eventually we will make this non-public. See issue #872 public class OpenFeatureClient implements Client { @@ -39,18 +48,18 @@ public class OpenFeatureClient implements Client { * Deprecated public constructor. Use OpenFeature.API.getClient() instead. * * @param openFeatureAPI Backing global singleton - * @param domain An identifier which logically binds clients with providers (used by observability tools). + * @param domain An identifier which logically binds clients with + * providers (used by observability tools). * @param version Version of the client (used by observability tools). * @deprecated Do not use this constructor. It's for internal use only. - * Clients created using it will not run event handlers. - * Use the OpenFeatureAPI's getClient factory method instead. + * Clients created using it will not run event handlers. + * Use the OpenFeatureAPI's getClient factory method instead. */ @Deprecated() // TODO: eventually we will make this non-public. See issue #872 public OpenFeatureClient( OpenFeatureAPI openFeatureAPI, String domain, - String version - ) { + String version) { this.openfeatureApi = openFeatureAPI; this.domain = domain; this.version = version; @@ -106,11 +115,10 @@ public EvaluationContext getEvaluationContext() { } private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key, T defaultValue, - EvaluationContext ctx, FlagEvaluationOptions options) { + EvaluationContext ctx, FlagEvaluationOptions options) { FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options, () -> FlagEvaluationOptions.builder().build()); Map hints = Collections.unmodifiableMap(flagOptions.getHookHints()); - ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext()); FlagEvaluationDetails details = null; List mergedHooks = null; @@ -183,17 +191,23 @@ private static void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvalu * @return merged evaluation context */ private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) { - final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null - ? openfeatureApi.getEvaluationContext() - : new ImmutableContext(); - final EvaluationContext clientContext = this.getEvaluationContext() != null - ? this.getEvaluationContext() - : new ImmutableContext(); - final EvaluationContext transactionContext = openfeatureApi.getTransactionContext() != null - ? openfeatureApi.getTransactionContext() - : new ImmutableContext(); - - return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext))); + final EvaluationContext apiContext = openfeatureApi.getEvaluationContext(); + final EvaluationContext clientContext = this.getEvaluationContext(); + final EvaluationContext transactionContext = openfeatureApi.getTransactionContext(); + return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext); + } + + private EvaluationContext mergeContextMaps(EvaluationContext... contexts) { + // avoid any unnecessary context instantiations and stream usage here; this is + // called with every evaluation. + Map merged = new HashMap<>(); + for (EvaluationContext evaluationContext : contexts) { + if (evaluationContext != null && !evaluationContext.isEmpty()) { + EvaluationContext.mergeMaps(ImmutableStructure::new, merged, + evaluationContext.asUnmodifiableMap()); + } + } + return new ImmutableContext(merged); } private ProviderEvaluation createProviderEvaluation( @@ -230,7 +244,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte @Override public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getBooleanDetails(key, defaultValue, ctx, options).getValue(); } @@ -246,7 +260,7 @@ public FlagEvaluationDetails getBooleanDetails(String key, Boolean defa @Override public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options); } @@ -262,7 +276,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext @Override public String getStringValue(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getStringDetails(key, defaultValue, ctx, options).getValue(); } @@ -278,7 +292,7 @@ public FlagEvaluationDetails getStringDetails(String key, String default @Override public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options); } @@ -294,7 +308,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte @Override public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getIntegerDetails(key, defaultValue, ctx, options).getValue(); } @@ -310,7 +324,7 @@ public FlagEvaluationDetails getIntegerDetails(String key, Integer defa @Override public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options); } @@ -326,7 +340,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext @Override public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue(); } @@ -342,7 +356,7 @@ public FlagEvaluationDetails getDoubleDetails(String key, Double default @Override public FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options); } @@ -358,7 +372,7 @@ public Value getObjectValue(String key, Value defaultValue, EvaluationContext ct @Override public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getObjectDetails(key, defaultValue, ctx, options).getValue(); } @@ -369,13 +383,13 @@ public FlagEvaluationDetails getObjectDetails(String key, Value defaultVa @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options); } diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index 02e36629e..f2fdc53e7 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -5,8 +5,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.Map.Entry; -import java.util.function.Function; import java.util.stream.Collectors; import static dev.openfeature.sdk.Value.objectToValue; @@ -46,6 +44,14 @@ public interface Structure { */ Map asMap(); + /** + * Get all values, as a map of Values. + * + * @return all attributes on the structure into a Map + */ + Map asUnmodifiableMap(); + + /** * Get all values, with as a map of Object. * @@ -95,7 +101,7 @@ default Object convertValue(Value value) { if (value.isStructure()) { Structure s = value.asStructure(); - return s.asMap() + return s.asUnmodifiableMap() .entrySet() .stream() .collect(HashMap::new, @@ -107,41 +113,6 @@ default Object convertValue(Value value) { throw new ValueNotConvertableError(); } - /** - * Recursively merges the base map with the overriding map. - * - * @param Structure type - * @param newStructure function to create the right structure - * @param base base map to merge - * @param overriding overriding map to merge - * @return resulting merged map - */ - default Map merge(Function, Structure> newStructure, - Map base, - Map overriding) { - - if (base.isEmpty()) { - return overriding; - } - if (overriding.isEmpty()) { - return base; - } - - final Map merged = new HashMap<>(base); - for (Entry overridingEntry : overriding.entrySet()) { - String key = overridingEntry.getKey(); - if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) { - Structure mergedValue = merged.get(key).asStructure(); - Structure overridingValue = overridingEntry.getValue().asStructure(); - Map newMap = this.merge(newStructure, mergedValue.asMap(), overridingValue.asMap()); - merged.put(key, new Value(newStructure.apply(newMap))); - } else { - merged.put(key, overridingEntry.getValue()); - } - } - return merged; - } - /** * Transform an object map to a {@link Structure} type. * diff --git a/src/main/java/dev/openfeature/sdk/Value.java b/src/main/java/dev/openfeature/sdk/Value.java index f0fdc8d45..7464ce5af 100644 --- a/src/main/java/dev/openfeature/sdk/Value.java +++ b/src/main/java/dev/openfeature/sdk/Value.java @@ -274,7 +274,7 @@ protected Value clone() { return new Value(copy); } if (this.isStructure()) { - return new Value(new ImmutableStructure(this.asStructure().asMap())); + return new Value(new ImmutableStructure(this.asStructure().asUnmodifiableMap())); } if (this.isInstant()) { Instant copy = Instant.ofEpochMilli(this.asInstant().toEpochMilli());