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