001package io.prometheus.metrics.instrumentation.caffeine; 002 003import com.github.benmanes.caffeine.cache.AsyncCache; 004import com.github.benmanes.caffeine.cache.Cache; 005import com.github.benmanes.caffeine.cache.LoadingCache; 006import com.github.benmanes.caffeine.cache.stats.CacheStats; 007import io.prometheus.metrics.model.registry.MultiCollector; 008import io.prometheus.metrics.model.snapshots.CounterSnapshot; 009import io.prometheus.metrics.model.snapshots.GaugeSnapshot; 010import io.prometheus.metrics.model.snapshots.Labels; 011import io.prometheus.metrics.model.snapshots.MetricSnapshots; 012import io.prometheus.metrics.model.snapshots.SummarySnapshot; 013import java.util.Arrays; 014import java.util.Collections; 015import java.util.List; 016import java.util.Map; 017import java.util.concurrent.ConcurrentHashMap; 018import java.util.concurrent.ConcurrentMap; 019 020/** 021 * Collect metrics from Caffeine's com.github.benmanes.caffeine.cache.Cache. 022 * 023 * <p> 024 * 025 * <pre>{@code 026 * // Note that `recordStats()` is required to gather non-zero statistics 027 * Cache<String, String> cache = Caffeine.newBuilder().recordStats().build(); 028 * CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); 029 * PrometheusRegistry.defaultRegistry.register(cacheMetrics); 030 * cacheMetrics.addCache("mycache", cache); 031 * 032 * }</pre> 033 * 034 * Exposed metrics are labeled with the provided cache name. 035 * 036 * <p>With the example above, sample metric names would be: 037 * 038 * <pre> 039 * caffeine_cache_hit_total{cache="mycache"} 10.0 040 * caffeine_cache_miss_total{cache="mycache"} 3.0 041 * caffeine_cache_requests_total{cache="mycache"} 13.0 042 * caffeine_cache_eviction_total{cache="mycache"} 1.0 043 * caffeine_cache_estimated_size{cache="mycache"} 5.0 044 * </pre> 045 * 046 * Additionally, if the cache includes a loader, the following metrics would be provided: 047 * 048 * <pre> 049 * caffeine_cache_load_failure_total{cache="mycache"} 2.0 050 * caffeine_cache_loads_total{cache="mycache"} 7.0 051 * caffeine_cache_load_duration_seconds_count{cache="mycache"} 7.0 052 * caffeine_cache_load_duration_seconds_sum{cache="mycache"} 0.0034 053 * </pre> 054 */ 055public class CacheMetricsCollector implements MultiCollector { 056 private static final double NANOSECONDS_PER_SECOND = 1_000_000_000.0; 057 058 private static final String METRIC_NAME_CACHE_HIT = "caffeine_cache_hit"; 059 private static final String METRIC_NAME_CACHE_MISS = "caffeine_cache_miss"; 060 private static final String METRIC_NAME_CACHE_REQUESTS = "caffeine_cache_requests"; 061 private static final String METRIC_NAME_CACHE_EVICTION = "caffeine_cache_eviction"; 062 private static final String METRIC_NAME_CACHE_EVICTION_WEIGHT = "caffeine_cache_eviction_weight"; 063 private static final String METRIC_NAME_CACHE_LOAD_FAILURE = "caffeine_cache_load_failure"; 064 private static final String METRIC_NAME_CACHE_LOADS = "caffeine_cache_loads"; 065 private static final String METRIC_NAME_CACHE_ESTIMATED_SIZE = "caffeine_cache_estimated_size"; 066 private static final String METRIC_NAME_CACHE_LOAD_DURATION_SECONDS = 067 "caffeine_cache_load_duration_seconds"; 068 069 private static final List<String> ALL_METRIC_NAMES = 070 Collections.unmodifiableList( 071 Arrays.asList( 072 METRIC_NAME_CACHE_HIT, 073 METRIC_NAME_CACHE_MISS, 074 METRIC_NAME_CACHE_REQUESTS, 075 METRIC_NAME_CACHE_EVICTION, 076 METRIC_NAME_CACHE_EVICTION_WEIGHT, 077 METRIC_NAME_CACHE_LOAD_FAILURE, 078 METRIC_NAME_CACHE_LOADS, 079 METRIC_NAME_CACHE_ESTIMATED_SIZE, 080 METRIC_NAME_CACHE_LOAD_DURATION_SECONDS)); 081 082 protected final ConcurrentMap<String, Cache<?, ?>> children = new ConcurrentHashMap<>(); 083 084 /** 085 * Add or replace the cache with the given name. 086 * 087 * <p>Any references any previous cache with this name is invalidated. 088 * 089 * @param cacheName The name of the cache, will be the metrics label value 090 * @param cache The cache being monitored 091 */ 092 public void addCache(String cacheName, Cache<?, ?> cache) { 093 children.put(cacheName, cache); 094 } 095 096 /** 097 * Add or replace the cache with the given name. 098 * 099 * <p>Any references any previous cache with this name is invalidated. 100 * 101 * @param cacheName The name of the cache, will be the metrics label value 102 * @param cache The cache being monitored 103 */ 104 public void addCache(String cacheName, AsyncCache<?, ?> cache) { 105 children.put(cacheName, cache.synchronous()); 106 } 107 108 /** 109 * Remove the cache with the given name. 110 * 111 * <p>Any references to the cache are invalidated. 112 * 113 * @param cacheName cache to be removed 114 */ 115 public Cache<?, ?> removeCache(String cacheName) { 116 return children.remove(cacheName); 117 } 118 119 /** 120 * Remove all caches. 121 * 122 * <p>Any references to all caches are invalidated. 123 */ 124 public void clear() { 125 children.clear(); 126 } 127 128 @Override 129 public MetricSnapshots collect() { 130 final MetricSnapshots.Builder metricSnapshotsBuilder = MetricSnapshots.builder(); 131 final List<String> labelNames = Arrays.asList("cache"); 132 133 final CounterSnapshot.Builder cacheHitTotal = 134 CounterSnapshot.builder().name(METRIC_NAME_CACHE_HIT).help("Cache hit totals"); 135 136 final CounterSnapshot.Builder cacheMissTotal = 137 CounterSnapshot.builder().name(METRIC_NAME_CACHE_MISS).help("Cache miss totals"); 138 139 final CounterSnapshot.Builder cacheRequestsTotal = 140 CounterSnapshot.builder() 141 .name(METRIC_NAME_CACHE_REQUESTS) 142 .help("Cache request totals, hits + misses"); 143 144 final CounterSnapshot.Builder cacheEvictionTotal = 145 CounterSnapshot.builder() 146 .name(METRIC_NAME_CACHE_EVICTION) 147 .help("Cache eviction totals, doesn't include manually removed entries"); 148 149 final GaugeSnapshot.Builder cacheEvictionWeight = 150 GaugeSnapshot.builder() 151 .name(METRIC_NAME_CACHE_EVICTION_WEIGHT) 152 .help("Cache eviction weight"); 153 154 final CounterSnapshot.Builder cacheLoadFailure = 155 CounterSnapshot.builder().name(METRIC_NAME_CACHE_LOAD_FAILURE).help("Cache load failures"); 156 157 final CounterSnapshot.Builder cacheLoadTotal = 158 CounterSnapshot.builder() 159 .name(METRIC_NAME_CACHE_LOADS) 160 .help("Cache loads: both success and failures"); 161 162 final GaugeSnapshot.Builder cacheSize = 163 GaugeSnapshot.builder().name(METRIC_NAME_CACHE_ESTIMATED_SIZE).help("Estimated cache size"); 164 165 final SummarySnapshot.Builder cacheLoadSummary = 166 SummarySnapshot.builder() 167 .name(METRIC_NAME_CACHE_LOAD_DURATION_SECONDS) 168 .help("Cache load duration: both success and failures"); 169 170 for (final Map.Entry<String, Cache<?, ?>> c : children.entrySet()) { 171 final List<String> cacheName = Collections.singletonList(c.getKey()); 172 final Labels labels = Labels.of(labelNames, cacheName); 173 174 final CacheStats stats = c.getValue().stats(); 175 176 try { 177 cacheEvictionWeight.dataPoint( 178 GaugeSnapshot.GaugeDataPointSnapshot.builder() 179 .labels(labels) 180 .value(stats.evictionWeight()) 181 .build()); 182 } catch (Exception e) { 183 // EvictionWeight metric is unavailable, newer version of Caffeine is needed. 184 } 185 186 cacheHitTotal.dataPoint( 187 CounterSnapshot.CounterDataPointSnapshot.builder() 188 .labels(labels) 189 .value(stats.hitCount()) 190 .build()); 191 192 cacheMissTotal.dataPoint( 193 CounterSnapshot.CounterDataPointSnapshot.builder() 194 .labels(labels) 195 .value(stats.missCount()) 196 .build()); 197 198 cacheRequestsTotal.dataPoint( 199 CounterSnapshot.CounterDataPointSnapshot.builder() 200 .labels(labels) 201 .value(stats.requestCount()) 202 .build()); 203 204 cacheEvictionTotal.dataPoint( 205 CounterSnapshot.CounterDataPointSnapshot.builder() 206 .labels(labels) 207 .value(stats.evictionCount()) 208 .build()); 209 210 cacheSize.dataPoint( 211 GaugeSnapshot.GaugeDataPointSnapshot.builder() 212 .labels(labels) 213 .value(c.getValue().estimatedSize()) 214 .build()); 215 216 if (c.getValue() instanceof LoadingCache) { 217 cacheLoadFailure.dataPoint( 218 CounterSnapshot.CounterDataPointSnapshot.builder() 219 .labels(labels) 220 .value(stats.loadFailureCount()) 221 .build()); 222 223 cacheLoadTotal.dataPoint( 224 CounterSnapshot.CounterDataPointSnapshot.builder() 225 .labels(labels) 226 .value(stats.loadCount()) 227 .build()); 228 229 cacheLoadSummary.dataPoint( 230 SummarySnapshot.SummaryDataPointSnapshot.builder() 231 .labels(labels) 232 .count(stats.loadCount()) 233 .sum(stats.totalLoadTime() / NANOSECONDS_PER_SECOND) 234 .build()); 235 } 236 } 237 238 return metricSnapshotsBuilder 239 .metricSnapshot(cacheHitTotal.build()) 240 .metricSnapshot(cacheMissTotal.build()) 241 .metricSnapshot(cacheRequestsTotal.build()) 242 .metricSnapshot(cacheEvictionTotal.build()) 243 .metricSnapshot(cacheEvictionWeight.build()) 244 .metricSnapshot(cacheLoadFailure.build()) 245 .metricSnapshot(cacheLoadTotal.build()) 246 .metricSnapshot(cacheSize.build()) 247 .metricSnapshot(cacheLoadSummary.build()) 248 .build(); 249 } 250 251 @Override 252 public List<String> getPrometheusNames() { 253 return ALL_METRIC_NAMES; 254 } 255}