1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.cache;
18
19import static com.google.common.base.Preconditions.checkArgument;
20
21import com.google.common.annotations.Beta;
22import com.google.common.annotations.GwtCompatible;
23import com.google.common.base.Objects;
24
25import javax.annotation.Nullable;
26
27/**
28 * Statistics about the performance of a {@link Cache}. Instances of this class are immutable.
29 *
30 * <p>Cache statistics are incremented according to the following rules:
31 *
32 * <ul>
33 * <li>When a cache lookup encounters an existing cache entry {@code hitCount} is incremented.
34 * <li>When a cache lookup first encounters a missing cache entry, a new entry is loaded.
35 * <ul>
36 * <li>After successfully loading an entry {@code missCount} and {@code loadSuccessCount} are
37 *     incremented, and the total loading time, in nanoseconds, is added to
38 *     {@code totalLoadTime}.
39 * <li>When an exception is thrown while loading an entry, {@code missCount} and {@code
40 *     loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is
41 *     added to {@code totalLoadTime}.
42 * <li>Cache lookups that encounter a missing cache entry that is still loading will wait
43 *     for loading to complete (whether successful or not) and then increment {@code missCount}.
44 * </ul>
45 * <li>When an entry is evicted from the cache, {@code evictionCount} is incremented.
46 * <li>No stats are modified when a cache entry is invalidated or manually removed.
47 * <li>No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of
48 *     the cache.
49 * </ul>
50 *
51 * @author Charles Fry
52 * @since 10.0
53 */
54@Beta
55@GwtCompatible
56public final class CacheStats {
57  private final long hitCount;
58  private final long missCount;
59  private final long loadSuccessCount;
60  private final long loadExceptionCount;
61  private final long totalLoadTime;
62  private final long evictionCount;
63
64  /**
65   * Constructs a new {@code CacheStats} instance.
66   *
67   * <p>Five parameters of the same type in a row is a bad thing, but this class is not constructed
68   * by end users and is too fine-grained for a builder.
69   */
70  public CacheStats(long hitCount, long missCount, long loadSuccessCount,
71      long loadExceptionCount, long totalLoadTime, long evictionCount) {
72    checkArgument(hitCount >= 0);
73    checkArgument(missCount >= 0);
74    checkArgument(loadSuccessCount >= 0);
75    checkArgument(loadExceptionCount >= 0);
76    checkArgument(totalLoadTime >= 0);
77    checkArgument(evictionCount >= 0);
78
79    this.hitCount = hitCount;
80    this.missCount = missCount;
81    this.loadSuccessCount = loadSuccessCount;
82    this.loadExceptionCount = loadExceptionCount;
83    this.totalLoadTime = totalLoadTime;
84    this.evictionCount = evictionCount;
85  }
86
87  /**
88   * Returns the number of times {@link Cache} lookup methods have returned either a cached or
89   * uncached value. This is defined as {@code hitCount + missCount}.
90   */
91  public long requestCount() {
92    return hitCount + missCount;
93  }
94
95  /**
96   * Returns the number of times {@link Cache} lookup methods have returned a cached value.
97   */
98  public long hitCount() {
99    return hitCount;
100  }
101
102  /**
103   * Returns the ratio of cache requests which were hits. This is defined as
104   * {@code hitCount / requestCount}, or {@code 1.0} when {@code requestCount == 0}.
105   * Note that {@code hitRate + missRate =~ 1.0}.
106   */
107  public double hitRate() {
108    long requestCount = requestCount();
109    return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount;
110  }
111
112  /**
113   * Returns the number of times {@link Cache} lookup methods have returned an uncached (newly
114   * loaded) value, or null. Multiple concurrent calls to {@link Cache} lookup methods on an absent
115   * value can result in multiple misses, all returning the results of a single cache load
116   * operation.
117   */
118  public long missCount() {
119    return missCount;
120  }
121
122  /**
123   * Returns the ratio of cache requests which were misses. This is defined as
124   * {@code missCount / requestCount}, or {@code 0.0} when {@code requestCount == 0}.
125   * Note that {@code hitRate + missRate =~ 1.0}. Cache misses include all requests which
126   * weren't cache hits, including requests which resulted in either successful or failed loading
127   * attempts, and requests which waited for other threads to finish loading. It is thus the case
128   * that {@code missCount &gt;= loadSuccessCount + loadExceptionCount}. Multiple
129   * concurrent misses for the same key will result in a single load operation.
130   */
131  public double missRate() {
132    long requestCount = requestCount();
133    return (requestCount == 0) ? 0.0 : (double) missCount / requestCount;
134  }
135
136  /**
137   * Returns the total number of times that {@link Cache} lookup methods attempted to load new
138   * values. This includes both successful load operations, as well as those that threw
139   * exceptions. This is defined as {@code loadSuccessCount + loadExceptionCount}.
140   */
141  public long loadCount() {
142    return loadSuccessCount + loadExceptionCount;
143  }
144
145  /**
146   * Returns the number of times {@link Cache} lookup methods have successfully loaded a new value.
147   * This is always incremented in conjunction with {@link #missCount}, though {@code missCount}
148   * is also incremented when an exception is encountered during cache loading (see
149   * {@link #loadExceptionCount}). Multiple concurrent misses for the same key will result in a
150   * single load operation.
151   */
152  public long loadSuccessCount() {
153    return loadSuccessCount;
154  }
155
156  /**
157   * Returns the number of times {@link Cache} lookup methods threw an exception while loading a
158   * new value. This is always incremented in conjunction with {@code missCount}, though
159   * {@code missCount} is also incremented when cache loading completes successfully (see
160   * {@link #loadSuccessCount}). Multiple concurrent misses for the same key will result in a
161   * single load operation.
162   */
163  public long loadExceptionCount() {
164    return loadExceptionCount;
165  }
166
167  /**
168   * Returns the ratio of cache loading attempts which threw exceptions. This is defined as
169   * {@code loadExceptionCount / (loadSuccessCount + loadExceptionCount)}, or
170   * {@code 0.0} when {@code loadSuccessCount + loadExceptionCount == 0}.
171   */
172  public double loadExceptionRate() {
173    long totalLoadCount = loadSuccessCount + loadExceptionCount;
174    return (totalLoadCount == 0)
175        ? 0.0
176        : (double) loadExceptionCount / totalLoadCount;
177  }
178
179  /**
180   * Returns the total number of nanoseconds the cache has spent loading new values. This can be
181   * used to calculate the miss penalty. This value is increased every time
182   * {@code loadSuccessCount} or {@code loadExceptionCount} is incremented.
183   */
184  public long totalLoadTime() {
185    return totalLoadTime;
186  }
187
188  /**
189   * Returns the average time spent loading new values. This is defined as
190   * {@code totalLoadTime / (loadSuccessCount + loadExceptionCount)}.
191   */
192  public double averageLoadPenalty() {
193    long totalLoadCount = loadSuccessCount + loadExceptionCount;
194    return (totalLoadCount == 0)
195        ? 0.0
196        : (double) totalLoadTime / totalLoadCount;
197  }
198
199  /**
200   * Returns the number of times an entry has been evicted. This count does not include manual
201   * {@linkplain Cache#invalidate invalidations}.
202   */
203  public long evictionCount() {
204    return evictionCount;
205  }
206
207  /**
208   * Returns a new {@code CacheStats} representing the difference between this {@code CacheStats}
209   * and {@code other}. Negative values, which aren't supported by {@code CacheStats} will be
210   * rounded up to zero.
211   */
212  public CacheStats minus(CacheStats other) {
213    return new CacheStats(
214        Math.max(0, hitCount - other.hitCount),
215        Math.max(0, missCount - other.missCount),
216        Math.max(0, loadSuccessCount - other.loadSuccessCount),
217        Math.max(0, loadExceptionCount - other.loadExceptionCount),
218        Math.max(0, totalLoadTime - other.totalLoadTime),
219        Math.max(0, evictionCount - other.evictionCount));
220  }
221
222  /**
223   * Returns a new {@code CacheStats} representing the sum of this {@code CacheStats}
224   * and {@code other}.
225   *
226   * @since 11.0
227   */
228  public CacheStats plus(CacheStats other) {
229    return new CacheStats(
230        hitCount + other.hitCount,
231        missCount + other.missCount,
232        loadSuccessCount + other.loadSuccessCount,
233        loadExceptionCount + other.loadExceptionCount,
234        totalLoadTime + other.totalLoadTime,
235        evictionCount + other.evictionCount);
236  }
237
238  @Override
239  public int hashCode() {
240    return Objects.hashCode(hitCount, missCount, loadSuccessCount, loadExceptionCount,
241        totalLoadTime, evictionCount);
242  }
243
244  @Override
245  public boolean equals(@Nullable Object object) {
246    if (object instanceof CacheStats) {
247      CacheStats other = (CacheStats) object;
248      return hitCount == other.hitCount
249          && missCount == other.missCount
250          && loadSuccessCount == other.loadSuccessCount
251          && loadExceptionCount == other.loadExceptionCount
252          && totalLoadTime == other.totalLoadTime
253          && evictionCount == other.evictionCount;
254    }
255    return false;
256  }
257
258  @Override
259  public String toString() {
260    return Objects.toStringHelper(this)
261        .add("hitCount", hitCount)
262        .add("missCount", missCount)
263        .add("loadSuccessCount", loadSuccessCount)
264        .add("loadExceptionCount", loadExceptionCount)
265        .add("totalLoadTime", totalLoadTime)
266        .add("evictionCount", evictionCount)
267        .toString();
268  }
269}
270