Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'application'
}

group = 'org.example'
Expand All @@ -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()
}
3 changes: 2 additions & 1 deletion src/main/java/org/cache/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


import org.cache.core.LocalCache;
import org.cache.eviction.EvictionPolicy;
import org.cache.eviction.LruEvictionPolicy;

public class Main {
Expand All @@ -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());
}
}
26 changes: 21 additions & 5 deletions src/main/java/org/cache/core/LocalCache.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +17,7 @@ public class LocalCache<K, V> implements Cache<K, V>, AutoCloseable {
private final ConcurrentHashMap<K, CacheEntry<V>> cache;
private final int capacity;
private final EvictionPolicy<K> evictionPolicy;
private final CacheMetrics metrics;
private final Object evictionLock;
private final int cleanupBatchSize;
private final ScheduledExecutorService cleanupScheduler;
Expand All @@ -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();
Expand Down Expand Up @@ -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();
}
});
}
}
Expand All @@ -74,24 +80,29 @@ public Optional<V> get(K key) {
var entry = cache.get(key);

if (entry == null) {
metrics.recordMiss();
return Optional.empty();
}

synchronized (evictionLock) {
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());
}
}
Expand Down Expand Up @@ -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();
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/cache/core/metrics/CacheMetrics.java
Original file line number Diff line number Diff line change
@@ -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
);
}
}
37 changes: 37 additions & 0 deletions src/main/java/org/cache/core/metrics/Snapshot.java
Original file line number Diff line number Diff line change
@@ -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;
}
}