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; 019 020/** 021 * Collect metrics from Guava's com.google.common.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 = CacheBuilder.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 * guava_cache_hit_total{cache="mycache"} 10.0 040 * guava_cache_miss_total{cache="mycache"} 3.0 041 * guava_cache_requests_total{cache="mycache"} 13.0 042 * guava_cache_eviction_total{cache="mycache"} 1.0 043 * guava_cache_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 * guava_cache_load_failure_total{cache="mycache"} 2.0 050 * guava_cache_loads_total{cache="mycache"} 7.0 051 * guava_cache_load_duration_seconds_count{cache="mycache"} 7.0 052 * guava_cache_load_duration_seconds_sum{cache="mycache"} 0.0034 053 * </pre> 054 */ 055@StableApi 056public class CacheMetricsCollector implements MultiCollector { 057 058 private static final double NANOSECONDS_PER_SECOND = 1_000_000_000.0; 059 060 private static final String METRIC_NAME_CACHE_HIT = "guava_cache_hit"; 061 private static final String METRIC_NAME_CACHE_MISS = "guava_cache_miss"; 062 private static final String METRIC_NAME_CACHE_REQUESTS = "guava_cache_requests"; 063 private static final String METRIC_NAME_CACHE_EVICTION = "guava_cache_eviction"; 064 private static final String METRIC_NAME_CACHE_LOAD_FAILURE = "guava_cache_load_failure"; 065 private static final String METRIC_NAME_CACHE_LOADS = "guava_cache_loads"; 066 private static final String METRIC_NAME_CACHE_SIZE = "guava_cache_size"; 067 private static final String METRIC_NAME_CACHE_LOAD_DURATION_SECONDS = 068 "guava_cache_load_duration_seconds"; 069 070 private static final List<String> ALL_METRIC_NAMES = 071 Collections.unmodifiableList( 072 Arrays.asList( 073 METRIC_NAME_CACHE_HIT, 074 METRIC_NAME_CACHE_MISS, 075 METRIC_NAME_CACHE_REQUESTS, 076 METRIC_NAME_CACHE_EVICTION, 077 METRIC_NAME_CACHE_LOAD_FAILURE, 078 METRIC_NAME_CACHE_LOADS, 079 METRIC_NAME_CACHE_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 * Remove the cache with the given name. 098 * 099 * <p>Any references to the cache are invalidated. 100 * 101 * @param cacheName cache to be removed 102 */ 103 public Cache<?, ?> removeCache(String cacheName) { 104 return children.remove(cacheName); 105 } 106 107 /** 108 * Remove all caches. 109 * 110 * <p>Any references to all caches are invalidated. 111 */ 112 public void clear() { 113 children.clear(); 114 } 115 116 @Override 117 public MetricSnapshots collect() { 118 final MetricSnapshots.Builder metricSnapshotsBuilder = MetricSnapshots.builder(); 119 final List<String> labelNames = Collections.singletonList("cache"); 120 121 final CounterSnapshot.Builder cacheHitTotal = 122 CounterSnapshot.builder().name(METRIC_NAME_CACHE_HIT).help("Cache hit totals"); 123 124 final CounterSnapshot.Builder cacheMissTotal = 125 CounterSnapshot.builder().name(METRIC_NAME_CACHE_MISS).help("Cache miss totals"); 126 127 final CounterSnapshot.Builder cacheRequestsTotal = 128 CounterSnapshot.builder().name(METRIC_NAME_CACHE_REQUESTS).help("Cache request totals"); 129 130 final CounterSnapshot.Builder cacheEvictionTotal = 131 CounterSnapshot.builder() 132 .name(METRIC_NAME_CACHE_EVICTION) 133 .help("Cache eviction totals, doesn't include manually removed entries"); 134 135 final CounterSnapshot.Builder cacheLoadFailure = 136 CounterSnapshot.builder().name(METRIC_NAME_CACHE_LOAD_FAILURE).help("Cache load failures"); 137 138 final CounterSnapshot.Builder cacheLoadTotal = 139 CounterSnapshot.builder() 140 .name(METRIC_NAME_CACHE_LOADS) 141 .help("Cache loads: both success and failures"); 142 143 final GaugeSnapshot.Builder cacheSize = 144 GaugeSnapshot.builder().name(METRIC_NAME_CACHE_SIZE).help("Cache size"); 145 146 final SummarySnapshot.Builder cacheLoadSummary = 147 SummarySnapshot.builder() 148 .name(METRIC_NAME_CACHE_LOAD_DURATION_SECONDS) 149 .help("Cache load duration: both success and failures"); 150 151 for (final Map.Entry<String, Cache<?, ?>> c : children.entrySet()) { 152 final List<String> cacheName = Collections.singletonList(c.getKey()); 153 final Labels labels = Labels.of(labelNames, cacheName); 154 155 final CacheStats stats = c.getValue().stats(); 156 157 cacheHitTotal.dataPoint( 158 CounterSnapshot.CounterDataPointSnapshot.builder() 159 .labels(labels) 160 .value(stats.hitCount()) 161 .build()); 162 163 cacheMissTotal.dataPoint( 164 CounterSnapshot.CounterDataPointSnapshot.builder() 165 .labels(labels) 166 .value(stats.missCount()) 167 .build()); 168 169 cacheRequestsTotal.dataPoint( 170 CounterSnapshot.CounterDataPointSnapshot.builder() 171 .labels(labels) 172 .value(stats.requestCount()) 173 .build()); 174 175 cacheEvictionTotal.dataPoint( 176 CounterSnapshot.CounterDataPointSnapshot.builder() 177 .labels(labels) 178 .value(stats.evictionCount()) 179 .build()); 180 181 cacheSize.dataPoint( 182 GaugeSnapshot.GaugeDataPointSnapshot.builder() 183 .labels(labels) 184 .value(c.getValue().size()) 185 .build()); 186 187 if (c.getValue() instanceof LoadingCache) { 188 cacheLoadFailure.dataPoint( 189 CounterSnapshot.CounterDataPointSnapshot.builder() 190 .labels(labels) 191 .value(stats.loadExceptionCount()) 192 .build()); 193 194 cacheLoadTotal.dataPoint( 195 CounterSnapshot.CounterDataPointSnapshot.builder() 196 .labels(labels) 197 .value(stats.loadCount()) 198 .build()); 199 200 cacheLoadSummary.dataPoint( 201 SummarySnapshot.SummaryDataPointSnapshot.builder() 202 .labels(labels) 203 .count(stats.loadCount()) 204 .sum(stats.totalLoadTime() / NANOSECONDS_PER_SECOND) 205 .build()); 206 } 207 } 208 209 metricSnapshotsBuilder.metricSnapshot(cacheHitTotal.build()); 210 metricSnapshotsBuilder.metricSnapshot(cacheMissTotal.build()); 211 metricSnapshotsBuilder.metricSnapshot(cacheRequestsTotal.build()); 212 metricSnapshotsBuilder.metricSnapshot(cacheEvictionTotal.build()); 213 metricSnapshotsBuilder.metricSnapshot(cacheLoadFailure.build()); 214 metricSnapshotsBuilder.metricSnapshot(cacheLoadTotal.build()); 215 metricSnapshotsBuilder.metricSnapshot(cacheSize.build()); 216 metricSnapshotsBuilder.metricSnapshot(cacheLoadSummary.build()); 217 218 return metricSnapshotsBuilder.build(); 219 } 220 221 /** 222 * @deprecated Use {@link #getMetricFamilyDescriptors()} instead. 223 */ 224 @Override 225 @Deprecated 226 @SuppressWarnings("InlineMeSuggester") 227 public List<String> getPrometheusNames() { 228 return ALL_METRIC_NAMES; 229 } 230}