001/*
002 * Copyright (C) 2011 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.cache;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.math.LongMath.saturatedAdd;
019import static com.google.common.math.LongMath.saturatedSubtract;
020
021import com.google.common.annotations.GwtCompatible;
022import com.google.common.base.MoreObjects;
023import com.google.common.base.Objects;
024import com.google.errorprone.annotations.CheckReturnValue;
025import java.util.concurrent.Callable;
026import javax.annotation.CheckForNull;
027
028/**
029 * Statistics about the performance of a {@link Cache}. Instances of this class are immutable.
030 *
031 * <p>Cache statistics are incremented according to the following rules:
032 *
033 * <ul>
034 *   <li>When a cache lookup encounters an existing cache entry {@code hitCount} is incremented.
035 *   <li>When a cache lookup first encounters a missing cache entry, a new entry is loaded.
036 *       <ul>
037 *         <li>After successfully loading an entry {@code missCount} and {@code loadSuccessCount}
038 *             are incremented, and the total loading time, in nanoseconds, is added to {@code
039 *             totalLoadTime}.
040 *         <li>When an exception is thrown while loading an entry, {@code missCount} and {@code
041 *             loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is
042 *             added to {@code totalLoadTime}.
043 *         <li>Cache lookups that encounter a missing cache entry that is still loading will wait
044 *             for loading to complete (whether successful or not) and then increment {@code
045 *             missCount}.
046 *       </ul>
047 *   <li>When an entry is evicted from the cache, {@code evictionCount} is incremented.
048 *   <li>No stats are modified when a cache entry is invalidated or manually removed.
049 *   <li>No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of
050 *       the cache.
051 * </ul>
052 *
053 * <p>A lookup is specifically defined as an invocation of one of the methods {@link
054 * LoadingCache#get(Object)}, {@link LoadingCache#getUnchecked(Object)}, {@link Cache#get(Object,
055 * Callable)}, or {@link LoadingCache#getAll(Iterable)}.
056 *
057 * @author Charles Fry
058 * @since 10.0
059 */
060@GwtCompatible
061@ElementTypesAreNonnullByDefault
062public final class CacheStats {
063  private final long hitCount;
064  private final long missCount;
065  private final long loadSuccessCount;
066  private final long loadExceptionCount;
067
068  @SuppressWarnings("GoodTime") // should be a java.time.Duration
069  private final long totalLoadTime;
070
071  private final long evictionCount;
072
073  /**
074   * Constructs a new {@code CacheStats} instance.
075   *
076   * <p>Five parameters of the same type in a row is a bad thing, but this class is not constructed
077   * by end users and is too fine-grained for a builder.
078   */
079  @SuppressWarnings("GoodTime") // should accept a java.time.Duration
080  public CacheStats(
081      long hitCount,
082      long missCount,
083      long loadSuccessCount,
084      long loadExceptionCount,
085      long totalLoadTime,
086      long evictionCount) {
087    checkArgument(hitCount >= 0);
088    checkArgument(missCount >= 0);
089    checkArgument(loadSuccessCount >= 0);
090    checkArgument(loadExceptionCount >= 0);
091    checkArgument(totalLoadTime >= 0);
092    checkArgument(evictionCount >= 0);
093
094    this.hitCount = hitCount;
095    this.missCount = missCount;
096    this.loadSuccessCount = loadSuccessCount;
097    this.loadExceptionCount = loadExceptionCount;
098    this.totalLoadTime = totalLoadTime;
099    this.evictionCount = evictionCount;
100  }
101
102  /**
103   * Returns the number of times {@link Cache} lookup methods have returned either a cached or
104   * uncached value. This is defined as {@code hitCount + missCount}.
105   *
106   * <p><b>Note:</b> the values of the metrics are undefined in case of overflow (though it is
107   * guaranteed not to throw an exception). If you require specific handling, we recommend
108   * implementing your own stats collector.
109   */
110  public long requestCount() {
111    return saturatedAdd(hitCount, missCount);
112  }
113
114  /** Returns the number of times {@link Cache} lookup methods have returned a cached value. */
115  public long hitCount() {
116    return hitCount;
117  }
118
119  /**
120   * Returns the ratio of cache requests which were hits. This is defined as {@code hitCount /
121   * requestCount}, or {@code 1.0} when {@code requestCount == 0}. Note that {@code hitRate +
122   * missRate =~ 1.0}.
123   */
124  public double hitRate() {
125    long requestCount = requestCount();
126    return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount;
127  }
128
129  /**
130   * Returns the number of times {@link Cache} lookup methods have returned an uncached (newly
131   * loaded) value, or null. Multiple concurrent calls to {@link Cache} lookup methods on an absent
132   * value can result in multiple misses, all returning the results of a single cache load
133   * operation.
134   */
135  public long missCount() {
136    return missCount;
137  }
138
139  /**
140   * Returns the ratio of cache requests which were misses. This is defined as {@code missCount /
141   * requestCount}, or {@code 0.0} when {@code requestCount == 0}. Note that {@code hitRate +
142   * missRate =~ 1.0}. Cache misses include all requests which weren't cache hits, including
143   * requests which resulted in either successful or failed loading attempts, and requests which
144   * waited for other threads to finish loading. It is thus the case that {@code missCount &gt;=
145   * loadSuccessCount + loadExceptionCount}. Multiple concurrent misses for the same key will result
146   * in a single load operation.
147   */
148  public double missRate() {
149    long requestCount = requestCount();
150    return (requestCount == 0) ? 0.0 : (double) missCount / requestCount;
151  }
152
153  /**
154   * Returns the total number of times that {@link Cache} lookup methods attempted to load new
155   * values. This includes both successful load operations and those that threw exceptions. This is
156   * defined as {@code loadSuccessCount + loadExceptionCount}.
157   *
158   * <p><b>Note:</b> the values of the metrics are undefined in case of overflow (though it is
159   * guaranteed not to throw an exception). If you require specific handling, we recommend
160   * implementing your own stats collector.
161   */
162  public long loadCount() {
163    return saturatedAdd(loadSuccessCount, loadExceptionCount);
164  }
165
166  /**
167   * Returns the number of times {@link Cache} lookup methods have successfully loaded a new value.
168   * This is usually incremented in conjunction with {@link #missCount}, though {@code missCount} is
169   * also incremented when an exception is encountered during cache loading (see {@link
170   * #loadExceptionCount}). Multiple concurrent misses for the same key will result in a single load
171   * operation. This may be incremented not in conjunction with {@code missCount} if the load occurs
172   * as a result of a refresh or if the cache loader returned more items than was requested. {@code
173   * missCount} may also be incremented not in conjunction with this (nor {@link
174   * #loadExceptionCount}) on calls to {@code getIfPresent}.
175   */
176  public long loadSuccessCount() {
177    return loadSuccessCount;
178  }
179
180  /**
181   * Returns the number of times {@link Cache} lookup methods threw an exception while loading a new
182   * value. This is usually incremented in conjunction with {@code missCount}, though {@code
183   * missCount} is also incremented when cache loading completes successfully (see {@link
184   * #loadSuccessCount}). Multiple concurrent misses for the same key will result in a single load
185   * operation. This may be incremented not in conjunction with {@code missCount} if the load occurs
186   * as a result of a refresh or if the cache loader returned more items than was requested. {@code
187   * missCount} may also be incremented not in conjunction with this (nor {@link #loadSuccessCount})
188   * on calls to {@code getIfPresent}.
189   */
190  public long loadExceptionCount() {
191    return loadExceptionCount;
192  }
193
194  /**
195   * Returns the ratio of cache loading attempts which threw exceptions. This is defined as {@code
196   * loadExceptionCount / (loadSuccessCount + loadExceptionCount)}, or {@code 0.0} when {@code
197   * loadSuccessCount + loadExceptionCount == 0}.
198   *
199   * <p><b>Note:</b> the values of the metrics are undefined in case of overflow (though it is
200   * guaranteed not to throw an exception). If you require specific handling, we recommend
201   * implementing your own stats collector.
202   */
203  public double loadExceptionRate() {
204    long totalLoadCount = saturatedAdd(loadSuccessCount, loadExceptionCount);
205    return (totalLoadCount == 0) ? 0.0 : (double) loadExceptionCount / totalLoadCount;
206  }
207
208  /**
209   * Returns the total number of nanoseconds the cache has spent loading new values. This can be
210   * used to calculate the miss penalty. This value is increased every time {@code loadSuccessCount}
211   * or {@code loadExceptionCount} is incremented.
212   */
213  @SuppressWarnings("GoodTime") // should return a java.time.Duration
214  public long totalLoadTime() {
215    return totalLoadTime;
216  }
217
218  /**
219   * Returns the average time spent loading new values. This is defined as {@code totalLoadTime /
220   * (loadSuccessCount + loadExceptionCount)}.
221   *
222   * <p><b>Note:</b> the values of the metrics are undefined in case of overflow (though it is
223   * guaranteed not to throw an exception). If you require specific handling, we recommend
224   * implementing your own stats collector.
225   */
226  public double averageLoadPenalty() {
227    long totalLoadCount = saturatedAdd(loadSuccessCount, loadExceptionCount);
228    return (totalLoadCount == 0) ? 0.0 : (double) totalLoadTime / totalLoadCount;
229  }
230
231  /**
232   * Returns the number of times an entry has been evicted. This count does not include manual
233   * {@linkplain Cache#invalidate invalidations}.
234   */
235  public long evictionCount() {
236    return evictionCount;
237  }
238
239  /**
240   * Returns a new {@code CacheStats} representing the difference between this {@code CacheStats}
241   * and {@code other}. Negative values, which aren't supported by {@code CacheStats} will be
242   * rounded up to zero.
243   */
244  public CacheStats minus(CacheStats other) {
245    return new CacheStats(
246        Math.max(0, saturatedSubtract(hitCount, other.hitCount)),
247        Math.max(0, saturatedSubtract(missCount, other.missCount)),
248        Math.max(0, saturatedSubtract(loadSuccessCount, other.loadSuccessCount)),
249        Math.max(0, saturatedSubtract(loadExceptionCount, other.loadExceptionCount)),
250        Math.max(0, saturatedSubtract(totalLoadTime, other.totalLoadTime)),
251        Math.max(0, saturatedSubtract(evictionCount, other.evictionCount)));
252  }
253
254  /**
255   * Returns a new {@code CacheStats} representing the sum of this {@code CacheStats} and {@code
256   * other}.
257   *
258   * <p><b>Note:</b> the values of the metrics are undefined in case of overflow (though it is
259   * guaranteed not to throw an exception). If you require specific handling, we recommend
260   * implementing your own stats collector.
261   *
262   * @since 11.0
263   */
264  @CheckReturnValue
265  public CacheStats plus(CacheStats other) {
266    return new CacheStats(
267        saturatedAdd(hitCount, other.hitCount),
268        saturatedAdd(missCount, other.missCount),
269        saturatedAdd(loadSuccessCount, other.loadSuccessCount),
270        saturatedAdd(loadExceptionCount, other.loadExceptionCount),
271        saturatedAdd(totalLoadTime, other.totalLoadTime),
272        saturatedAdd(evictionCount, other.evictionCount));
273  }
274
275  @Override
276  public int hashCode() {
277    return Objects.hashCode(
278        hitCount, missCount, loadSuccessCount, loadExceptionCount, totalLoadTime, evictionCount);
279  }
280
281  @Override
282  public boolean equals(@CheckForNull Object object) {
283    if (object instanceof CacheStats) {
284      CacheStats other = (CacheStats) object;
285      return hitCount == other.hitCount
286          && missCount == other.missCount
287          && loadSuccessCount == other.loadSuccessCount
288          && loadExceptionCount == other.loadExceptionCount
289          && totalLoadTime == other.totalLoadTime
290          && evictionCount == other.evictionCount;
291    }
292    return false;
293  }
294
295  @Override
296  public String toString() {
297    return MoreObjects.toStringHelper(this)
298        .add("hitCount", hitCount)
299        .add("missCount", missCount)
300        .add("loadSuccessCount", loadSuccessCount)
301        .add("loadExceptionCount", loadExceptionCount)
302        .add("totalLoadTime", totalLoadTime)
303        .add("evictionCount", evictionCount)
304        .toString();
305  }
306}