CacheStatistics.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.impl.cache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import org.apache.maven.api.cache.CacheRetention;
/**
* Cache statistics that tracks detailed metrics
* about cache performance and usage patterns.
* <p>
* This implementation integrates with the improved cache architecture and
* provides thread-safe statistics tracking with minimal performance overhead.
* </p>
*/
public class CacheStatistics {
private final AtomicLong totalRequests = new AtomicLong();
private final AtomicLong cacheHits = new AtomicLong();
private final AtomicLong cacheMisses = new AtomicLong();
private final AtomicLong cachedExceptions = new AtomicLong();
// Enhanced eviction tracking
private final AtomicLong keyEvictions = new AtomicLong();
private final AtomicLong valueEvictions = new AtomicLong();
private final AtomicLong totalEvictions = new AtomicLong();
private final Map<String, RequestTypeStatistics> requestTypeStats = new ConcurrentHashMap<>();
private final Map<CacheRetention, RetentionStatistics> retentionStats = new ConcurrentHashMap<>();
private final Map<CacheRetention, Supplier<Long>> cacheSizeSuppliers = new ConcurrentHashMap<>();
// Reference type statistics
private final Map<String, ReferenceTypeStatistics> referenceTypeStats = new ConcurrentHashMap<>();
public long getTotalRequests() {
return totalRequests.get();
}
public long getCacheHits() {
return cacheHits.get();
}
public long getCacheMisses() {
return cacheMisses.get();
}
public double getHitRatio() {
long total = getTotalRequests();
return total == 0 ? 0.0 : (getCacheHits() * 100.0) / total;
}
public double getMissRatio() {
long total = getTotalRequests();
return total == 0 ? 0.0 : (getCacheMisses() * 100.0) / total;
}
public Map<String, RequestTypeStatistics> getRequestTypeStatistics() {
return Map.copyOf(requestTypeStats);
}
public Map<CacheRetention, RetentionStatistics> getRetentionStatistics() {
return Map.copyOf(retentionStats);
}
public Map<String, ReferenceTypeStatistics> getReferenceTypeStatistics() {
return Map.copyOf(referenceTypeStats);
}
public Map<CacheRetention, Long> getCacheSizes() {
Map<CacheRetention, Long> sizes = new ConcurrentHashMap<>();
cacheSizeSuppliers.forEach((retention, supplier) -> sizes.put(retention, supplier.get()));
return sizes;
}
public long getCachedExceptions() {
return cachedExceptions.get();
}
/**
* Returns the total number of key evictions across all caches.
*/
public long getKeyEvictions() {
return keyEvictions.get();
}
/**
* Returns the total number of value evictions across all caches.
*/
public long getValueEvictions() {
return valueEvictions.get();
}
/**
* Returns the total number of evictions (keys + values).
*/
public long getTotalEvictions() {
return totalEvictions.get();
}
/**
* Returns the ratio of key evictions to total evictions.
*/
public double getKeyEvictionRatio() {
long total = getTotalEvictions();
return total == 0 ? 0.0 : (getKeyEvictions() * 100.0) / total;
}
/**
* Returns the ratio of value evictions to total evictions.
*/
public double getValueEvictionRatio() {
long total = getTotalEvictions();
return total == 0 ? 0.0 : (getValueEvictions() * 100.0) / total;
}
/**
* Records a cache hit for the given request type and retention policy.
*/
public void recordHit(String requestType, CacheRetention retention) {
totalRequests.incrementAndGet();
cacheHits.incrementAndGet();
requestTypeStats
.computeIfAbsent(requestType, RequestTypeStatistics::new)
.recordHit();
retentionStats.computeIfAbsent(retention, RetentionStatistics::new).recordHit();
}
/**
* Records a cache miss for the given request type and retention policy.
*/
public void recordMiss(String requestType, CacheRetention retention) {
totalRequests.incrementAndGet();
cacheMisses.incrementAndGet();
requestTypeStats
.computeIfAbsent(requestType, RequestTypeStatistics::new)
.recordMiss();
retentionStats.computeIfAbsent(retention, RetentionStatistics::new).recordMiss();
}
/**
* Records a cached exception.
*/
public void recordCachedException() {
cachedExceptions.incrementAndGet();
}
/**
* Records a key eviction for the specified retention policy.
*/
public void recordKeyEviction(CacheRetention retention) {
keyEvictions.incrementAndGet();
totalEvictions.incrementAndGet();
retentionStats.computeIfAbsent(retention, RetentionStatistics::new).recordKeyEviction();
}
/**
* Records a value eviction for the specified retention policy.
*/
public void recordValueEviction(CacheRetention retention) {
valueEvictions.incrementAndGet();
totalEvictions.incrementAndGet();
retentionStats.computeIfAbsent(retention, RetentionStatistics::new).recordValueEviction();
}
/**
* Registers a cache size supplier for the given retention policy.
*/
public void registerCacheSizeSupplier(CacheRetention retention, Supplier<Long> sizeSupplier) {
cacheSizeSuppliers.put(retention, sizeSupplier);
retentionStats.computeIfAbsent(retention, RetentionStatistics::new).setSizeSupplier(sizeSupplier);
}
/**
* Returns eviction statistics by retention policy.
*/
public Map<CacheRetention, Long> getKeyEvictionsByRetention() {
Map<CacheRetention, Long> evictions = new ConcurrentHashMap<>();
retentionStats.forEach((retention, stats) -> evictions.put(retention, stats.getKeyEvictions()));
return evictions;
}
/**
* Returns value eviction statistics by retention policy.
*/
public Map<CacheRetention, Long> getValueEvictionsByRetention() {
Map<CacheRetention, Long> evictions = new ConcurrentHashMap<>();
retentionStats.forEach((retention, stats) -> evictions.put(retention, stats.getValueEvictions()));
return evictions;
}
/**
* Records cache creation with specific reference types.
*/
public void recordCacheCreation(String keyRefType, String valueRefType, CacheRetention retention) {
String refTypeKey = keyRefType + "/" + valueRefType;
referenceTypeStats
.computeIfAbsent(refTypeKey, ReferenceTypeStatistics::new)
.recordCacheCreation(retention);
}
/**
* Records cache access for specific reference types.
*/
public void recordCacheAccess(String keyRefType, String valueRefType, boolean hit) {
String refTypeKey = keyRefType + "/" + valueRefType;
ReferenceTypeStatistics stats = referenceTypeStats.computeIfAbsent(refTypeKey, ReferenceTypeStatistics::new);
if (hit) {
stats.recordHit();
} else {
stats.recordMiss();
}
}
/**
* Default implementation of request type statistics.
*/
public static class RequestTypeStatistics {
private final String requestType;
private final AtomicLong hits = new AtomicLong();
private final AtomicLong misses = new AtomicLong();
RequestTypeStatistics(String requestType) {
this.requestType = requestType;
}
public String getRequestType() {
return requestType;
}
public long getHits() {
return hits.get();
}
public long getMisses() {
return misses.get();
}
public long getTotal() {
return getHits() + getMisses();
}
public double getHitRatio() {
long total = getTotal();
return total == 0 ? 0.0 : (getHits() * 100.0) / total;
}
void recordHit() {
hits.incrementAndGet();
}
void recordMiss() {
misses.incrementAndGet();
}
}
/**
* Default implementation of retention statistics.
*/
public static class RetentionStatistics {
private final CacheRetention retention;
private final AtomicLong hits = new AtomicLong();
private final AtomicLong misses = new AtomicLong();
private final AtomicLong keyEvictions = new AtomicLong();
private final AtomicLong valueEvictions = new AtomicLong();
private volatile Supplier<Long> sizeSupplier = () -> 0L;
RetentionStatistics(CacheRetention retention) {
this.retention = retention;
}
public CacheRetention getRetention() {
return retention;
}
public long getHits() {
return hits.get();
}
public long getMisses() {
return misses.get();
}
public long getTotal() {
return getHits() + getMisses();
}
public double getHitRatio() {
long total = getTotal();
return total == 0 ? 0.0 : (getHits() * 100.0) / total;
}
public long getCurrentSize() {
return sizeSupplier.get();
}
public long getKeyEvictions() {
return keyEvictions.get();
}
public long getValueEvictions() {
return valueEvictions.get();
}
public long getTotalEvictions() {
return getKeyEvictions() + getValueEvictions();
}
public double getKeyEvictionRatio() {
long total = getTotalEvictions();
return total == 0 ? 0.0 : (getKeyEvictions() * 100.0) / total;
}
void recordHit() {
hits.incrementAndGet();
}
void recordMiss() {
misses.incrementAndGet();
}
void recordKeyEviction() {
keyEvictions.incrementAndGet();
}
void recordValueEviction() {
valueEvictions.incrementAndGet();
}
void setSizeSupplier(Supplier<Long> sizeSupplier) {
this.sizeSupplier = sizeSupplier;
}
}
/**
* Statistics for specific reference type combinations.
*/
public static class ReferenceTypeStatistics {
private final String referenceTypeKey;
private final AtomicLong hits = new AtomicLong();
private final AtomicLong misses = new AtomicLong();
private final AtomicLong cacheCreations = new AtomicLong();
private final Map<CacheRetention, AtomicLong> creationsByRetention = new ConcurrentHashMap<>();
ReferenceTypeStatistics(String referenceTypeKey) {
this.referenceTypeKey = referenceTypeKey;
}
public String getReferenceTypeKey() {
return referenceTypeKey;
}
public long getHits() {
return hits.get();
}
public long getMisses() {
return misses.get();
}
public long getTotal() {
return getHits() + getMisses();
}
public double getHitRatio() {
long total = getTotal();
return total == 0 ? 0.0 : (getHits() * 100.0) / total;
}
public long getCacheCreations() {
return cacheCreations.get();
}
public Map<CacheRetention, Long> getCreationsByRetention() {
Map<CacheRetention, Long> result = new ConcurrentHashMap<>();
creationsByRetention.forEach((retention, count) -> result.put(retention, count.get()));
return result;
}
void recordHit() {
hits.incrementAndGet();
}
void recordMiss() {
misses.incrementAndGet();
}
void recordCacheCreation(CacheRetention retention) {
cacheCreations.incrementAndGet();
creationsByRetention
.computeIfAbsent(retention, k -> new AtomicLong())
.incrementAndGet();
}
}
}