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