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}