From 91c53aad61442209c7467de33cce26fccd6232e4 Mon Sep 17 00:00:00 2001 From: ferid333 <135500346+ferid333@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:53:48 +1000 Subject: [PATCH] Phase-3: Adding metrics --- build.gradle | 9 ++++ src/main/java/org/cache/Main.java | 3 +- src/main/java/org/cache/core/LocalCache.java | 26 +++++++++--- .../org/cache/core/metrics/CacheMetrics.java | 41 +++++++++++++++++++ .../java/org/cache/core/metrics/Snapshot.java | 37 +++++++++++++++++ 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/cache/core/metrics/CacheMetrics.java create mode 100644 src/main/java/org/cache/core/metrics/Snapshot.java diff --git a/build.gradle b/build.gradle index 64b612e..51276ba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'application' } group = 'org.example' @@ -14,6 +15,14 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' } +application { + mainClass = 'org.cache.Main' +} + +tasks.withType(JavaCompile).configureEach { + options.release = 17 +} + test { useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/org/cache/Main.java b/src/main/java/org/cache/Main.java index 93cb8e4..f13ccb3 100644 --- a/src/main/java/org/cache/Main.java +++ b/src/main/java/org/cache/Main.java @@ -2,7 +2,6 @@ import org.cache.core.LocalCache; -import org.cache.eviction.EvictionPolicy; import org.cache.eviction.LruEvictionPolicy; public class Main { @@ -24,5 +23,7 @@ public static void main(String[] args) { System.out.println(localCache.get("test")); System.out.println(localCache.get("test1").orElse(null)); + + System.out.println(localCache.metrics().getEvictions()); } } \ No newline at end of file diff --git a/src/main/java/org/cache/core/LocalCache.java b/src/main/java/org/cache/core/LocalCache.java index 2dcda3f..0be6cb6 100644 --- a/src/main/java/org/cache/core/LocalCache.java +++ b/src/main/java/org/cache/core/LocalCache.java @@ -1,5 +1,7 @@ package org.cache.core; +import org.cache.core.metrics.CacheMetrics; +import org.cache.core.metrics.Snapshot; import org.cache.eviction.EvictionPolicy; import java.util.Iterator; @@ -15,6 +17,7 @@ public class LocalCache implements Cache, AutoCloseable { private final ConcurrentHashMap> cache; private final int capacity; private final EvictionPolicy evictionPolicy; + private final CacheMetrics metrics; private final Object evictionLock; private final int cleanupBatchSize; private final ScheduledExecutorService cleanupScheduler; @@ -34,6 +37,7 @@ public LocalCache( this.cache = new ConcurrentHashMap<>(); this.capacity = capacity; this.evictionPolicy = evictionPolicy; + this.metrics = new CacheMetrics(); this.evictionLock = new Object(); this.cleanupBatchSize = cleanupBatchSize; this.cleanupIterator = cache.entrySet().iterator(); @@ -62,8 +66,10 @@ public void put(K key, V value, long ttlMillis) { if (cache.size() > capacity) { evictionPolicy.selectVictim().ifPresent(victim -> { - cache.remove(victim); - evictionPolicy.onKeyRemoved(victim); + if (cache.remove(victim) != null) { + evictionPolicy.onKeyRemoved(victim); + metrics.recordEviction(); + } }); } } @@ -74,6 +80,7 @@ public Optional get(K key) { var entry = cache.get(key); if (entry == null) { + metrics.recordMiss(); return Optional.empty(); } @@ -81,17 +88,21 @@ public Optional get(K key) { var currentEntry = cache.get(key); if (currentEntry == null) { + metrics.recordMiss(); return Optional.empty(); } - if(currentEntry.isExpired()) { + if (currentEntry.isExpired()) { cache.remove(key); evictionPolicy.onKeyRemoved(key); + metrics.recordMiss(); + metrics.recordExpiration(); return Optional.empty(); } evictionPolicy.onKeyAccessed(key); + metrics.recordHit(); return Optional.ofNullable(currentEntry.getValue()); } } @@ -141,13 +152,18 @@ public synchronized void removeExpiredEntries() { if (currentEntry != null && currentEntry.isExpired()) { cache.remove(entry.getKey()); evictionPolicy.onKeyRemoved(entry.getKey()); + metrics.recordExpiration(); } } } } @Override - public void close() throws Exception { - cleanupScheduler.close(); + public void close() { + cleanupScheduler.shutdownNow(); + } + + public Snapshot metrics() { + return metrics.snapshot(); } } diff --git a/src/main/java/org/cache/core/metrics/CacheMetrics.java b/src/main/java/org/cache/core/metrics/CacheMetrics.java new file mode 100644 index 0000000..8486e7d --- /dev/null +++ b/src/main/java/org/cache/core/metrics/CacheMetrics.java @@ -0,0 +1,41 @@ +package org.cache.core.metrics; + +import java.util.concurrent.atomic.LongAdder; + +public class CacheMetrics { + + private final LongAdder hits = new LongAdder(); + private final LongAdder misses = new LongAdder(); + private final LongAdder evictions = new LongAdder(); + private final LongAdder expirations = new LongAdder(); + + public void recordHit() { + hits.increment(); + } + + public void recordMiss() { + misses.increment(); + } + + public void recordEviction() { + evictions.increment(); + } + + public void recordExpiration() { + expirations.increment(); + } + + public Snapshot snapshot() { + var hitCount = hits.sum(); + var missCount = misses.sum(); + var requestCount = hitCount + missCount; + + return new Snapshot( + hitCount, + missCount, + evictions.sum(), + expirations.sum(), + requestCount == 0 ? 0.0 : (double) hitCount / requestCount + ); + } +} diff --git a/src/main/java/org/cache/core/metrics/Snapshot.java b/src/main/java/org/cache/core/metrics/Snapshot.java new file mode 100644 index 0000000..30852b6 --- /dev/null +++ b/src/main/java/org/cache/core/metrics/Snapshot.java @@ -0,0 +1,37 @@ +package org.cache.core.metrics; + +public class Snapshot { + private final long hits; + private final long misses; + private final long evictions; + private final long expirations; + private final double hitRate; + + Snapshot(long hits, long misses, long evictions, long expirations, double hitRate) { + this.hits = hits; + this.misses = misses; + this.evictions = evictions; + this.expirations = expirations; + this.hitRate = hitRate; + } + + public long getHits() { + return hits; + } + + public long getMisses() { + return misses; + } + + public long getEvictions() { + return evictions; + } + + public long getExpirations() { + return expirations; + } + + public double getHitRate() { + return hitRate; + } +} \ No newline at end of file