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