From c81898494f3a9afba2ab01532ff49b080aaaecde Mon Sep 17 00:00:00 2001 From: Ben Manes Date: Sat, 15 Feb 2025 15:47:38 -0800 Subject: [PATCH] modernize the simulator to use jdk21 (unrelated to the core library) As dependencies are moving on from jdk11, it makes sense for the analysis tools to upgrade as well. This was already executing as 21 to support those dependencies and now simply requires it at the language level. The user-facing library and jmh benchmarks remain jdk11 compatible. A profile of the execution showed excessive garbage, which is now reduced to improve runtimes. - A direct mapped cache of boxed Long keys are used to reduce the allocations by caching libraries. The research policies use primitive based data structures, but user-facing libraries are not as fortunate. - Using cache listeners to track eviction counts can require the libraries allocate, e.g. copy the entry. This is safe enough to trust their stats because its usually the hit rate that is accidentally miscalculated and we verify that matches. The eviction count is easily derrived (miss count - size) so just a sanity check. * This particular sped up Ehcache from an 18.25m run to 17m, because if any event type is registered against then it emits and does work for the unregistered event types too. This may seem like low-hanging fruit for them to fix, but every other cache runs in seconds (7s for Caffeine) so it is already unusable. Their response was that Ehcache v3 is not intended to be used in scenarios where LRU eviction might occur, only as a safety net, so any eviction is considered user error. Some nice simplifications due to language improvements: - Replaced AutoValue with Records - More concise instanceof casting - More concise switch expressions --- .github/actions/run-gradle/action.yml | 1 - .github/workflows/spelling.yml | 2 +- .../benmanes/caffeine/cache/TimerWheel.java | 2 +- gradle/libs.versions.toml | 11 +- .../lifecycle/auto-value.caffeine.gradle.kts | 21 --- .../java-library.caffeine.gradle.kts | 25 ++- simulator/build.gradle.kts | 5 +- .../caffeine/cache/simulator/Synthetic.java | 44 +++--- .../cache/simulator/admission/TinyLfu.java | 30 ++-- .../simulator/parser/AbstractTraceReader.java | 22 +-- .../cache/simulator/parser/Rewriter.java | 5 +- .../wikipedia/WikipediaTraceReader.java | 18 +-- .../cache/simulator/policy/AccessEvent.java | 27 +++- .../cache/simulator/policy/PolicyStats.java | 144 ++++++++++-------- .../cache/simulator/policy/Registry.java | 18 +-- .../policy/greedy_dual/CampPolicy.java | 10 +- .../policy/greedy_dual/GdsfPolicy.java | 8 +- .../cache/simulator/policy/irr/FrdPolicy.java | 14 +- .../policy/irr/HillClimberFrdPolicy.java | 14 +- .../policy/irr/IndicatorFrdPolicy.java | 14 +- .../simulator/policy/irr/LirsPolicy.java | 48 ++---- .../policy/product/Cache2kPolicy.java | 16 +- .../policy/product/CaffeinePolicy.java | 28 ++-- .../policy/product/CoherencePolicy.java | 17 ++- .../policy/product/Ehcache3Policy.java | 40 +---- .../policy/product/ExpiringMapPolicy.java | 7 +- .../simulator/policy/product/GuavaPolicy.java | 23 ++- .../policy/product/HazelcastPolicy.java | 9 +- .../policy/product/TCachePolicy.java | 16 +- .../policy/sketch/climbing/HillClimber.java | 15 +- .../HillClimberWindowTinyLfuPolicy.java | 16 +- .../sketch/climbing/gradient/Stochastic.java | 18 +-- .../policy/two_queue/TwoQueuePolicy.java | 12 +- .../cache/simulator/report/Metrics.java | 126 +++++++++------ .../report/csv/CombinedCsvReport.java | 2 +- .../simulator/report/csv/CsvReporter.java | 6 +- .../cache/simulator/report/csv/PlotCsv.java | 106 +++++-------- 37 files changed, 452 insertions(+), 488 deletions(-) delete mode 100644 gradle/plugins/src/main/kotlin/lifecycle/auto-value.caffeine.gradle.kts diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 39f1853722..d70f0784aa 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -82,7 +82,6 @@ runs: gradle-home-cache-strict-match: true gradle-home-cache-includes: | caches - wrapper notifications cache-encryption-key: ${{ inputs.cache-encryption-key }} - name: Run ${{ inputs.arguments }} diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 4ab4591546..9e5be54946 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -42,4 +42,4 @@ jobs: with: persist-credentials: false - name: Typos - uses: crate-ci/typos@11ca4583f2f3f74c7e7785c0ecb20fe2c99a4308 # v1.29.5 + uses: crate-ci/typos@51f257b946f503b768e522781f56e9b7b5570d48 # v1.29.7 diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java index f2f8c9bb41..4e6d025de9 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java @@ -206,7 +206,7 @@ public void deschedule(Node node) { @SuppressWarnings("Varifier") Node findBucket(@Var long time) { long duration = Math.max(0L, time - nanos); - if (duration <= 0L) { + if (duration == 0L) { time = nanos; } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d14eef0142..37082e7025 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] asm = "9.7.1" -auto-value = "1.11.0" awaitility = "4.2.2" bcel = "6.10.0" bnd = "7.1.0" @@ -40,7 +39,7 @@ hamcrest = "3.0" hazelcast = "5.5.0" httpclient = "4.5.14" idea = "1.1.10" -jackrabbit = "1.74.0" +jackrabbit = "1.76.0" jackson = "2.18.2" jacoco = "0.8.12" jakarta-inject = "2.0.1" @@ -82,15 +81,15 @@ pmd = "7.10.0" protobuf = "4.29.3" slf4j = "2.0.16" slf4j-test = "3.0.1" -snakeyaml = "2.3" +snakeyaml = "2.4" sigstore = "1.2.0" sonarqube = "6.0.1.5171" spotbugs = "4.9.1" spotbugs-contrib = "7.6.9" -spotbugs-plugin = "6.1.4" +spotbugs-plugin = "6.1.5" stream = "2.9.8" tcache = "2.0.1" -testng = "7.10.2" +testng = "7.11.0" truth = "1.4.4" univocity-parsers = "2.9.1" versions = "0.52.0" @@ -101,8 +100,6 @@ zstd = "1.5.6-10" [libraries] asm-bom = { module = "org.ow2.asm:asm-bom", version.ref = "asm" } -auto-value-annotations = { module = "com.google.auto.value:auto-value-annotations", version.ref = "auto-value" } -auto-value-processor = { module = "com.google.auto.value:auto-value", version.ref = "auto-value" } awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } bcel = { module = "org.apache.bcel:bcel", version.ref = "bcel" } bouncycastle-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle-jdk18on" } diff --git a/gradle/plugins/src/main/kotlin/lifecycle/auto-value.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/lifecycle/auto-value.caffeine.gradle.kts deleted file mode 100644 index 1a11bd8256..0000000000 --- a/gradle/plugins/src/main/kotlin/lifecycle/auto-value.caffeine.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - eclipse - `java-library` -} - -java { - withSourcesJar() -} - -dependencies { - compileOnly(libs.auto.value.annotations) - annotationProcessor(libs.auto.value.processor) -} - -tasks.named("sourcesJar").configure { - dependsOn(tasks.compileJava) -} - -eclipse { - synchronizationTasks(tasks.compileJava) -} diff --git a/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts index 8b2b3d3a3f..d6a155c2c7 100644 --- a/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts +++ b/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts @@ -18,19 +18,16 @@ dependencies { annotationProcessor(platform(libs.kotlin.bom)) } -val javaVersion = JavaLanguageVersion.of(System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 11) -val javaVendor = System.getenv("JAVA_VENDOR")?.let { JvmVendorSpec.matching(it) } -val javaRuntimeVersion = maxOf(javaVersion, JavaLanguageVersion.of(21)) java.toolchain { - languageVersion = javaVersion - vendor = javaVendor + languageVersion = JavaLanguageVersion.of(System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 11) + vendor = System.getenv("JAVA_VENDOR")?.let { JvmVendorSpec.matching(it) } } +val javaRuntimeVersion: Provider = + java.toolchain.languageVersion.map { maxOf(it, JavaLanguageVersion.of(21)) } tasks.withType().configureEach { - inputs.property("javaVendor", javaVendor.toString()) - sourceCompatibility = javaVersion.toString() - targetCompatibility = javaVersion.toString() - options.release = javaVersion.asInt() + inputs.property("javaVendor", java.toolchain.vendor.get().toString()) + options.release = java.toolchain.languageVersion.get().asInt() javaCompiler = javaToolchains.compilerFor { // jdk 17+ is required by compiler plugins, e.g. error-prone @@ -44,7 +41,7 @@ tasks.withType().configureEach { if (isCI()) { compilerArgs.add("-Werror") } - if (javaVersion.canCompileOrRun(21)) { + if (java.toolchain.languageVersion.get().canCompileOrRun(21)) { compilerArgs.add("-proc:full") } encoding = "UTF-8" @@ -80,10 +77,10 @@ tasks.jar { properties.empty() bnd(mapOf( "Bundle-License" to "https://www.apache.org/licenses/LICENSE-2.0", + "Build-Jdk-Spec" to java.toolchain.languageVersion.get(), "Implementation-Title" to project.description, "Bundle-Description" to project.description, "Implementation-Version" to version, - "Build-Jdk-Spec" to javaVersion, "-noextraheaders" to true, "-reproducible" to true, "-snapshot" to "SNAPSHOT")) @@ -96,14 +93,14 @@ tasks.withType().configureEach { use() quiet() noTimestamp() - addStringOption("-release", javaVersion.toString()) + addStringOption("-release", java.toolchain.languageVersion.get().toString()) addStringOption("-link-modularity-mismatch", "info") links( "https://jspecify.dev/docs/api/", "https://errorprone.info/api/latest/", "https://lightbend.github.io/config/latest/api/", - "https://docs.oracle.com/en/java/javase/$javaVersion/docs/api/", - "https://guava.dev/releases/${libs.versions.guava.get()}/api/docs/") + "https://guava.dev/releases/${libs.versions.guava.get()}/api/docs/", + "https://docs.oracle.com/en/java/javase/${java.toolchain.languageVersion.get()}/docs/api/") if (project != project(":caffeine")) { linksOffline("https://static.javadoc.io/$group/caffeine/$version/", diff --git a/simulator/build.gradle.kts b/simulator/build.gradle.kts index c414d521da..9a1cd8354c 100644 --- a/simulator/build.gradle.kts +++ b/simulator/build.gradle.kts @@ -6,7 +6,6 @@ import net.ltgt.gradle.nullaway.nullaway plugins { id("application") - id("auto-value.caffeine") id("java-library.caffeine") } @@ -47,6 +46,10 @@ application { mainClass = "com.github.benmanes.caffeine.cache.simulator.Simulator" } +java.toolchain { + languageVersion = maxOf(languageVersion.get(), JavaLanguageVersion.of(21)) +} + forbiddenApis { bundledSignatures.addAll(listOf("commons-io-unsafe-2.15.1", "jdk-deprecated", "jdk-internal", "jdk-non-portable", "jdk-reflection", "jdk-unsafe")) diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java index 987e1a8ccd..de9105d42c 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java @@ -20,7 +20,6 @@ import java.util.stream.LongStream; -import com.github.benmanes.caffeine.cache.simulator.BasicSettings.SyntheticSettings.HotspotSettings; import com.github.benmanes.caffeine.cache.simulator.BasicSettings.TraceSettings; import com.github.benmanes.caffeine.cache.simulator.parser.TraceReader.KeyOnlyTraceReader; @@ -46,32 +45,33 @@ private Synthetic() {} /** Returns a sequence of events based on the setting's distribution. */ public static KeyOnlyTraceReader generate(TraceSettings settings) { int events = settings.synthetic().events(); - switch (settings.synthetic().distribution().toLowerCase(US)) { - case "counter": - return counter(settings.synthetic().counter().start(), events); - case "repeating": - return repeating(settings.synthetic().repeating().items(), events); - case "uniform": - var uniform = settings.synthetic().uniform(); - return uniform(uniform.lowerBound(), uniform.upperBound(), events); - case "exponential": - return exponential(settings.synthetic().exponential().mean(), events); - case "hotspot": - HotspotSettings hotspot = settings.synthetic().hotspot(); - return Synthetic.hotspot(hotspot.lowerBound(), hotspot.upperBound(), + return switch (settings.synthetic().distribution().toLowerCase(US)) { + case "counter" -> + counter(settings.synthetic().counter().start(), events); + case "repeating" -> + repeating(settings.synthetic().repeating().items(), events); + case "uniform" -> + uniform(settings.synthetic().uniform().lowerBound(), + settings.synthetic().uniform().upperBound(), events); + case "exponential" -> + exponential(settings.synthetic().exponential().mean(), events); + case "hotspot" -> { + var hotspot = settings.synthetic().hotspot(); + yield hotspot(hotspot.lowerBound(), hotspot.upperBound(), hotspot.hotOpnFraction(), hotspot.hotsetFraction(), events); - case "zipfian": - return zipfian(settings.synthetic().zipfian().items(), + } + case "zipfian" -> + zipfian(settings.synthetic().zipfian().items(), settings.synthetic().zipfian().constant(), events); - case "scrambled-zipfian": - return scrambledZipfian(settings.synthetic().zipfian().items(), + case "scrambled-zipfian" -> + scrambledZipfian(settings.synthetic().zipfian().items(), settings.synthetic().zipfian().constant(), events); - case "skewed-zipfian-latest": - return skewedZipfianLatest(settings.synthetic().zipfian().items(), events); - default: + case "skewed-zipfian-latest" -> + skewedZipfianLatest(settings.synthetic().zipfian().items(), events); + default -> throw new IllegalStateException("Unknown distribution: " + settings.synthetic().distribution()); - } + }; } /** diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java index eceec74324..bf7964fd72 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java @@ -86,22 +86,22 @@ public boolean admit(long candidateKey, long victimKey) { /** Returns the frequency histogram. */ private static Frequency makeSketch(BasicSettings settings) { String type = settings.tinyLfu().sketch(); - switch (type.toLowerCase(US)) { - case "count-min-4": { + return switch (type.toLowerCase(US)) { + case "count-min-4" -> { String reset = settings.tinyLfu().countMin4().reset(); - switch (reset.toLowerCase(US)) { - case "climber": return new ClimberResetCountMin4(settings.config()); - case "periodic": return new PeriodicResetCountMin4(settings.config()); - case "indicator": return new IndicatorResetCountMin4(settings.config()); - case "incremental": return new IncrementalResetCountMin4(settings.config()); - default: throw new IllegalStateException("Unknown reset type: " + reset); - } + yield switch (reset.toLowerCase(US)) { + case "climber" -> new ClimberResetCountMin4(settings.config()); + case "periodic" -> new PeriodicResetCountMin4(settings.config()); + case "indicator" -> new IndicatorResetCountMin4(settings.config()); + case "incremental" -> new IncrementalResetCountMin4(settings.config()); + default -> throw new IllegalStateException("Unknown reset type: " + reset); + }; } - case "tiny-table": return new TinyCacheAdapter(settings.config()); - case "count-min-64": return new CountMin64TinyLfu(settings.config()); - case "perfect-table": return new PerfectFrequency(settings.config()); - case "random-table": return new RandomRemovalFrequencyTable(settings.config()); - default: throw new IllegalStateException("Unknown sketch type: " + type); - } + case "tiny-table" -> new TinyCacheAdapter(settings.config()); + case "count-min-64" -> new CountMin64TinyLfu(settings.config()); + case "perfect-table" -> new PerfectFrequency(settings.config()); + case "random-table" -> new RandomRemovalFrequencyTable(settings.config()); + default -> throw new IllegalStateException("Unknown sketch type: " + type); + }; } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/AbstractTraceReader.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/AbstractTraceReader.java index 11cc196666..9f58570af7 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/AbstractTraceReader.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/AbstractTraceReader.java @@ -67,27 +67,27 @@ protected BufferedInputStream readFile() { @SuppressWarnings("PMD.CloseResource") protected BufferedInputStream readInput(InputStream input) { - @Var BufferedInputStream buffered = null; + @Var BufferedInputStream bufferedStream = null; try { - buffered = new BufferedInputStream(input, BUFFER_SIZE); + bufferedStream = new BufferedInputStream(input, BUFFER_SIZE); var extractors = List.>of( AbstractTraceReader::tryXz, AbstractTraceReader::tryCompressed, this::tryArchived); for (var extractor : extractors) { - buffered.mark(100); - InputStream next = extractor.apply(buffered); + bufferedStream.mark(100); + InputStream next = extractor.apply(bufferedStream); if (next == null) { - buffered.reset(); - } else if (next instanceof BufferedInputStream) { - buffered = (BufferedInputStream) next; + bufferedStream.reset(); + } else if (next instanceof BufferedInputStream buffered) { + bufferedStream = buffered; } else { - buffered = new BufferedInputStream(next, BUFFER_SIZE); + bufferedStream = new BufferedInputStream(next, BUFFER_SIZE); } } - return buffered; + return bufferedStream; } catch (Throwable t) { try { - if (buffered != null) { - buffered.close(); + if (bufferedStream != null) { + bufferedStream.close(); } } catch (IOException e) { t.addSuppressed(e); diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/Rewriter.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/Rewriter.java index 3d27bdc569..795a3fa210 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/Rewriter.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/Rewriter.java @@ -22,12 +22,13 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import com.google.common.base.CaseFormat; import com.google.common.base.Stopwatch; -import com.google.common.collect.Lists; import com.google.errorprone.annotations.Var; import picocli.CommandLine; @@ -91,7 +92,7 @@ public void run() { } private static String[] argumentsWithDefaults(String[] args) { - var params = Lists.newArrayList(args); + var params = new ArrayList<>(Arrays.asList(args)); if (!params.contains("--inputFormat")) { @Var boolean found = false; @Var boolean defaultFormat = true; diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/wikipedia/WikipediaTraceReader.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/wikipedia/WikipediaTraceReader.java index 5842cbbe8e..2a163e2fd5 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/wikipedia/WikipediaTraceReader.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/parser/wikipedia/WikipediaTraceReader.java @@ -15,6 +15,8 @@ */ package com.github.benmanes.caffeine.cache.simulator.parser.wikipedia; +import static java.util.Objects.requireNonNull; + import java.util.Objects; import java.util.stream.LongStream; @@ -23,7 +25,6 @@ import com.github.benmanes.caffeine.cache.simulator.parser.TextTraceReader; import com.github.benmanes.caffeine.cache.simulator.parser.TraceReader.KeyOnlyTraceReader; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; import com.google.errorprone.annotations.Var; @@ -42,8 +43,8 @@ public final class WikipediaTraceReader extends TextTraceReader implements KeyOn "wiki/Special:Search", "w/query.php", "wiki/Talk:", "wiki/Special:AutoLogin", "Special:UserLogin", "w/api.php", "error:"); private static final ImmutableList REPLACEMENTS = ImmutableList.of( - Replacement.of("%2F", "/"), Replacement.of("%20", " "), - Replacement.of("&", "&"), Replacement.of("%3A", ":")); + new Replacement("%2F", "/"), new Replacement("%20", " "), + new Replacement("&", "&"), new Replacement("%3A", ":")); public WikipediaTraceReader(String filePath) { super(filePath); @@ -133,13 +134,10 @@ public static boolean isAllowed(String path) { return true; } - @AutoValue - abstract static class Replacement { - abstract String search(); - abstract String replace(); - - static Replacement of(String search, String replace) { - return new AutoValue_WikipediaTraceReader_Replacement(search, replace); + record Replacement(String search, String replace) { + Replacement { + requireNonNull(search); + requireNonNull(replace); } } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/AccessEvent.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/AccessEvent.java index 70922f213e..6f761246fb 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/AccessEvent.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/AccessEvent.java @@ -18,11 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.stream.LongStream; import org.jspecify.annotations.Nullable; import com.google.common.base.MoreObjects; import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.Var; /** * The key and metadata for accessing a cache. @@ -31,6 +34,10 @@ */ @Immutable public class AccessEvent { + private static final AtomicReferenceArray keys = new AtomicReferenceArray<>( + LongStream.range(0, 1 << 20).map(i -> 0L).boxed().toArray(Long[]::new)); + private static final int mask = keys.length() - 1; + private final long key; private AccessEvent(long key) { @@ -42,6 +49,17 @@ public long key() { return key; } + /** Returns the object key. */ + public Long longKey() { + int index = Long.hashCode(key) & mask; + @Var Long longKey = keys.get(index); + if (longKey != key) { + longKey = key; + keys.set(index, longKey); + } + return longKey; + } + /** Returns the weight of the entry. */ public int weight() { return 1; @@ -64,13 +82,8 @@ public boolean isPenaltyAware() { @Override public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } else if (!(o instanceof AccessEvent)) { - return false; - } - var event = (AccessEvent) o; - return (key() == event.key()) + return (o instanceof AccessEvent event) + && (key() == event.key()) && (weight() == event.weight()) && (hitPenalty() == event.hitPenalty()) && (missPenalty() == event.missPenalty()); diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/PolicyStats.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/PolicyStats.java index 0186d1804d..951b590580 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/PolicyStats.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/PolicyStats.java @@ -23,19 +23,21 @@ import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.builder.ToStringStyle.MULTI_LINE_STYLE; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.function.DoubleSupplier; import java.util.function.LongSupplier; import java.util.function.Supplier; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jspecify.annotations.Nullable; import com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic; -import com.google.auto.value.AutoValue; -import com.google.auto.value.AutoValue.CopyAnnotations; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; /** @@ -67,34 +69,34 @@ public PolicyStats(String format, Object... args) { this.name = String.format(US, format, args); this.metrics = new LinkedHashMap<>(); - addMetric(Metric.builder() - .name("Policy").addValue(this::name).type(OBJECT).required(true)); - addMetric(Metric.builder() - .name("Hit Rate").addValue(this::hitRate).type(PERCENT).required(true)); - addMetric(Metric.builder() - .name("Miss Rate").addValue(this::missRate).type(PERCENT).required(true)); - addMetric(Metric.builder() - .name("Hits").addValue(this::hitCount).type(NUMBER).required(true)); - addMetric(Metric.builder() - .name("Misses").addValue(this::missCount).type(NUMBER).required(true)); - addMetric(Metric.builder() - .name("Misses").addValue(this::missCount).type(NUMBER).required(true)); - addMetric(Metric.builder() - .name("Requests").addValue(this::requestCount).type(NUMBER).required(true)); - addMetric(Metric.builder() - .name("Evictions").addValue(this::evictionCount).type(NUMBER).required(true)); + addMetric(new Metric.Builder() + .name("Policy").value(this::name).type(OBJECT).required(true)); + addMetric(new Metric.Builder() + .name("Hit Rate").value(this::hitRate).type(PERCENT).required(true)); + addMetric(new Metric.Builder() + .name("Miss Rate").value(this::missRate).type(PERCENT).required(true)); + addMetric(new Metric.Builder() + .name("Hits").value(this::hitCount).type(NUMBER).required(true)); + addMetric(new Metric.Builder() + .name("Misses").value(this::missCount).type(NUMBER).required(true)); + addMetric(new Metric.Builder() + .name("Misses").value(this::missCount).type(NUMBER).required(true)); + addMetric(new Metric.Builder() + .name("Requests").value(this::requestCount).type(NUMBER).required(true)); + addMetric(new Metric.Builder() + .name("Evictions").value(this::evictionCount).type(NUMBER).required(true)); addPercentMetric("Admit rate", () -> (admittedCount + rejectedCount) == 0 ? 0 : admissionRate()); - addMetric(Metric.builder() - .name("Requests Weight").addValue(this::requestsWeight) - .type(NUMBER).addCharacteristic(WEIGHTED)); - addMetric(Metric.builder() - .name("Weighted Hit Rate").addValue(this::weightedHitRate) - .addCharacteristic(WEIGHTED).type(PERCENT)); - addMetric(Metric.builder() - .name("Weighted Miss Rate").addValue(this::weightedMissRate) - .type(PERCENT).addCharacteristic(WEIGHTED)); + addMetric(new Metric.Builder() + .name("Requests Weight").value(this::requestsWeight) + .type(NUMBER).characteristic(WEIGHTED)); + addMetric(new Metric.Builder() + .name("Weighted Hit Rate").value(this::weightedHitRate) + .characteristic(WEIGHTED).type(PERCENT)); + addMetric(new Metric.Builder() + .name("Weighted Miss Rate").value(this::weightedMissRate) + .type(PERCENT).characteristic(WEIGHTED)); addPercentMetric("Adaption", this::percentAdaption); addMetric("Average Miss Penalty", this::averageMissPenalty); addMetric("Average Penalty", this::averagePenalty); @@ -108,19 +110,19 @@ public void addMetric(Metric.Builder metricBuilder) { } public void addMetric(String name, Supplier supplier) { - addMetric(Metric.builder().name(name).value(supplier).type(OBJECT)); + addMetric(new Metric.Builder().name(name).value(supplier).type(OBJECT)); } public void addMetric(String name, LongSupplier supplier) { - addMetric(Metric.builder().name(name).value(supplier).type(NUMBER)); + addMetric(new Metric.Builder().name(name).value(supplier).type(NUMBER)); } public void addMetric(String name, DoubleSupplier supplier) { - addMetric(Metric.builder().name(name).value(supplier).type(NUMBER)); + addMetric(new Metric.Builder().name(name).value(supplier).type(NUMBER)); } public void addPercentMetric(String name, DoubleSupplier supplier) { - addMetric(Metric.builder().name(name).value(supplier).type(PERCENT)); + addMetric(new Metric.Builder().name(name).value(supplier).type(PERCENT)); } public Map metrics() { @@ -301,48 +303,70 @@ public String toString() { return ToStringBuilder.reflectionToString(this, MULTI_LINE_STYLE); } - @AutoValue - public abstract static class Metric { + public record Metric(String name, Object value, MetricType type, + ImmutableSet characteristics, boolean required) { public enum MetricType { NUMBER, PERCENT, OBJECT } - public abstract String name(); - public abstract Object value(); - public abstract MetricType type(); - public abstract boolean required(); - public abstract ImmutableSet characteristics(); - - public static Metric of(String name, Object value, MetricType type, boolean required) { - return builder().name(name).value(value).type(type).required(required).build(); - } - public static Metric.Builder builder() { - return new AutoValue_PolicyStats_Metric.Builder().required(false); + public Metric { + requireNonNull(type); + requireNonNull(name); + requireNonNull(value); + requireNonNull(characteristics); } - @AutoValue.Builder @CopyAnnotations - public abstract static class Builder { - public abstract Builder name(String name); - public abstract Builder value(Object value); - public abstract Builder type(MetricType type); - public abstract Builder required(boolean required); - public abstract ImmutableSet.Builder characteristicsBuilder(); - public abstract Metric build(); + public static final class Builder { + private final Set characteristics; + + private @Nullable MetricType type; + private @Nullable Object value; + private @Nullable String name; + private boolean required; + public Builder() { + characteristics = EnumSet.noneOf(Characteristic.class); + } @CanIgnoreReturnValue - public final Builder addCharacteristic(Characteristic characteristic) { - characteristicsBuilder().add(characteristic); + public Builder characteristic(Characteristic characteristic) { + characteristics.add(characteristic); return this; } @CanIgnoreReturnValue - public Builder addValue(Supplier value) { - return value(value); + public Builder name(String name) { + this.name = requireNonNull(name); + return this; } @CanIgnoreReturnValue - public Builder addValue(LongSupplier value) { - return value(value); + public Builder value(Object value) { + this.value = requireNonNull(value); + return this; + } + @CanIgnoreReturnValue + public Builder value(Supplier value) { + return value((Object) value); } @CanIgnoreReturnValue - public Builder addValue(DoubleSupplier value) { - return value(value); + public Builder value(LongSupplier value) { + return value((Object) value); + } + @CanIgnoreReturnValue + public Builder value(DoubleSupplier value) { + return value((Object) value); + } + @CanIgnoreReturnValue + public Builder type(MetricType type) { + this.type = requireNonNull(type); + return this; + } + @CanIgnoreReturnValue + public Builder required(boolean required) { + this.required = required; + return this; + } + public Metric build() { + requireNonNull(type); + requireNonNull(name); + requireNonNull(value); + return new Metric(name, value, type, Sets.immutableEnumSet(characteristics), required); } } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java index 3e62d61ca4..54d620e84a 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.util.Locale.US; +import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.Arrays; @@ -76,7 +77,6 @@ import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.S3FifoPolicy; import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.TuQueuePolicy; import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.TwoQueuePolicy; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.typesafe.config.Config; @@ -145,7 +145,7 @@ private void registerMany(Class policyClass, @SuppressWarnings("InconsistentOverloads") private void registerMany(String name, Class policyClass, Function> creator) { - factories.put(name.trim().toLowerCase(US), Factory.of(policyClass, creator)); + factories.put(name.trim().toLowerCase(US), new Factory(policyClass, creator)); } private void registerOptimal() { @@ -235,20 +235,16 @@ private void registerProduct() { registerMany(ExpiringMapPolicy.class, ExpiringMapPolicy::policies); } - @AutoValue - abstract static class Factory { - abstract Class policyClass(); - abstract Function> creator(); - + record Factory(Class policyClass, Function> creator) { + Factory { + requireNonNull(policyClass); + requireNonNull(creator); + } ImmutableSet characteristics() { var policySpec = policyClass().getAnnotation(PolicySpec.class); return (policySpec == null) ? ImmutableSet.of() : Sets.immutableEnumSet(Arrays.asList(policySpec.characteristics())); } - - static Factory of(Class policyClass, Function> creator) { - return new AutoValue_Registry_Factory(policyClass, creator); - } } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/CampPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/CampPolicy.java index b7eaa83fc0..cf30fe4ab4 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/CampPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/CampPolicy.java @@ -220,13 +220,9 @@ public int compareTo(Sentinel sentinel) { @Override public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } else if (!(o instanceof Sentinel)) { - return false; - } - var sentinel = (Sentinel) o; - return (cost == sentinel.cost) && (priority == sentinel.priority); + return (o instanceof Sentinel node) + && (priority == node.priority) + && (cost == node.cost); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/GdsfPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/GdsfPolicy.java index f46b61a899..c46e4d146d 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/GdsfPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/greedy_dual/GdsfPolicy.java @@ -207,13 +207,7 @@ public int compareTo(Node node) { @Override public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } else if (!(o instanceof Node)) { - return false; - } - var node = (Node) o; - return compareTo(node) == 0; + return (o instanceof Node node) && (compareTo(node) == 0); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/FrdPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/FrdPolicy.java index 0bb6d3e069..21b4b12635 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/FrdPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/FrdPolicy.java @@ -268,26 +268,23 @@ public void moveToTop(StackType stackType) { } switch (stackType) { - case FILTER: { + case FILTER -> { Node next = requireNonNull(headFilter.nextFilter); headFilter.nextFilter = this; next.prevFilter = this; this.nextFilter = next; this.prevFilter = headFilter; isInFilter = true; - return; } - case MAIN: { + case MAIN -> { Node next = requireNonNull(headMain.nextMain); headMain.nextMain = this; next.prevMain = this; this.nextMain = next; this.prevMain = headMain; isInMain = true; - return; } } - throw new IllegalArgumentException(); } @SuppressWarnings("PMD.TooFewBranchesForSwitch") @@ -295,7 +292,7 @@ public void removeFrom(StackType stackType) { checkState(isInStack(stackType)); switch (stackType) { - case FILTER: { + case FILTER -> { requireNonNull(prevFilter); requireNonNull(nextFilter); @@ -303,9 +300,8 @@ public void removeFrom(StackType stackType) { nextFilter.prevFilter = prevFilter; prevFilter = nextFilter = null; isInFilter = false; - return; } - case MAIN: { + case MAIN -> { requireNonNull(prevMain); requireNonNull(nextMain); @@ -313,10 +309,8 @@ public void removeFrom(StackType stackType) { nextMain.prevMain = prevMain; prevMain = nextMain = null; isInMain = false; - return; } } - throw new IllegalArgumentException(); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/HillClimberFrdPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/HillClimberFrdPolicy.java index a98807a28a..cde8486c85 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/HillClimberFrdPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/HillClimberFrdPolicy.java @@ -364,26 +364,23 @@ public void moveToTop(StackType stackType) { } switch (stackType) { - case FILTER: { + case FILTER -> { Node next = requireNonNull(headFilter.nextFilter); headFilter.nextFilter = this; next.prevFilter = this; this.nextFilter = next; this.prevFilter = headFilter; isInFilter = true; - break; } - case MAIN: { + case MAIN -> { Node next = requireNonNull(headMain.nextMain); headMain.nextMain = this; next.prevMain = this; this.nextMain = next; this.prevMain = headMain; isInMain = true; - break; } } - throw new IllegalArgumentException(); } @SuppressWarnings("PMD.TooFewBranchesForSwitch") @@ -391,7 +388,7 @@ public void removeFrom(StackType stackType) { checkState(isInStack(stackType)); switch (stackType) { - case FILTER: { + case FILTER -> { requireNonNull(prevFilter); requireNonNull(nextFilter); @@ -399,9 +396,8 @@ public void removeFrom(StackType stackType) { nextFilter.prevFilter = prevFilter; prevFilter = nextFilter = null; isInFilter = false; - break; } - case MAIN: { + case MAIN -> { requireNonNull(prevMain); requireNonNull(nextMain); @@ -409,10 +405,8 @@ public void removeFrom(StackType stackType) { nextMain.prevMain = prevMain; prevMain = nextMain = null; isInMain = false; - break; } } - throw new IllegalArgumentException(); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/IndicatorFrdPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/IndicatorFrdPolicy.java index 22d92a4af6..651d94f460 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/IndicatorFrdPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/IndicatorFrdPolicy.java @@ -333,26 +333,23 @@ public void moveToTop(StackType stackType) { } switch (stackType) { - case FILTER: { + case FILTER -> { Node next = requireNonNull(headFilter.nextFilter); headFilter.nextFilter = this; next.prevFilter = this; this.nextFilter = next; this.prevFilter = headFilter; isInFilter = true; - return; } - case MAIN: { + case MAIN -> { Node next = requireNonNull(headMain.nextMain); headMain.nextMain = this; next.prevMain = this; this.nextMain = next; this.prevMain = headMain; isInMain = true; - return; } } - throw new IllegalArgumentException(); } @SuppressWarnings("PMD.TooFewBranchesForSwitch") @@ -360,7 +357,7 @@ public void removeFrom(StackType stackType) { checkState(isInStack(stackType)); switch (stackType) { - case FILTER: { + case FILTER -> { requireNonNull(prevFilter); requireNonNull(nextFilter); @@ -368,9 +365,8 @@ public void removeFrom(StackType stackType) { nextFilter.prevFilter = prevFilter; prevFilter = nextFilter = null; isInFilter = false; - return; } - case MAIN: { + case MAIN -> { requireNonNull(prevMain); requireNonNull(nextMain); @@ -378,10 +374,8 @@ public void removeFrom(StackType stackType) { nextMain.prevMain = prevMain; prevMain = nextMain = null; isInMain = false; - return; } } - throw new IllegalArgumentException(); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/LirsPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/LirsPolicy.java index 8e4700c14e..403e097783 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/LirsPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/irr/LirsPolicy.java @@ -390,27 +390,19 @@ final class Node { } public boolean isInStack(StackType stackType) { - switch (stackType) { - case S: - return isInS; - case Q: - return isInQ; - case NR: - return isInNR; - } - throw new IllegalArgumentException(); + return switch (stackType) { + case S -> isInS; + case Q -> isInQ; + case NR -> isInNR; + }; } public boolean isStackTop(StackType stackType) { - switch (stackType) { - case S: - return (headS.nextS == this); - case Q: - return (headQ.nextQ == this); - case NR: - return (headNR.nextNR == this); - } - throw new IllegalArgumentException(); + return switch (stackType) { + case S -> (headS.nextS == this); + case Q -> (headQ.nextQ == this); + case NR -> (headNR.nextNR == this); + }; } public void moveToTop(StackType stackType) { @@ -419,7 +411,7 @@ public void moveToTop(StackType stackType) { } switch (stackType) { - case S: { + case S -> { Node next = requireNonNull(headS.nextS); headS.nextS = this; next.prevS = this; @@ -427,9 +419,8 @@ public void moveToTop(StackType stackType) { this.prevS = headS; isInS = true; sizeS++; - return; } - case Q: { + case Q -> { Node next = requireNonNull(headQ.nextQ); headQ.nextQ = this; next.prevQ = this; @@ -437,9 +428,8 @@ public void moveToTop(StackType stackType) { this.prevQ = headQ; isInQ = true; sizeQ++; - return; } - case NR: { + case NR -> { Node next = requireNonNull(headNR.nextNR); headNR.nextNR = this; next.prevNR = this; @@ -447,17 +437,15 @@ public void moveToTop(StackType stackType) { this.prevNR = headNR; isInNR = true; sizeNR++; - return; } } - throw new IllegalArgumentException(); } public void removeFrom(StackType stackType) { checkState(isInStack(stackType)); switch (stackType) { - case S: { + case S -> { requireNonNull(prevS); requireNonNull(nextS); @@ -466,9 +454,8 @@ public void removeFrom(StackType stackType) { prevS = nextS = null; isInS = false; sizeS--; - return; } - case Q: { + case Q -> { requireNonNull(prevQ); requireNonNull(nextQ); @@ -477,9 +464,8 @@ public void removeFrom(StackType stackType) { prevQ = nextQ = null; isInQ = false; sizeQ--; - return; } - case NR: { + case NR -> { requireNonNull(prevNR); requireNonNull(nextNR); @@ -488,10 +474,8 @@ public void removeFrom(StackType stackType) { prevNR = nextNR = null; isInNR = false; sizeNR--; - return; } } - throw new IllegalArgumentException(); } @Override diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Cache2kPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Cache2kPolicy.java index d9ae2b209c..1d24cee998 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Cache2kPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Cache2kPolicy.java @@ -16,6 +16,7 @@ package com.github.benmanes.caffeine.cache.simulator.policy.product; import static com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic.WEIGHTED; +import static com.google.common.base.Preconditions.checkState; import java.util.Set; import java.util.logging.Level; @@ -24,6 +25,7 @@ import org.cache2k.Cache; import org.cache2k.Cache2kBuilder; import org.cache2k.event.CacheEntryEvictedListener; +import org.cache2k.operation.CacheControl; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; @@ -53,7 +55,8 @@ public Cache2kPolicy(Config config, Set characteristics) { (cache, entry) -> policyStats.recordEviction(); var builder = Cache2kBuilder.of(Long.class, AccessEvent.class) .addListener(listener) - .strictEviction(true); + .strictEviction(true) + .eternal(true); if (characteristics.contains(WEIGHTED)) { builder.weigher((Long key, AccessEvent value) -> value.weight()); builder.maximumWeight(settings.maximumSize()); @@ -65,14 +68,15 @@ public Cache2kPolicy(Config config, Set characteristics) { @Override public void record(AccessEvent event) { - AccessEvent value = cache.peek(event.key()); + Long key = event.longKey(); + var value = cache.peek(key); if (value == null) { - cache.put(event.key(), event); + cache.put(key, event); policyStats.recordWeightedMiss(event.weight()); } else { policyStats.recordWeightedHit(event.weight()); if (event.weight() != value.weight()) { - cache.put(event.key(), event); + cache.put(key, event); } } } @@ -85,5 +89,9 @@ public PolicyStats stats() { @Override public void finished() { cache.close(); + var stats = CacheControl.of(cache).sampleStatistics(); + checkState(policyStats.missCount() == stats.getMissCount()); + checkState(policyStats.evictionCount() == stats.getEvictedCount()); + checkState(policyStats.hitCount() == stats.getGetCount() - stats.getMissCount()); } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CaffeinePolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CaffeinePolicy.java index 402d614e72..65c8a72203 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CaffeinePolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CaffeinePolicy.java @@ -16,14 +16,12 @@ package com.github.benmanes.caffeine.cache.simulator.policy.product; import static com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic.WEIGHTED; +import static com.google.common.base.Preconditions.checkState; import java.util.Set; -import org.jspecify.annotations.Nullable; - import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; import com.github.benmanes.caffeine.cache.simulator.policy.Policy; @@ -45,30 +43,28 @@ public final class CaffeinePolicy implements Policy { public CaffeinePolicy(Config config, Set characteristics) { policyStats = new PolicyStats(name()); var settings = new BasicSettings(config); - Caffeine builder = Caffeine.newBuilder() - .removalListener((@Nullable Long key, @Nullable AccessEvent value, RemovalCause cause) -> - policyStats.recordEviction()) - .executor(Runnable::run); + var builder = Caffeine.newBuilder().executor(Runnable::run); if (characteristics.contains(WEIGHTED)) { builder.maximumWeight(settings.maximumSize()); - builder.weigher((key, value) -> value.weight()); + builder.weigher((Long key, AccessEvent value) -> value.weight()); } else { builder.maximumSize(settings.maximumSize()); builder.initialCapacity(Ints.saturatedCast(settings.maximumSize())); } - cache = builder.build(); + cache = builder.recordStats().build(); } @Override public void record(AccessEvent event) { - AccessEvent value = cache.getIfPresent(event.key()); + Long key = event.longKey(); + var value = cache.getIfPresent(key); if (value == null) { - cache.put(event.key(), event); + cache.put(key, event); policyStats.recordWeightedMiss(event.weight()); } else { policyStats.recordWeightedHit(event.weight()); if (event.weight() != value.weight()) { - cache.put(event.key(), event); + cache.put(key, event); } } } @@ -77,4 +73,12 @@ public void record(AccessEvent event) { public PolicyStats stats() { return policyStats; } + + @Override + public void finished() { + var stats = cache.stats(); + policyStats.addEvictions(stats.evictionCount()); + checkState(policyStats.hitCount() == stats.hitCount()); + checkState(policyStats.missCount() == stats.missCount()); + } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CoherencePolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CoherencePolicy.java index c71a8412ab..a0ac8e6fda 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CoherencePolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/CoherencePolicy.java @@ -16,6 +16,7 @@ package com.github.benmanes.caffeine.cache.simulator.policy.product; import static com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic.WEIGHTED; +import static com.google.common.base.Preconditions.checkState; import static java.util.stream.Collectors.toUnmodifiableSet; import java.util.EnumSet; @@ -29,6 +30,7 @@ import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.google.common.base.CaseFormat; import com.google.errorprone.annotations.Var; +import com.tangosol.net.cache.CacheStatistics; import com.tangosol.net.cache.ConfigurableCacheMap.UnitCalculator; import com.tangosol.net.cache.LocalCache; import com.tangosol.util.MapEvent; @@ -45,6 +47,7 @@ public final class CoherencePolicy implements Policy { private final Map map; private final PolicyStats policyStats; + private final CacheStatistics stats; @SuppressWarnings("unchecked") public CoherencePolicy(CoherenceSettings settings, Eviction policy) { @@ -64,6 +67,7 @@ public CoherencePolicy(CoherenceSettings settings, Eviction policy) { cache.setEvictionType(policy.type); cache.addMapListener(new CoherenceListener()); cache.setUnitCalculator(new AccessEventCalculator()); + stats = cache.getCacheStatistics(); map = cache; } @@ -77,14 +81,15 @@ public static Set policies(Config config) { @Override public void record(AccessEvent event) { - var value = map.get(event.key()); + Long key = event.longKey(); + var value = map.get(key); if (value == null) { - map.put(event.key(), event); + map.put(key, event); policyStats.recordWeightedMiss(event.weight()); } else { policyStats.recordWeightedHit(event.weight()); if (event.weight() != value.weight()) { - map.put(event.key(), event); + map.put(key, event); } } } @@ -94,6 +99,12 @@ public PolicyStats stats() { return policyStats; } + @Override + public void finished() { + checkState(policyStats.hitCount() == stats.getCacheHits()); + checkState(policyStats.missCount() == stats.getCacheMisses()); + } + private final class CoherenceListener implements MapListener { @Override public void entryInserted(MapEvent event) {} @Override public void entryUpdated(MapEvent event) {} diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Ehcache3Policy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Ehcache3Policy.java index 206ff88045..38f8b5397d 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Ehcache3Policy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/Ehcache3Policy.java @@ -16,13 +16,6 @@ package com.github.benmanes.caffeine.cache.simulator.policy.product; import static com.google.common.base.Preconditions.checkState; -import static org.ehcache.event.EventFiring.SYNCHRONOUS; -import static org.ehcache.event.EventOrdering.ORDERED; -import static org.ehcache.event.EventType.EVICTED; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; import org.ehcache.Cache; import org.ehcache.CacheManager; @@ -31,16 +24,13 @@ import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.config.units.EntryUnit; import org.ehcache.core.internal.statistics.DefaultStatisticsService; -import org.ehcache.core.spi.service.ExecutionService; import org.ehcache.core.statistics.CacheStatistics; -import org.ehcache.spi.service.Service; -import org.ehcache.spi.service.ServiceProvider; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; -import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy; +import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; +import com.github.benmanes.caffeine.cache.simulator.policy.Policy; import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; -import com.google.common.util.concurrent.MoreExecutors; import com.typesafe.config.Config; /** @@ -49,7 +39,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ @PolicySpec(name = "product.Ehcache3") -public final class Ehcache3Policy implements KeyOnlyPolicy { +public final class Ehcache3Policy implements Policy { private final Cache cache; private final CacheManager cacheManager; private final PolicyStats policyStats; @@ -60,7 +50,6 @@ public Ehcache3Policy(Config config) { var settings = new BasicSettings(config); var statistics = new DefaultStatisticsService(); cacheManager = CacheManagerBuilder.newCacheManagerBuilder() - .using(new SameThreadExecutionService()) .using(statistics) .build(true); cache = cacheManager.createCache("ehcache3", @@ -68,13 +57,12 @@ public Ehcache3Policy(Config config) { ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(settings.maximumSize(), EntryUnit.ENTRIES)) .build()); - cache.getRuntimeConfiguration().registerCacheEventListener( - event -> policyStats.recordEviction(), ORDERED, SYNCHRONOUS, EVICTED); stats = statistics.getCacheStatistics("ehcache3"); } @Override - public void record(long key) { + public void record(AccessEvent event) { + Long key = event.longKey(); var value = cache.get(key); if (value == null) { cache.put(key, Boolean.TRUE); @@ -92,24 +80,8 @@ public PolicyStats stats() { @Override public void finished() { cacheManager.close(); + policyStats.addEvictions(stats.getCacheEvictions()); checkState(policyStats.hitCount() == stats.getCacheHits()); checkState(policyStats.missCount() == stats.getCacheMisses()); - checkState(policyStats.evictionCount() == stats.getCacheEvictions()); - } - - private static final class SameThreadExecutionService implements ExecutionService { - @Override public ScheduledExecutorService getScheduledExecutor(String alias) { - throw new UnsupportedOperationException(); - } - @Override public ExecutorService getOrderedExecutor( - String alias, BlockingQueue queue) { - return MoreExecutors.newDirectExecutorService(); - } - @Override public ExecutorService getUnorderedExecutor( - String alias, BlockingQueue queue) { - return MoreExecutors.newDirectExecutorService(); - } - @Override public void start(ServiceProvider provider) {} - @Override public void stop() {} } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/ExpiringMapPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/ExpiringMapPolicy.java index 21f1e32f00..3d9f488822 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/ExpiringMapPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/ExpiringMapPolicy.java @@ -22,8 +22,8 @@ import java.util.Set; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; +import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; import com.github.benmanes.caffeine.cache.simulator.policy.Policy; -import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy; import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.google.common.base.CaseFormat; @@ -38,7 +38,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ @PolicySpec(name = "product.ExpiringMap") -public final class ExpiringMapPolicy implements KeyOnlyPolicy { +public final class ExpiringMapPolicy implements Policy { private final Map cache; private final PolicyStats policyStats; private final int maximumSize; @@ -61,7 +61,8 @@ public static Set policies(Config config) { } @Override - public void record(long key) { + public void record(AccessEvent event) { + Long key = event.longKey(); var value = cache.get(key); if (value == null) { if (cache.size() == maximumSize) { diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/GuavaPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/GuavaPolicy.java index 446e5c3a1e..d8738ed0de 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/GuavaPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/GuavaPolicy.java @@ -16,6 +16,7 @@ package com.github.benmanes.caffeine.cache.simulator.policy.product; import static com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic.WEIGHTED; +import static com.google.common.base.Preconditions.checkState; import java.util.Set; @@ -40,28 +41,28 @@ public final class GuavaPolicy implements Policy { public GuavaPolicy(Config config, Set characteristics) { policyStats = new PolicyStats(name()); + var builder = CacheBuilder.newBuilder(); var settings = new BasicSettings(config); - CacheBuilder builder = CacheBuilder.newBuilder() - .removalListener(notification -> policyStats.recordEviction()); if (characteristics.contains(WEIGHTED)) { builder.maximumWeight(settings.maximumSize()); - builder.weigher((key, value) -> value.weight()); + builder.weigher((Long key, AccessEvent value) -> value.weight()); } else { builder.maximumSize(settings.maximumSize()); } - cache = builder.build(); + cache = builder.recordStats().build(); } @Override public void record(AccessEvent event) { - AccessEvent value = cache.getIfPresent(event.key()); + Long key = event.longKey(); + var value = cache.getIfPresent(key); if (value == null) { - cache.put(event.key(), event); + cache.put(key, event); policyStats.recordWeightedMiss(event.weight()); } else { policyStats.recordWeightedHit(event.weight()); if (event.weight() != value.weight()) { - cache.put(event.key(), event); + cache.put(key, event); } } } @@ -70,4 +71,12 @@ public void record(AccessEvent event) { public PolicyStats stats() { return policyStats; } + + @Override + public void finished() { + var stats = cache.stats(); + policyStats.addEvictions(stats.evictionCount()); + checkState(policyStats.hitCount() == stats.hitCount()); + checkState(policyStats.missCount() == stats.missCount()); + } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/HazelcastPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/HazelcastPolicy.java index e5fc65e6de..8fadd7b888 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/HazelcastPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/HazelcastPolicy.java @@ -24,8 +24,8 @@ import java.util.Set; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; +import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; import com.github.benmanes.caffeine.cache.simulator.policy.Policy; -import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy; import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.google.common.base.CaseFormat; @@ -48,7 +48,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ @PolicySpec(name = "product.Hazelcast") -public final class HazelcastPolicy implements KeyOnlyPolicy { +public final class HazelcastPolicy implements Policy { private final NearCache cache; private final PolicyStats policyStats; private final int maximumSize; @@ -78,8 +78,9 @@ public static Set policies(Config config) { } @Override - public void record(long key) { - Object value = cache.get(key); + public void record(AccessEvent event) { + Long key = event.longKey(); + var value = cache.get(key); if (value == null) { if (cache.size() == maximumSize) { policyStats.recordEviction(); diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/TCachePolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/TCachePolicy.java index 3c695be1af..d79b688736 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/TCachePolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/TCachePolicy.java @@ -15,14 +15,15 @@ */ package com.github.benmanes.caffeine.cache.simulator.policy.product; +import static com.google.common.base.Preconditions.checkState; import static java.util.stream.Collectors.toUnmodifiableSet; import java.util.EnumSet; import java.util.Set; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; +import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent; import com.github.benmanes.caffeine.cache.simulator.policy.Policy; -import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy; import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.google.common.base.CaseFormat; @@ -37,7 +38,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ @PolicySpec(name = "product.TCache") -public final class TCachePolicy implements KeyOnlyPolicy { +public final class TCachePolicy implements Policy { private final Cache cache; private final PolicyStats policyStats; private final TCacheFactory factory; @@ -61,8 +62,9 @@ public static Set policies(Config config) { } @Override - public void record(long key) { - Object value = cache.get(key); + public void record(AccessEvent event) { + Long key = event.longKey(); + var value = cache.get(key); if (value == null) { policyStats.recordMiss(); cache.put(key, Boolean.TRUE); @@ -78,8 +80,12 @@ public PolicyStats stats() { @Override public void finished() { + var stats = cache.statistics(); factory.close(); - policyStats.addEvictions(cache.statistics().getEvictionCount()); + + policyStats.addEvictions(stats.getEvictionCount()); + checkState(policyStats.hitCount() == stats.getHitCount()); + checkState(policyStats.missCount() == stats.getMissCount()); } public static final class TCacheSettings extends BasicSettings { diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimber.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimber.java index e255468968..e1a6133b2b 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimber.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimber.java @@ -16,7 +16,7 @@ package com.github.benmanes.caffeine.cache.simulator.policy.sketch.climbing; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; /** * A hill climbing algorithm to tune the admission window size. @@ -70,7 +70,7 @@ public enum Type { private Adaptation(double amount, Type type) { checkArgument(amount >= 0, "Step size %s must be positive", amount); - this.type = checkNotNull(type); + this.type = requireNonNull(type); this.amount = amount; } @@ -100,12 +100,11 @@ public static Adaptation decreaseWindow(double amount) { @Override public String toString() { - switch (type) { - case HOLD: return "0"; - case INCREASE_WINDOW: return "+" + amount; - case DECREASE_WINDOW: return "-" + amount; - } - throw new IllegalStateException(); + return switch (type) { + case HOLD -> "0"; + case INCREASE_WINDOW -> "+" + amount; + case DECREASE_WINDOW -> "-" + amount; + }; } } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimberWindowTinyLfuPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimberWindowTinyLfuPolicy.java index 65839b2ed7..549fac18b9 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimberWindowTinyLfuPolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/HillClimberWindowTinyLfuPolicy.java @@ -145,19 +145,11 @@ private void onMiss(long key) { /** Moves or promotes as if necessary. */ private void onHit(Node node) { requireNonNull(node.queue); + policyStats.recordHit(); switch (node.queue) { - case WINDOW: - onWindowHit(node); - policyStats.recordHit(); - return; - case PROBATION: - onProbationHit(node); - policyStats.recordHit(); - return; - case PROTECTED: - onProtectedHit(node); - policyStats.recordHit(); - return; + case WINDOW -> onWindowHit(node); + case PROBATION -> onProbationHit(node); + case PROTECTED -> onProtectedHit(node); } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java index 17b4a99762..c43edd0326 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java @@ -65,19 +65,19 @@ protected double adjust(double hitRate) { double previousMissRate = (1 - previousHitRate); double gradient = currentMissRate - previousMissRate; - switch (acceleration) { - case NONE: - return stepSize * gradient; - case MOMENTUM: + return switch (acceleration) { + case NONE -> stepSize * gradient; + case MOMENTUM -> { velocity = (beta * velocity) + (1 - beta) * gradient; - return stepSize * velocity; - case NESTEROV: + yield stepSize * velocity; + } + case NESTEROV -> { // http://cs231n.github.io/neural-networks-3/#sgd double previousVelocity = velocity; velocity = (beta * velocity) + stepSize * gradient; - return -(beta * previousVelocity) + ((1 + beta) * velocity); - } - throw new IllegalStateException("Unknown acceleration type: " + acceleration); + yield -(beta * previousVelocity) + ((1 + beta) * velocity); + } + }; } enum Acceleration { NONE, MOMENTUM, NESTEROV } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java index d5876355f7..b152b9c61f 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java @@ -96,11 +96,11 @@ public void record(long key) { if (node != null) { requireNonNull(node.type); switch (node.type) { - case MAIN: + case MAIN -> { node.moveToTail(headMain); policyStats.recordHit(); - return; - case OUT: + } + case OUT -> { node.remove(); sizeOut--; @@ -111,13 +111,11 @@ public void record(long key) { sizeMain++; policyStats.recordMiss(); - return; - case IN: + } + case IN -> // do nothing policyStats.recordHit(); - return; } - throw new IllegalStateException("Unknown type: " + node.type); } else { node = new Node(key); node.type = QueueType.IN; diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/Metrics.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/Metrics.java index 793b1dfb78..820a716940 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/Metrics.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/Metrics.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.io.Serializable; import java.util.Comparator; import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; @@ -25,43 +26,48 @@ import java.util.function.LongSupplier; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats.Metric; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats.Metric.MetricType; -import com.google.auto.value.AutoValue; import com.google.common.base.MoreObjects; +import com.google.errorprone.annotations.CanIgnoreReturnValue; /** * A utility for performing common operations against a {@link Metric}. * * @author ben.manes@gmail.com (Ben Manes) */ -@AutoValue -public abstract class Metrics { - public abstract Function objectFormatter(); - public abstract DoubleFunction percentFormatter(); - public abstract DoubleFunction doubleFormatter(); - public abstract LongFunction longFormatter(); +public record Metrics(Function objectFormatter, LongFunction longFormatter, + DoubleFunction percentFormatter, DoubleFunction doubleFormatter) { + + public Metrics { + requireNonNull(longFormatter); + requireNonNull(objectFormatter); + requireNonNull(doubleFormatter); + requireNonNull(percentFormatter); + } /** Returns the stringified value for the metric; empty if absent. */ public String format(Metric metric) { if (metric == null) { return ""; - } else if (metric.value() instanceof LongSupplier) { - long value = ((LongSupplier) metric.value()).getAsLong(); + } else if (metric.value() instanceof LongSupplier supplier) { + long value = supplier.getAsLong(); return (value > 0) || metric.required() ? longFormatter().apply(value) : ""; - } else if (metric.value() instanceof DoubleSupplier) { - double value = ((DoubleSupplier) metric.value()).getAsDouble(); + } else if (metric.value() instanceof DoubleSupplier supplier) { + double value = supplier.getAsDouble(); if ((value == 0.0) && !metric.required()) { return ""; } return (metric.type() == MetricType.PERCENT) ? percentFormatter().apply(value) : doubleFormatter().apply(value); - } else if (metric.value() instanceof Supplier) { - Object value = ((Supplier) metric.value()).get(); + } else if (metric.value() instanceof Supplier supplier) { + Object value = supplier.get(); return MoreObjects.firstNonNull(objectFormatter().apply(value), ""); } return MoreObjects.firstNonNull(objectFormatter().apply(metric.value()), ""); @@ -69,60 +75,86 @@ public String format(Metric metric) { /** A comparator to sort by the given column. */ public Comparator comparator(String header) { - return new MetricComparator(header); + return new MetricComparator(this, header); } public static Metrics.Builder builder() { - return new AutoValue_Metrics.Builder() + return new Metrics.Builder() .percentFormatter(value -> (value == 0.0) ? "" : Double.toString(100 * value)) .doubleFormatter(value -> (value == 0.0) ? "" : Double.toString(value)) .objectFormatter(object -> (object == null) ? "" : object.toString()) .longFormatter(value -> (value == 0) ? "" : Long.toString(value)); } - @AutoValue.Builder - public abstract static class Builder { - public abstract Builder objectFormatter(Function objectFormatter); - public abstract Builder percentFormatter(DoubleFunction percentFormatter); - public abstract Builder doubleFormatter(DoubleFunction doubleFormatter); - public abstract Builder longFormatter(LongFunction longFormatter); - public abstract Metrics build(); - } - - private final class MetricComparator implements Comparator { - private final String header; - - public MetricComparator(String header) { - this.header = requireNonNull(header); + private record MetricComparator(Metrics metrics, String header) + implements Comparator, Serializable { + private MetricComparator { + requireNonNull(metrics); + requireNonNull(header); } - - @Override - public int compare(PolicyStats p1, PolicyStats p2) { + @Override public int compare(PolicyStats p1, PolicyStats p2) { Metric metric1 = p1.metrics().get(header); Metric metric2 = p2.metrics().get(header); if (metric1 == null) { return (metric2 == null) ? 0 : -1; } else if (metric2 == null) { return 1; - } else if (metric1.value() instanceof LongSupplier) { - return Long.compare(((LongSupplier) metric1.value()).getAsLong(), - ((LongSupplier) metric2.value()).getAsLong()); - } else if (metric1.value() instanceof DoubleSupplier) { - return Double.compare(((DoubleSupplier) metric1.value()).getAsDouble(), - ((DoubleSupplier) metric2.value()).getAsDouble()); - } else if (metric1.value() instanceof Supplier) { - Object value1 = ((Supplier) metric1.value()).get(); - Object value2 = ((Supplier) metric2.value()).get(); + } else if (metric1.value() instanceof LongSupplier supplier1 + && metric2.value() instanceof LongSupplier supplier2) { + return Long.compare(supplier1.getAsLong(), supplier2.getAsLong()); + } else if (metric1.value() instanceof DoubleSupplier supplier1 + && metric2.value() instanceof DoubleSupplier supplier2) { + return Double.compare(supplier1.getAsDouble(), supplier2.getAsDouble()); + } else if (metric1.value() instanceof Supplier supplier1 + && metric2.value() instanceof Supplier supplier2) { + Object value1 = supplier1.get(); + Object value2 = supplier2.get(); if (value1 instanceof Comparable) { @SuppressWarnings("unchecked") - var comparator = (Comparable) value1; - return comparator.compareTo(value2); + var comparable = (Comparable) value1; + return comparable.compareTo(value2); } - return objectFormatter().apply(value1) - .compareTo(objectFormatter().apply(value2)); + return metrics.objectFormatter.apply(value1) + .compareTo(metrics.objectFormatter.apply(value2)); } - return objectFormatter().apply(metric1.value()) - .compareTo(objectFormatter().apply(metric2.value())); + return metrics.objectFormatter.apply(metric1.value()) + .compareTo(metrics.objectFormatter.apply(metric2.value())); + } + } + + public static final class Builder { + private @Nullable Function objectFormatter; + private @Nullable DoubleFunction percentFormatter; + private @Nullable DoubleFunction doubleFormatter; + private @Nullable LongFunction longFormatter; + + @CanIgnoreReturnValue + public Builder objectFormatter(Function objectFormatter) { + this.objectFormatter = requireNonNull(objectFormatter); + requireNonNull(objectFormatter); + return this; + } + @CanIgnoreReturnValue + public Builder percentFormatter(DoubleFunction percentFormatter) { + this.percentFormatter = requireNonNull(percentFormatter); + return this; + } + @CanIgnoreReturnValue + public Builder doubleFormatter(DoubleFunction doubleFormatter) { + this.doubleFormatter = requireNonNull(doubleFormatter); + return this; + } + @CanIgnoreReturnValue + public Builder longFormatter(LongFunction longFormatter) { + this.longFormatter = requireNonNull(longFormatter); + return this; + } + public Metrics build() { + requireNonNull(longFormatter); + requireNonNull(objectFormatter); + requireNonNull(doubleFormatter); + requireNonNull(percentFormatter); + return new Metrics(objectFormatter, longFormatter, percentFormatter, doubleFormatter); } } } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/csv/CombinedCsvReport.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/csv/CombinedCsvReport.java index 89e793b7a3..20abb37665 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/csv/CombinedCsvReport.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/report/csv/CombinedCsvReport.java @@ -116,7 +116,7 @@ private static final class Label implements Comparable