Skip to content

Commit c74e1e3

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

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 com.github.benmanes.caffeine.cache.Cache;
21+
import com.github.benmanes.caffeine.cache.Caffeine;
22+
import org.apache.kafka.common.metrics.KafkaMetric;
23+
import org.apache.kafka.common.metrics.Metrics;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.time.Duration;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.assertj.core.api.InstanceOfAssertFactories.DOUBLE;
30+
import static org.awaitility.Awaitility.await;
31+
32+
class CaffeineCacheMetricsTest {
33+
34+
@Test
35+
void testCacheHitMissMetric() {
36+
// Given
37+
final Metrics metrics = new Metrics();
38+
final Cache<String, String> cache = Caffeine.newBuilder()
39+
.recordStats()
40+
.build();
41+
new CaffeineCacheMetrics(metrics, cache);
42+
cache.put("key1", "value1");
43+
44+
// When there is a cache hit
45+
var value = cache.getIfPresent("key1"); // hit
46+
assertThat(value).isEqualTo("value1");
47+
48+
// Then
49+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_COUNT).metricValue())
50+
.asInstanceOf(DOUBLE)
51+
.isOne();
52+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_RATE).metricValue())
53+
.asInstanceOf(DOUBLE)
54+
.isOne();
55+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_RATE).metricValue())
56+
.asInstanceOf(DOUBLE)
57+
.isZero();
58+
59+
// When there is a cache miss
60+
value = cache.getIfPresent("key2"); // miss
61+
assertThat(value).isNull();
62+
63+
// Then
64+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_COUNT).metricValue())
65+
.asInstanceOf(DOUBLE)
66+
.isOne();
67+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_HIT_RATE).metricValue())
68+
.asInstanceOf(DOUBLE)
69+
.isEqualTo(0.5); // 1 hit, 1 miss
70+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_MISS_RATE).metricValue())
71+
.asInstanceOf(DOUBLE)
72+
.isEqualTo(0.5); // 1 hit, 1 miss
73+
74+
// overall load is zero
75+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_AVG_LOAD_PENALTY_NANOSECONDS).metricValue())
76+
.asInstanceOf(DOUBLE)
77+
.isZero();
78+
}
79+
80+
@Test
81+
void testCacheSizeAndEviction() {
82+
// Given
83+
final Metrics metrics = new Metrics();
84+
final Cache<String, String> cache = Caffeine.newBuilder()
85+
.maximumSize(1)
86+
.recordStats()
87+
.build();
88+
new CaffeineCacheMetrics(metrics, cache);
89+
90+
// Initially, cache size should be zero
91+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue())
92+
.asInstanceOf(DOUBLE)
93+
.isZero();
94+
95+
// When adding entries to the cache
96+
cache.put("key1", "value1");
97+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue())
98+
.asInstanceOf(DOUBLE)
99+
.isOne();
100+
101+
// Adding another entry should evict the first one due to max size of 1
102+
cache.put("key2", "value2"); // This should evict key1
103+
await().atMost(Duration.ofSeconds(10)).until(() ->
104+
// size should eventually come back to 1
105+
(double) getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_SIZE).metricValue() == 1.0
106+
);
107+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_EVICTION_COUNT).metricValue())
108+
.asInstanceOf(DOUBLE)
109+
.isOne();
110+
}
111+
112+
@Test
113+
void testCacheLoadTime() {
114+
final Metrics metrics = new Metrics();
115+
final Cache<String, String> cache = Caffeine.newBuilder()
116+
.recordStats()
117+
.build();
118+
new CaffeineCacheMetrics(metrics, cache);
119+
120+
// Simulate cache loads with some computation time
121+
cache.get("key1", key -> {
122+
try {
123+
Thread.sleep(10); // Simulate load time
124+
} catch (InterruptedException e) {
125+
Thread.currentThread().interrupt();
126+
}
127+
return "value1";
128+
});
129+
130+
assertThat(getMetric(metrics, CaffeineCacheMetricsRegistry.CACHE_AVG_LOAD_PENALTY_NANOSECONDS).metricValue())
131+
.asInstanceOf(DOUBLE)
132+
.isGreaterThan(0.0);
133+
}
134+
135+
private static KafkaMetric getMetric(Metrics metrics, String metricName) {
136+
return metrics.metric(metrics.metricName(metricName, CaffeineCacheMetricsRegistry.METRIC_GROUP));
137+
}
138+
}

0 commit comments

Comments
 (0)