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; 020import static java.lang.Math.max; 021 022import com.google.common.annotations.GwtCompatible; 023import com.google.common.base.MoreObjects; 024import com.google.common.base.Objects; 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 >= 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 max(0, saturatedSubtract(hitCount, other.hitCount)), 247 max(0, saturatedSubtract(missCount, other.missCount)), 248 max(0, saturatedSubtract(loadSuccessCount, other.loadSuccessCount)), 249 max(0, saturatedSubtract(loadExceptionCount, other.loadExceptionCount)), 250 max(0, saturatedSubtract(totalLoadTime, other.totalLoadTime)), 251 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 public CacheStats plus(CacheStats other) { 265 return new CacheStats( 266 saturatedAdd(hitCount, other.hitCount), 267 saturatedAdd(missCount, other.missCount), 268 saturatedAdd(loadSuccessCount, other.loadSuccessCount), 269 saturatedAdd(loadExceptionCount, other.loadExceptionCount), 270 saturatedAdd(totalLoadTime, other.totalLoadTime), 271 saturatedAdd(evictionCount, other.evictionCount)); 272 } 273 274 @Override 275 public int hashCode() { 276 return Objects.hashCode( 277 hitCount, missCount, loadSuccessCount, loadExceptionCount, totalLoadTime, evictionCount); 278 } 279 280 @Override 281 public boolean equals(@CheckForNull Object object) { 282 if (object instanceof CacheStats) { 283 CacheStats other = (CacheStats) object; 284 return hitCount == other.hitCount 285 && missCount == other.missCount 286 && loadSuccessCount == other.loadSuccessCount 287 && loadExceptionCount == other.loadExceptionCount 288 && totalLoadTime == other.totalLoadTime 289 && evictionCount == other.evictionCount; 290 } 291 return false; 292 } 293 294 @Override 295 public String toString() { 296 return MoreObjects.toStringHelper(this) 297 .add("hitCount", hitCount) 298 .add("missCount", missCount) 299 .add("loadSuccessCount", loadSuccessCount) 300 .add("loadExceptionCount", loadExceptionCount) 301 .add("totalLoadTime", totalLoadTime) 302 .add("evictionCount", evictionCount) 303 .toString(); 304 } 305}