    001    /*
    002     * Copyright (C) 2011 The Guava Authors
    003     *
    004     * Licensed under the Apache License, Version 2.0 (the "License");
    005     * you may not use this file except in compliance with the License.
    006     * You may obtain a copy of the License at
    007     *
    008     * http://www.apache.org/licenses/LICENSE-2.0
    009     *
    010     * Unless required by applicable law or agreed to in writing, software
    011     * distributed under the License is distributed on an "AS IS" BASIS,
    012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    013     * See the License for the specific language governing permissions and
    014     * limitations under the License.
    015     */
    016
    017    package com.google.common.cache;
    018
    019    import static com.google.common.base.Preconditions.checkArgument;
    020
    021    import com.google.common.annotations.Beta;
    022    import com.google.common.annotations.GwtCompatible;
    023    import com.google.common.base.Objects;
    024
    025    import javax.annotation.Nullable;
    026
    027    /**
    028     * Statistics about the performance of a {@link Cache}. Instances of this class are immutable.
    029     *
    030     * <p>Cache statistics are incremented according to the following rules:
    031     *
    032     * <ul>
    033     * <li>When a cache lookup encounters an existing cache entry {@code hitCount} is incremented.
    034     * <li>When a cache lookup first encounters a missing cache entry, a new entry is loaded.
    035     * <ul>
    036     * <li>After successfully loading an entry {@code missCount} and {@code loadSuccessCount} are
    037     *     incremented, and the total loading time, in nanoseconds, is added to
    038     *     {@code totalLoadTime}.
    039     * <li>When an exception is thrown while loading an entry, {@code missCount} and {@code
    040     *     loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is
    041     *     added to {@code totalLoadTime}.
    042     * <li>Cache lookups that encounter a missing cache entry that is still loading will wait
    043     *     for loading to complete (whether successful or not) and then increment {@code missCount}.
    044     * </ul>
    045     * <li>When an entry is evicted from the cache, {@code evictionCount} is incremented.
    046     * <li>No stats are modified when a cache entry is invalidated or manually removed.
    047     * <li>No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of
    048     *     the cache.
    049     * </ul>
    050     *
    051     * @author Charles Fry
    052     * @since 10.0
    053     */
    054    @Beta
    055    @GwtCompatible
    056    public final class CacheStats {
    057      private final long hitCount;
    058      private final long missCount;
    059      private final long loadSuccessCount;
    060      private final long loadExceptionCount;
    061      private final long totalLoadTime;
    062      private final long evictionCount;
    063
    064      /**
    065       * Constructs a new {@code CacheStats} instance.
    066       *
    067       * <p>Five parameters of the same type in a row is a bad thing, but this class is not constructed
    068       * by end users and is too fine-grained for a builder.
    069       */
    070      public CacheStats(long hitCount, long missCount, long loadSuccessCount,
    071          long loadExceptionCount, long totalLoadTime, long evictionCount) {
    072        checkArgument(hitCount >= 0);
    073        checkArgument(missCount >= 0);
    074        checkArgument(loadSuccessCount >= 0);
    075        checkArgument(loadExceptionCount >= 0);
    076        checkArgument(totalLoadTime >= 0);
    077        checkArgument(evictionCount >= 0);
    078
    079        this.hitCount = hitCount;
    080        this.missCount = missCount;
    081        this.loadSuccessCount = loadSuccessCount;
    082        this.loadExceptionCount = loadExceptionCount;
    083        this.totalLoadTime = totalLoadTime;
    084        this.evictionCount = evictionCount;
    085      }
    086
    087      /**
    088       * Returns the number of times {@link Cache} lookup methods have returned either a cached or
    089       * uncached value. This is defined as {@code hitCount + missCount}.
    090       */
    091      public long requestCount() {
    092        return hitCount + missCount;
    093      }
    094
    095      /**
    096       * Returns the number of times {@link Cache} lookup methods have returned a cached value.
    097       */
    098      public long hitCount() {
    099        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    }