Coverage Report - com.allanbank.mongodb.bson.io.AbstractStringCache
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractStringCache
96%
56/58
92%
26/28
2.538
AbstractStringCache$ReverseIntegerComparator
100%
10/10
100%
4/4
2.538
 
 1  
 /*
 2  
  * #%L
 3  
  * AbstractStringCache.java - mongodb-async-driver - Allanbank Consulting, Inc.
 4  
  * %%
 5  
  * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
 6  
  * %%
 7  
  * Licensed under the Apache License, Version 2.0 (the "License");
 8  
  * you may not use this file except in compliance with the License.
 9  
  * You may obtain a copy of the License at
 10  
  * 
 11  
  *      http://www.apache.org/licenses/LICENSE-2.0
 12  
  * 
 13  
  * Unless required by applicable law or agreed to in writing, software
 14  
  * distributed under the License is distributed on an "AS IS" BASIS,
 15  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16  
  * See the License for the specific language governing permissions and
 17  
  * limitations under the License.
 18  
  * #L%
 19  
  */
 20  
 
 21  
 package com.allanbank.mongodb.bson.io;
 22  
 
 23  
 import java.io.Serializable;
 24  
 import java.util.Comparator;
 25  
 import java.util.Iterator;
 26  
 import java.util.LinkedList;
 27  
 import java.util.List;
 28  
 import java.util.SortedMap;
 29  
 import java.util.TreeMap;
 30  
 import java.util.concurrent.ConcurrentHashMap;
 31  
 import java.util.concurrent.ConcurrentMap;
 32  
 import java.util.concurrent.atomic.AtomicInteger;
 33  
 
 34  
 /**
 35  
  * AbstractStringCache provides the basic functionality for tracking string
 36  
  * usage and triggering rebuilds of the cache over time.
 37  
  * <p>
 38  
  * The basic function of the cache is to maintain two structures. The first
 39  
  * (provided by this class) tracks the usage of strings in the cache via a
 40  
  * 'seen' {@link ConcurrentMap}. As the cache sees more usage it will
 41  
  * periodically trim entries from the map that are not being used often (this is
 42  
  * to limit memory usage). Once the cache thinks it has seen enough usage it
 43  
  * will trigger the building of the runtime cache. This runtime cache does not
 44  
  * require any locking as it is read-only after being constructed.
 45  
  * </p>
 46  
  * <p>
 47  
  * There are two controls on the amount of caching instances of this class will
 48  
  * do:
 49  
  * </p>
 50  
  * <ol>
 51  
  * <li>
 52  
  * {@link #getMaxCacheLength()} limits the length of the encoded bytes this
 53  
  * class will try and retrieve from the cache. Setting this value to zero
 54  
  * disables the cache and limit any memory overhead.</li>
 55  
  * <li>{@link #getMaxCacheEntries()} controls the number of cached entries in
 56  
  * the runtime-cache and how often the accumulated statistics are trimmed. Each
 57  
  * entry represents a single encoded string.</li>
 58  
  * </ol>
 59  
  * 
 60  
  * 
 61  
  * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
 62  
  *         mutated in incompatible ways between any two releases of the driver.
 63  
  * @copyright 2014, Allanbank Consulting, Inc., All Rights Reserved
 64  
  */
 65  
 public abstract class AbstractStringCache {
 66  
 
 67  
     /** The default maximum number of entries to keep in the cache. */
 68  
     public static final int DEFAULT_MAX_CACHE_ENTRIES = 24;
 69  
 
 70  
     /** The default maximum length byte array to cache. */
 71  
     public static final int DEFAULT_MAX_CACHE_LENGTH = 25;
 72  
 
 73  
     /** The maximum value for the multiplier. */
 74  
     protected static final int MAX_MULTIPLIER = 100;
 75  
 
 76  
     /**
 77  
      * The maximum length of a string to cache. This can be used to stop a
 78  
      * single long string from pushing useful values out of the cache.
 79  
      */
 80  
     protected int myMaxCacheLength;
 81  
 
 82  
     /** The maximum number of entries to have in the cache. */
 83  
     protected int myMaxCachEntries;
 84  
 
 85  
     /**
 86  
      * The multiplier for the number of times the cache is used before we
 87  
      * re-load the cache.
 88  
      */
 89  
     protected volatile int myMaxCachEntriesMultiplier;
 90  
 
 91  
     /**
 92  
      * The map of seen strings that will be used periodically to build the cache
 93  
      * cache.
 94  
      */
 95  
     protected final ConcurrentMap<String, SeenString> mySeen;
 96  
 
 97  
     /**
 98  
      * The number of time the cache has been used since the last re-load of
 99  
      * runtime, read-only cache.
 100  
      */
 101  
     protected final AtomicInteger myUseCount;
 102  
 
 103  
     /**
 104  
      * Creates a new AbstractStringCache.
 105  
      */
 106  
     public AbstractStringCache() {
 107  6958
         super();
 108  
 
 109  6958
         mySeen = new ConcurrentHashMap<String, SeenString>(1024);
 110  
 
 111  6958
         myMaxCacheLength = DEFAULT_MAX_CACHE_LENGTH;
 112  6958
         myMaxCachEntries = DEFAULT_MAX_CACHE_ENTRIES;
 113  
 
 114  6958
         myMaxCachEntriesMultiplier = 0;
 115  6958
         myUseCount = new AtomicInteger(0);
 116  6958
     }
 117  
 
 118  
     /**
 119  
      * Returns the maximum node count.
 120  
      * 
 121  
      * @return The maximum node count.
 122  
      */
 123  
     public int getMaxCacheEntries() {
 124  56
         return myMaxCachEntries;
 125  
     }
 126  
 
 127  
     /**
 128  
      * Returns the maximum length of strings to cache. This can be used to stop
 129  
      * a single long string from pushing useful values out of the cache.
 130  
      * 
 131  
      * @return The maximum length of strings to cache.
 132  
      */
 133  
     public int getMaxCacheLength() {
 134  3
         return myMaxCacheLength;
 135  
     }
 136  
 
 137  
     /**
 138  
      * Sets the value of maximum number of cached strings.
 139  
      * 
 140  
      * @param maxCacheEntries
 141  
      *            The new value for the maximum number of cached strings.
 142  
      */
 143  
     public void setMaxCacheEntries(final int maxCacheEntries) {
 144  111
         myMaxCachEntries = maxCacheEntries;
 145  111
     }
 146  
 
 147  
     /**
 148  
      * Sets the value of maximum length of strings to cache to the new value.
 149  
      * This can be used to stop a single long string from pushing useful values
 150  
      * out of the cache.
 151  
      * 
 152  
      * @param maxlength
 153  
      *            The new value for the maximum length of strings to cache.
 154  
      */
 155  
     public void setMaxCacheLength(final int maxlength) {
 156  110
         myMaxCacheLength = maxlength;
 157  
 
 158  
         // The user has turned the cache off. Release all of the memory.
 159  110
         if (maxlength <= 0) {
 160  35
             clear();
 161  
         }
 162  110
     }
 163  
 
 164  
     /**
 165  
      * Notification that a string/byte[] have been used.
 166  
      * 
 167  
      * @param source
 168  
      *            The bytes in the string.
 169  
      * @param offset
 170  
      *            The offset of the first byte.
 171  
      * @param length
 172  
      *            The length of the bytes with a terminal zero byte.
 173  
      * @param decoded
 174  
      *            The decoded string.
 175  
      */
 176  
     public void used(final String decoded, final byte[] source,
 177  
             final int offset, final int length) {
 178  
 
 179  
         // Check for the easiest value to cache or entries that are too long.
 180  194066
         if ((length <= 1) || (myMaxCacheLength < length)) {
 181  8246
             return;
 182  
         }
 183  
 
 184  185820
         SeenString entry = mySeen.get(decoded);
 185  185818
         if (entry == null) {
 186  136605
             entry = new SeenString(source, offset, length, decoded);
 187  
 
 188  136605
             final SeenString existing = mySeen.putIfAbsent(decoded, entry);
 189  136605
             if (existing != null) {
 190  0
                 entry = existing;
 191  
             }
 192  
         }
 193  
 
 194  
         // Count the use.
 195  185819
         entry.incrementCount();
 196  
 
 197  
         // Rebuild the count after period of use.
 198  185821
         int use = myUseCount.incrementAndGet();
 199  190310
         while ((myMaxCachEntries * myMaxCachEntriesMultiplier) < use) {
 200  4489
             use = tryRebuild(use);
 201  
         }
 202  
 
 203  
         // Periodically trim low count entries.
 204  185821
         if ((use != 0) && ((use % myMaxCachEntries) == 0)) {
 205  7357
             trimPending();
 206  
         }
 207  185821
     }
 208  
 
 209  
     /**
 210  
      * Builds a map of the seen strings at each count. The order of the map is
 211  
      * reversed so that higher counts are first in the map.
 212  
      * 
 213  
      * @return A map of the seen strings at each count.
 214  
      */
 215  
     protected SortedMap<Integer, List<SeenString>> buildCacheGroups() {
 216  4490
         myMaxCachEntriesMultiplier = Math.min(myMaxCachEntriesMultiplier + 1,
 217  
                 MAX_MULTIPLIER);
 218  
 
 219  
         // Copy the current entries.
 220  4490
         final SortedMap<Integer, List<SeenString>> order = new TreeMap<Integer, List<SeenString>>(
 221  
                 ReverseIntegerComparator.INSTANCE);
 222  
 
 223  4490
         for (final SeenString seen : mySeen.values()) {
 224  10458
             final Integer seenCount = Integer.valueOf(seen.reset());
 225  10458
             List<SeenString> seenAtCount = order.get(seenCount);
 226  10458
             if (seenAtCount == null) {
 227  4805
                 seenAtCount = new LinkedList<SeenString>();
 228  4805
                 order.put(seenCount, seenAtCount);
 229  
             }
 230  
 
 231  10458
             seenAtCount.add(seen);
 232  10458
         }
 233  
 
 234  4490
         return order;
 235  
     }
 236  
 
 237  
     /**
 238  
      * Clears the cache.
 239  
      */
 240  
     protected void clear() {
 241  35
         mySeen.clear();
 242  35
         myUseCount.set(0);
 243  35
     }
 244  
 
 245  
     /**
 246  
      * Rebuilds the cache from the current {@link #mySeen} map. By the end of
 247  
      * the method the caceh should have been rebuild and the {@link #mySeen} map
 248  
      * cleared.
 249  
      */
 250  
     protected abstract void rebuildCache();
 251  
 
 252  
     /**
 253  
      * Trims the seen map of any entries that have not accumulated many counts.
 254  
      * This is to avoid the seen map holding too many entries.
 255  
      */
 256  
     private void trimPending() {
 257  
         // Only keep entries that have more than 1% (or more than 1 if 1% is <
 258  
         // 1) entries.
 259  7357
         final int trimLevel = Math.max(1, (int) (myMaxCachEntries * 0.01));
 260  7357
         int toRemove = Math.max(0, mySeen.size() - myMaxCachEntries);
 261  
 
 262  7357
         final Iterator<SeenString> iter = mySeen.values().iterator();
 263  135250
         while (iter.hasNext() && (0 < toRemove)) {
 264  127893
             final SeenString seen = iter.next();
 265  127893
             if (seen.getCount() <= trimLevel) {
 266  127822
                 iter.remove();
 267  127822
                 toRemove -= 1;
 268  
             }
 269  127893
         }
 270  7357
     }
 271  
 
 272  
     /**
 273  
      * Attempts to rebuild the cache.
 274  
      * 
 275  
      * @param use
 276  
      *            The expected use count.
 277  
      * @return The use count after the attempt.
 278  
      */
 279  
     private int tryRebuild(final int use) {
 280  4489
         if (myUseCount.compareAndSet(use, 0)) {
 281  4489
             rebuildCache();
 282  
 
 283  4489
             return 0;
 284  
         }
 285  0
         return myUseCount.get();
 286  
     }
 287  
 
 288  
     /**
 289  
      * ReverseIntegerComparator provides a {@link Comparator} that considers
 290  
      * higher value integers to be less then lower value integers.
 291  
      * 
 292  
      * @copyright 2014, Allanbank Consulting, Inc., All Rights Reserved
 293  
      */
 294  14535
     public static class ReverseIntegerComparator implements
 295  
             Comparator<Integer>, Serializable {
 296  
 
 297  
         /** The single instance of the comparator. */
 298  1
         public static final Comparator<Integer> INSTANCE = new ReverseIntegerComparator();
 299  
 
 300  
         /** The serialization version for the class. */
 301  
         private static final long serialVersionUID = 7342816815375930353L;
 302  
 
 303  
         /**
 304  
          * Creates a new ReverseIntegerComparator. Private since this class is
 305  
          * stateless and we only need a single copy.
 306  
          */
 307  
         private ReverseIntegerComparator() {
 308  1
             super();
 309  1
         }
 310  
 
 311  
         /**
 312  
          * {@inheritDoc}
 313  
          * <p>
 314  
          * Overridden to reverse the comparison of integers.
 315  
          * </p>
 316  
          */
 317  
         @Override
 318  
         public int compare(final Integer o1, final Integer o2) {
 319  14535
             final int value = o1.compareTo(o2);
 320  
 
 321  14535
             if (value < 0) {
 322  4043
                 return 1;
 323  
             }
 324  10492
             else if (0 < value) {
 325  349
                 return -1;
 326  
             }
 327  
             else {
 328  10143
                 return 0;
 329  
             }
 330  
         }
 331  
     }
 332  
 }