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