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