or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asynchronous-caching.mdcache-construction.mdcache-policies.mdfunctional-interfaces.mdindex.mdstatistics.mdsynchronous-caching.md

statistics.mddocs/

0

# Statistics

1

2

Caffeine provides comprehensive cache performance statistics through the `CacheStats` class and configurable `StatsCounter` implementations. Statistics enable monitoring cache effectiveness, identifying performance bottlenecks, and making informed tuning decisions.

3

4

## CacheStats Class

5

6

The `CacheStats` class provides an immutable snapshot of cache performance metrics.

7

8

```java { .api }

9

public final class CacheStats {

10

// Request metrics

11

public long requestCount();

12

public long hitCount();

13

public double hitRate();

14

public long missCount();

15

public double missRate();

16

17

// Load metrics

18

public long loadCount();

19

public long loadSuccessCount();

20

public long loadFailureCount();

21

public double loadFailureRate();

22

public double averageLoadPenalty();

23

public long totalLoadTime();

24

25

// Eviction metrics

26

public long evictionCount();

27

public long evictionWeight();

28

29

// Statistics operations

30

public CacheStats minus(CacheStats other);

31

public CacheStats plus(CacheStats other);

32

33

// Factory methods

34

public static CacheStats empty();

35

public static CacheStats of(long hitCount, long missCount, long loadSuccessCount,

36

long loadFailureCount, long totalLoadTime,

37

long evictionCount, long evictionWeight);

38

}

39

```

40

41

### Basic Statistics Usage

42

43

```java

44

Cache<String, String> statsCache = Caffeine.newBuilder()

45

.maximumSize(1000)

46

.recordStats()

47

.build();

48

49

// Perform cache operations

50

statsCache.put("key1", "value1");

51

statsCache.put("key2", "value2");

52

statsCache.getIfPresent("key1"); // hit

53

statsCache.getIfPresent("key3"); // miss

54

55

// Get statistics snapshot

56

CacheStats stats = statsCache.stats();

57

58

// Request statistics

59

System.out.println("Total requests: " + stats.requestCount()); // 2

60

System.out.println("Hits: " + stats.hitCount()); // 1

61

System.out.println("Hit rate: " + stats.hitRate()); // 0.5

62

System.out.println("Misses: " + stats.missCount()); // 1

63

System.out.println("Miss rate: " + stats.missRate()); // 0.5

64

```

65

66

### Load Statistics

67

68

```java

69

LoadingCache<String, String> loadingCache = Caffeine.newBuilder()

70

.maximumSize(1000)

71

.recordStats()

72

.build(key -> {

73

if (key.equals("error")) {

74

throw new RuntimeException("Load error");

75

}

76

Thread.sleep(100); // Simulate load time

77

return "loaded_" + key;

78

});

79

80

// Perform operations that trigger loading

81

loadingCache.get("key1"); // successful load

82

loadingCache.get("key2"); // successful load

83

try {

84

loadingCache.get("error"); // failed load

85

} catch (Exception e) {

86

// Handle load failure

87

}

88

89

CacheStats loadStats = loadingCache.stats();

90

91

// Load statistics

92

System.out.println("Load count: " + loadStats.loadCount()); // 3

93

System.out.println("Load successes: " + loadStats.loadSuccessCount()); // 2

94

System.out.println("Load failures: " + loadStats.loadFailureCount()); // 1

95

System.out.println("Load failure rate: " + loadStats.loadFailureRate()); // 0.33

96

System.out.println("Average load time (ns): " + loadStats.averageLoadPenalty()); // ~100ms in ns

97

System.out.println("Total load time (ns): " + loadStats.totalLoadTime());

98

```

99

100

### Eviction Statistics

101

102

```java

103

Cache<String, String> evictionCache = Caffeine.newBuilder()

104

.maximumSize(3)

105

.recordStats()

106

.build();

107

108

// Fill cache beyond capacity to trigger evictions

109

evictionCache.put("key1", "value1");

110

evictionCache.put("key2", "value2");

111

evictionCache.put("key3", "value3");

112

evictionCache.put("key4", "value4"); // Triggers eviction

113

evictionCache.put("key5", "value5"); // Triggers eviction

114

115

CacheStats evictionStats = evictionCache.stats();

116

117

System.out.println("Evictions: " + evictionStats.evictionCount()); // 2

118

System.out.println("Eviction weight: " + evictionStats.evictionWeight()); // 2 (default weight 1 per entry)

119

```

120

121

### Weight-Based Eviction Statistics

122

123

```java

124

Cache<String, String> weightedCache = Caffeine.newBuilder()

125

.maximumWeight(10)

126

.weigher((key, value) -> key.length() + value.length())

127

.recordStats()

128

.build();

129

130

// Add entries with different weights

131

weightedCache.put("a", "1"); // weight: 2

132

weightedCache.put("bb", "22"); // weight: 4

133

weightedCache.put("ccc", "333"); // weight: 6

134

weightedCache.put("dddd", "4444"); // weight: 8, may trigger eviction

135

136

CacheStats weightStats = weightedCache.stats();

137

138

System.out.println("Evictions: " + weightStats.evictionCount());

139

System.out.println("Total evicted weight: " + weightStats.evictionWeight());

140

```

141

142

## Statistics Operations

143

144

### Combining Statistics

145

146

```java

147

Cache<String, String> cache1 = Caffeine.newBuilder()

148

.maximumSize(100)

149

.recordStats()

150

.build();

151

152

Cache<String, String> cache2 = Caffeine.newBuilder()

153

.maximumSize(100)

154

.recordStats()

155

.build();

156

157

// Perform operations on both caches

158

cache1.put("key1", "value1");

159

cache1.getIfPresent("key1"); // hit

160

161

cache2.put("key2", "value2");

162

cache2.getIfPresent("key3"); // miss

163

164

// Combine statistics

165

CacheStats stats1 = cache1.stats();

166

CacheStats stats2 = cache2.stats();

167

CacheStats combined = stats1.plus(stats2);

168

169

System.out.println("Combined hits: " + combined.hitCount()); // 1

170

System.out.println("Combined misses: " + combined.missCount()); // 1

171

System.out.println("Combined requests: " + combined.requestCount()); // 2

172

```

173

174

### Statistics Differences

175

176

```java

177

Cache<String, String> monitoredCache = Caffeine.newBuilder()

178

.maximumSize(1000)

179

.recordStats()

180

.build();

181

182

// Take baseline snapshot

183

CacheStats baseline = monitoredCache.stats();

184

185

// Perform operations

186

monitoredCache.put("key1", "value1");

187

monitoredCache.put("key2", "value2");

188

monitoredCache.getIfPresent("key1"); // hit

189

monitoredCache.getIfPresent("key3"); // miss

190

191

// Calculate delta

192

CacheStats current = monitoredCache.stats();

193

CacheStats delta = current.minus(baseline);

194

195

System.out.println("New hits: " + delta.hitCount()); // 1

196

System.out.println("New misses: " + delta.missCount()); // 1

197

System.out.println("New requests: " + delta.requestCount()); // 2

198

```

199

200

## StatsCounter Interface

201

202

The `StatsCounter` interface allows custom statistics collection and reporting.

203

204

```java { .api }

205

public interface StatsCounter {

206

// Recording methods

207

void recordHits(int count);

208

void recordMisses(int count);

209

void recordLoadSuccess(long loadTime);

210

void recordLoadFailure(long loadTime);

211

void recordEviction(int weight, RemovalCause cause);

212

213

// Snapshot method

214

CacheStats snapshot();

215

}

216

```

217

218

### Built-in StatsCounter Implementations

219

220

#### ConcurrentStatsCounter

221

222

```java

223

// Default thread-safe implementation using LongAdder

224

Cache<String, String> concurrentStatsCache = Caffeine.newBuilder()

225

.maximumSize(1000)

226

.recordStats(ConcurrentStatsCounter::new)

227

.build();

228

229

// High-concurrency statistics collection

230

ExecutorService executor = Executors.newFixedThreadPool(10);

231

for (int i = 0; i < 100; i++) {

232

final int threadId = i;

233

executor.submit(() -> {

234

concurrentStatsCache.put("key_" + threadId, "value_" + threadId);

235

concurrentStatsCache.getIfPresent("key_" + threadId);

236

});

237

}

238

239

// Statistics remain accurate under high concurrency

240

CacheStats concurrentStats = concurrentStatsCache.stats();

241

System.out.println("Concurrent hit rate: " + concurrentStats.hitRate());

242

```

243

244

#### Custom StatsCounter Implementation

245

246

```java

247

public class CustomStatsCounter implements StatsCounter {

248

private final AtomicLong hits = new AtomicLong();

249

private final AtomicLong misses = new AtomicLong();

250

private final AtomicLong loadSuccesses = new AtomicLong();

251

private final AtomicLong loadFailures = new AtomicLong();

252

private final AtomicLong totalLoadTime = new AtomicLong();

253

private final AtomicLong evictions = new AtomicLong();

254

private final AtomicLong evictionWeight = new AtomicLong();

255

256

@Override

257

public void recordHits(int count) {

258

hits.addAndGet(count);

259

// Custom logging or external metrics reporting

260

logger.info("Cache hits recorded: {}", count);

261

}

262

263

@Override

264

public void recordMisses(int count) {

265

misses.addAndGet(count);

266

// Alert on high miss rates

267

long totalRequests = hits.get() + misses.get();

268

if (totalRequests > 100 && (double) misses.get() / totalRequests > 0.9) {

269

alertingService.sendAlert("High cache miss rate detected");

270

}

271

}

272

273

@Override

274

public void recordLoadSuccess(long loadTime) {

275

loadSuccesses.incrementAndGet();

276

totalLoadTime.addAndGet(loadTime);

277

// Track slow loads

278

if (loadTime > TimeUnit.SECONDS.toNanos(1)) {

279

logger.warn("Slow cache load detected: {}ms", TimeUnit.NANOSECONDS.toMillis(loadTime));

280

}

281

}

282

283

@Override

284

public void recordLoadFailure(long loadTime) {

285

loadFailures.incrementAndGet();

286

totalLoadTime.addAndGet(loadTime);

287

logger.error("Cache load failure after {}ms", TimeUnit.NANOSECONDS.toMillis(loadTime));

288

}

289

290

@Override

291

public void recordEviction(int weight, RemovalCause cause) {

292

evictions.incrementAndGet();

293

evictionWeight.addAndGet(weight);

294

metricsRegistry.counter("cache.evictions", "cause", cause.name()).increment();

295

}

296

297

@Override

298

public CacheStats snapshot() {

299

return CacheStats.of(

300

hits.get(),

301

misses.get(),

302

loadSuccesses.get(),

303

loadFailures.get(),

304

totalLoadTime.get(),

305

evictions.get(),

306

evictionWeight.get()

307

);

308

}

309

}

310

311

// Use custom stats counter

312

Cache<String, String> customStatsCache = Caffeine.newBuilder()

313

.maximumSize(1000)

314

.recordStats(CustomStatsCounter::new)

315

.build();

316

```

317

318

## Statistics Monitoring and Alerting

319

320

### Periodic Statistics Collection

321

322

```java

323

Cache<String, String> monitoredCache = Caffeine.newBuilder()

324

.maximumSize(10000)

325

.expireAfterAccess(Duration.ofMinutes(30))

326

.recordStats()

327

.build();

328

329

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

330

331

scheduler.scheduleAtFixedRate(() -> {

332

CacheStats stats = monitoredCache.stats();

333

334

// Log key metrics

335

System.out.printf("Cache Stats - Requests: %d, Hit Rate: %.2f%%, " +

336

"Avg Load Time: %.2fms, Evictions: %d%n",

337

stats.requestCount(),

338

stats.hitRate() * 100,

339

stats.averageLoadPenalty() / 1_000_000.0,

340

stats.evictionCount());

341

342

// Send metrics to monitoring system

343

metricsCollector.gauge("cache.hit_rate", stats.hitRate());

344

metricsCollector.gauge("cache.miss_rate", stats.missRate());

345

metricsCollector.gauge("cache.avg_load_time_ms", stats.averageLoadPenalty() / 1_000_000.0);

346

metricsCollector.counter("cache.evictions").increment(stats.evictionCount());

347

348

}, 0, 30, TimeUnit.SECONDS);

349

```

350

351

### Performance Analysis

352

353

```java

354

public class CachePerformanceAnalyzer {

355

public void analyzePerformance(Cache<?, ?> cache) {

356

CacheStats stats = cache.stats();

357

358

// Hit rate analysis

359

double hitRate = stats.hitRate();

360

if (hitRate < 0.8) {

361

System.out.println("WARNING: Low hit rate (" + (hitRate * 100) + "%)");

362

System.out.println("Consider: increasing cache size, adjusting expiration, or reviewing access patterns");

363

}

364

365

// Load time analysis

366

double avgLoadTimeMs = stats.averageLoadPenalty() / 1_000_000.0;

367

if (avgLoadTimeMs > 100) {

368

System.out.println("WARNING: High average load time (" + avgLoadTimeMs + "ms)");

369

System.out.println("Consider: optimizing data loading, adding async loading, or caching strategies");

370

}

371

372

// Load failure analysis

373

double loadFailureRate = stats.loadFailureRate();

374

if (loadFailureRate > 0.05) {

375

System.out.println("WARNING: High load failure rate (" + (loadFailureRate * 100) + "%)");

376

System.out.println("Consider: improving error handling, adding retry logic, or fallback values");

377

}

378

379

// Eviction analysis

380

long evictionCount = stats.evictionCount();

381

long requestCount = stats.requestCount();

382

if (requestCount > 0 && evictionCount > requestCount * 0.1) {

383

System.out.println("WARNING: High eviction rate (" + evictionCount + " evictions)");

384

System.out.println("Consider: increasing cache size, adjusting expiration, or using weight-based eviction");

385

}

386

387

// Overall health summary

388

System.out.printf("Cache Health Summary:%n" +

389

" Requests: %d%n" +

390

" Hit Rate: %.1f%%%n" +

391

" Avg Load Time: %.1fms%n" +

392

" Load Failures: %d (%.1f%%)%n" +

393

" Evictions: %d%n",

394

requestCount,

395

hitRate * 100,

396

avgLoadTimeMs,

397

stats.loadFailureCount(),

398

loadFailureRate * 100,

399

evictionCount);

400

}

401

}

402

```

403

404

## Statistics Best Practices

405

406

### When to Enable Statistics

407

408

```java

409

// Enable for production monitoring

410

Cache<String, String> productionCache = Caffeine.newBuilder()

411

.maximumSize(10000)

412

.recordStats() // Small overhead, valuable insights

413

.build();

414

415

// Disable for high-frequency, low-value caches

416

Cache<String, String> highFreqCache = Caffeine.newBuilder()

417

.maximumSize(1000)

418

// No recordStats() - avoid overhead for simple caches

419

.build();

420

421

// Use custom counter for specialized monitoring

422

Cache<String, String> specializedCache = Caffeine.newBuilder()

423

.maximumSize(5000)

424

.recordStats(() -> new CustomMetricsStatsCounter(metricsRegistry))

425

.build();

426

```

427

428

### Performance Considerations

429

430

- **Overhead**: Statistics collection adds minimal overhead (typically <2% performance impact)

431

- **Memory**: Statistics counters use additional memory (typically 8-16 bytes per cache)

432

- **Concurrency**: `ConcurrentStatsCounter` provides excellent concurrent performance using `LongAdder`

433

- **Custom Counters**: Expensive operations in custom counters can impact cache performance