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