Skip to content

Commit 46cfe26

Browse files
committed
fixup! refactor(inkless): reuse storage metrics instance on cache
Add tests
1 parent f0bcd4e commit 46cfe26

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Inkless
3+
* Copyright (C) 2024 - 2025 Aiven OY
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package io.aiven.inkless.cache;
19+
20+
import org.apache.kafka.common.metrics.KafkaMetric;
21+
import org.apache.kafka.common.metrics.Metrics;
22+
23+
import com.github.benmanes.caffeine.cache.Cache;
24+
import com.github.benmanes.caffeine.cache.Caffeine;
25+
26+
import org.junit.jupiter.api.Test;
27+
28+
import java.time.Duration;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.InstanceOfAssertFactories.DOUBLE;
32+
import static org.awaitility.Awaitility.await;
33+
34+
class CaffeineCacheMetricsTest {
35+
36+
@Test
37+
void testCacheHitMissMetric() {
38+
// Given
39+
final Metrics metrics = new Metrics();
40+
final Cache<String, String> cache = Caffeine.newBuilder()
41+
.recordStats()
42+
.build();
43+
new CaffeineCacheMetrics(metrics, cache);
44+
cache.put("key1", "value1");
45+
46+
// When there is a cache hit
47+
var value = cache.getIfPresent("key1"); // hit
48+
assertThat(value).isEqualTo("value1");
49+
50+
// Then
51+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_COUNT).metricValue())
52+
.asInstanceOf(DOUBLE)
53+
.isOne();
54+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_RATE).metricValue())
55+
.asInstanceOf(DOUBLE)
56+
.isOne();
57+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_RATE).metricValue())
58+
.asInstanceOf(DOUBLE)
59+
.isZero();
60+
61+
// When there is a cache miss
62+
value = cache.getIfPresent("key2"); // miss
63+
assertThat(value).isNull();
64+
65+
// Then
66+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_COUNT).metricValue())
67+
.asInstanceOf(DOUBLE)
68+
.isOne();
69+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_RATE).metricValue())
70+
.asInstanceOf(DOUBLE)
71+
.isEqualTo(0.5); // 1 hit, 1 miss
72+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_RATE).metricValue())
73+
.asInstanceOf(DOUBLE)
74+
.isEqualTo(0.5); // 1 hit, 1 miss
75+
76+
// overall load is zero
77+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_AVG_LOAD_PENALTY_NANOSECONDS).metricValue())
78+
.asInstanceOf(DOUBLE)
79+
.isZero();
80+
}
81+
82+
@Test
83+
void testCacheSizeAndEviction() {
84+
// Given
85+
final Metrics metrics = new Metrics();
86+
final Cache<String, String> cache = Caffeine.newBuilder()
87+
.maximumSize(1)
88+
.recordStats()
89+
.build();
90+
new CaffeineCacheMetrics(metrics, cache);
91+
92+
// Initially, cache size should be zero
93+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue())
94+
.asInstanceOf(DOUBLE)
95+
.isZero();
96+
97+
// When adding entries to the cache
98+
cache.put("key1", "value1");
99+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue())
100+
.asInstanceOf(DOUBLE)
101+
.isOne();
102+
103+
// Adding another entry should evict the first one due to max size of 1
104+
cache.put("key2", "value2"); // This should evict key1
105+
await().atMost(Duration.ofSeconds(10)).until(() ->
106+
// size should eventually come back to 1
107+
(double) getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue() == 1.0
108+
);
109+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_EVICTION_COUNT).metricValue())
110+
.asInstanceOf(DOUBLE)
111+
.isOne();
112+
}
113+
114+
@Test
115+
void testCacheLoadTime() {
116+
final Metrics metrics = new Metrics();
117+
final Cache<String, String> cache = Caffeine.newBuilder()
118+
.recordStats()
119+
.build();
120+
new CaffeineCacheMetrics(metrics, cache);
121+
122+
// Simulate cache loads with some computation time
123+
cache.get("key1", key -> {
124+
try {
125+
Thread.sleep(10); // Simulate load time
126+
} catch (InterruptedException e) {
127+
Thread.currentThread().interrupt();
128+
}
129+
return "value1";
130+
});
131+
132+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_AVG_LOAD_PENALTY_NANOSECONDS).metricValue())
133+
.asInstanceOf(DOUBLE)
134+
.isGreaterThan(0.0);
135+
}
136+
137+
private static KafkaMetric getMetric(Metrics metrics, String metricName) {
138+
return metrics.metric(metrics.metricName(metricName, CaffeineCacheMetricsRegistry.METRIC_GROUP));
139+
}
140+
}

0 commit comments

Comments
 (0)