001package io.prometheus.client.cache.caffeine; 002 003import com.github.benmanes.caffeine.cache.AsyncLoadingCache; 004import com.github.benmanes.caffeine.cache.Cache; 005import com.github.benmanes.caffeine.cache.LoadingCache; 006import com.github.benmanes.caffeine.cache.stats.CacheStats; 007import io.prometheus.client.Collector; 008import io.prometheus.client.CounterMetricFamily; 009import io.prometheus.client.GaugeMetricFamily; 010import io.prometheus.client.SummaryMetricFamily; 011 012import java.util.ArrayList; 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/** 021 * Collect metrics from Caffiene's com.github.benmanes.caffeine.cache.Cache. 022 * <p> 023 * <pre>{@code 024 * 025 * // Note that `recordStats()` is required to gather non-zero statistics 026 * Cache<String, String> cache = Caffeine.newBuilder().recordStats().build(); 027 * CacheMetricsCollector cacheMetrics = new CacheMetricsCollector().register(); 028 * cacheMetrics.addCache("mycache", cache); 029 * 030 * }</pre> 031 * 032 * Exposed metrics are labeled with the provided cache name. 033 * 034 * With the example above, sample metric names would be: 035 * <pre> 036 * caffeine_cache_hit_total{cache="mycache"} 10.0 037 * caffeine_cache_miss_total{cache="mycache"} 3.0 038 * caffeine_cache_requests_total{cache="mycache"} 13.0 039 * caffeine_cache_eviction_total{cache="mycache"} 1.0 040 * caffeine_cache_estimated_size{cache="mycache"} 5.0 041 * </pre> 042 * 043 * Additionally if the cache includes a loader, the following metrics would be provided: 044 * <pre> 045 * caffeine_cache_load_failure_total{cache="mycache"} 2.0 046 * caffeine_cache_loads_total{cache="mycache"} 7.0 047 * caffeine_cache_load_duration_seconds_count{cache="mycache"} 7.0 048 * caffeine_cache_load_duration_seconds_sum{cache="mycache"} 0.0034 049 * </pre> 050 * 051 */ 052public class CacheMetricsCollector extends Collector { 053 protected final ConcurrentMap<String, Cache> children = new ConcurrentHashMap<String, Cache>(); 054 055 /** 056 * Add or replace the cache with the given name. 057 * <p> 058 * Any references any previous cache with this name is invalidated. 059 * 060 * @param cacheName The name of the cache, will be the metrics label value 061 * @param cache The cache being monitored 062 */ 063 public void addCache(String cacheName, Cache cache) { 064 children.put(cacheName, cache); 065 } 066 067 /** 068 * Add or replace the cache with the given name. 069 * <p> 070 * Any references any previous cache with this name is invalidated. 071 * 072 * @param cacheName The name of the cache, will be the metrics label value 073 * @param cache The cache being monitored 074 */ 075 public void addCache(String cacheName, AsyncLoadingCache cache) { 076 children.put(cacheName, cache.synchronous()); 077 } 078 079 /** 080 * Remove the cache with the given name. 081 * <p> 082 * Any references to the cache are invalidated. 083 * 084 * @param cacheName cache to be removed 085 */ 086 public Cache removeCache(String cacheName) { 087 return children.remove(cacheName); 088 } 089 090 /** 091 * Remove all caches. 092 * <p> 093 * Any references to all caches are invalidated. 094 */ 095 public void clear(){ 096 children.clear(); 097 } 098 099 @Override 100 public List<MetricFamilySamples> collect() { 101 List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>(); 102 List<String> labelNames = Arrays.asList("cache"); 103 104 CounterMetricFamily cacheHitTotal = new CounterMetricFamily("caffeine_cache_hit_total", 105 "Cache hit totals", labelNames); 106 mfs.add(cacheHitTotal); 107 108 CounterMetricFamily cacheMissTotal = new CounterMetricFamily("caffeine_cache_miss_total", 109 "Cache miss totals", labelNames); 110 mfs.add(cacheMissTotal); 111 112 CounterMetricFamily cacheRequestsTotal = new CounterMetricFamily("caffeine_cache_requests_total", 113 "Cache request totals, hits + misses", labelNames); 114 mfs.add(cacheRequestsTotal); 115 116 CounterMetricFamily cacheEvictionTotal = new CounterMetricFamily("caffeine_cache_eviction_total", 117 "Cache eviction totals, doesn't include manually removed entries", labelNames); 118 mfs.add(cacheEvictionTotal); 119 120 GaugeMetricFamily cacheEvictionWeight = new GaugeMetricFamily("caffeine_cache_eviction_weight", 121 "Cache eviction weight", labelNames); 122 mfs.add(cacheEvictionWeight); 123 124 CounterMetricFamily cacheLoadFailure = new CounterMetricFamily("caffeine_cache_load_failure_total", 125 "Cache load failures", labelNames); 126 mfs.add(cacheLoadFailure); 127 128 CounterMetricFamily cacheLoadTotal = new CounterMetricFamily("caffeine_cache_loads_total", 129 "Cache loads: both success and failures", labelNames); 130 mfs.add(cacheLoadTotal); 131 132 GaugeMetricFamily cacheSize = new GaugeMetricFamily("caffeine_cache_estimated_size", 133 "Estimated cache size", labelNames); 134 mfs.add(cacheSize); 135 136 SummaryMetricFamily cacheLoadSummary = new SummaryMetricFamily("caffeine_cache_load_duration_seconds", 137 "Cache load duration: both success and failures", labelNames); 138 mfs.add(cacheLoadSummary); 139 140 for(Map.Entry<String, Cache> c: children.entrySet()) { 141 List<String> cacheName = Arrays.asList(c.getKey()); 142 CacheStats stats = c.getValue().stats(); 143 144 try{ 145 cacheEvictionWeight.addMetric(cacheName, stats.evictionWeight()); 146 } catch (Exception e) { 147 // EvictionWeight metric is unavailable, newer version of Caffeine is needed. 148 } 149 150 cacheHitTotal.addMetric(cacheName, stats.hitCount()); 151 cacheMissTotal.addMetric(cacheName, stats.missCount()); 152 cacheRequestsTotal.addMetric(cacheName, stats.requestCount()); 153 cacheEvictionTotal.addMetric(cacheName, stats.evictionCount()); 154 cacheSize.addMetric(cacheName, c.getValue().estimatedSize()); 155 156 if(c.getValue() instanceof LoadingCache) { 157 cacheLoadFailure.addMetric(cacheName, stats.loadFailureCount()); 158 cacheLoadTotal.addMetric(cacheName, stats.loadCount()); 159 160 cacheLoadSummary.addMetric(cacheName, stats.loadCount(), stats.totalLoadTime() / Collector.NANOSECONDS_PER_SECOND); 161 } 162 } 163 return mfs; 164 } 165}