Package org.radargun.stages.cache.test

Source Code of org.radargun.stages.cache.test.KeyExpirationTestStage$Load

package org.radargun.stages.cache.test;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.TreeSet;

import org.radargun.Operation;
import org.radargun.config.Init;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.test.OperationLogic;
import org.radargun.stages.test.Stressor;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.InjectTrait;
import org.radargun.utils.SizeConverter;
import org.radargun.utils.TimeConverter;

/**
* @author Radim Vansa <rvansa@redhat.com>
*/
@Stage(doc = "During execution, keys expire (entries are removed from the cache) and new keys are used.")
public class KeyExpirationTestStage extends CacheTestStage {

   @Property(doc = "Maximum number of entries stored in the cache by one stressor thread at one moment.")
   protected long numEntriesPerThread = 0;

   @Property(doc = "Maximum number of bytes in entries' values stored in the cache by one stressor thread at one moment.", converter = SizeConverter.class)
   protected long numBytesPerThread = 0;

   @Property(doc = "Due to configuration (eviction, expiration), some keys may spuriously disappear. Do not issue a warning for this situation. Default is false.")
   protected boolean expectLostKeys = false;

   @Property(doc = "With fixedKeys=false, maximum lifespan of an entry. Default is 1 hour.", converter = TimeConverter.class)
   protected long entryLifespan = 3600000;

   @Property(doc = "Ratio of GET requests. Default is 4.")
   protected int getRatio = 4;

   @Property(doc = "Ratio of PUT requests. Default is 1.")
   protected int putRatio = 1;

   @InjectTrait
   BasicOperations basicOperations;

   protected OperationSelector operationSelector;

   @Init
   public void init() {
      operationSelector = new OperationSelector.Builder()
            .add(BasicOperations.PUT, putRatio)
            .add(BasicOperations.GET, getRatio)
            .build();
   }


   @Override
   public OperationLogic getLogic() {
      return new Logic();
   }

   /**
    * @author Radim Vansa <rvansa@redhat.com>
    */
   protected class Logic extends OperationLogic {
      private long minRemoveTimestamp = Long.MAX_VALUE;
      private int minRemoveSize = 0;
      private HashMap<Integer, Load> loadForSize = new HashMap<>();
      private int nextKeyIndex;
      private BasicOperations.Cache nonTxCache;
      private BasicOperations.Cache cache;

      @Override
      public void init(Stressor stressor) {
         super.init(stressor);
         nextKeyIndex = stressor.getGlobalThreadIndex();
         double averageSize = 0;
         Map<Integer, Double> probabilityMap = entrySize.getProbabilityMap();
         long entries;
         if (numBytesPerThread > 0) {
            for (Map.Entry<Integer, Double> entry : probabilityMap.entrySet()) {
               averageSize += entry.getValue() * entry.getKey();
            }
            entries = (long) (numBytesPerThread / averageSize);
         } else {
            entries = numEntriesPerThread;
         }
         long expectedMax = 0;
         for (Map.Entry<Integer, Double> entry : probabilityMap.entrySet()) {
            long valuesForSize = (long) (entries * entry.getValue());
            expectedMax += valuesForSize * entry.getKey();
            loadForSize.put(entry.getKey(), new Load(valuesForSize));
         }
         log.info("Expecting maximal load of " + new SizeConverter().convertToString(expectedMax));

         String cacheName = cacheSelector.getCacheName(stressor.getGlobalThreadIndex());
         nonTxCache = basicOperations.getCache(cacheName);
         if (!useTransactions(cacheName)) {
            cache = nonTxCache;
         }
         stressor.setUseTransactions(useTransactions(cacheName));
      }

      @Override
      public void transactionStarted() {
         cache = stressor.wrap(nonTxCache);
      }

      @Override
      public void transactionEnded() {
         cache = null;
      }

      @Override
      public Object run() throws RequestException {
         Random random = stressor.getRandom();
         KeyWithRemovalTime pair;
         long timestamp = System.currentTimeMillis();
         if (minRemoveTimestamp <= timestamp) {
            Load load = loadForSize.get(minRemoveSize);
            pair = load.scheduledKeys.pollFirst();
            Boolean value;
            try {
               value = (Boolean) stressor.makeRequest(new Invocations.Remove(cache, pair.key));
            } catch (RequestException e) {
               load.scheduledKeys.add(pair);
               return null;
            }
            updateMin();
            if (!value && !expectLostKeys) {
               log.error("REMOVE: Entry for key " + pair.key + " was not found!");
            }
            return value;
         } else {
            Operation operation = operationSelector.next(random);
            if (operation == BasicOperations.GET && minRemoveTimestamp < Long.MAX_VALUE) {
               Load load = null;
               for (int attempt = 0; attempt < 100; ++attempt) {
                  load = loadForSize.get(entrySize.next(random));
                  if (!load.scheduledKeys.isEmpty()) break;
               }
               if (load.scheduledKeys.isEmpty()) {
                  log.error("Current load seems to be null but timestamp is " + minRemoveTimestamp);
                  return null;
               }
               pair = getRandomPair(load.scheduledKeys, timestamp, random);
               Object value = stressor.makeRequest(new Invocations.Get(cache, pair.key));
               if (value == null) {
                  if (expectLostKeys) {
                     load.scheduledKeys.remove(pair);
                     updateMin();
                  } else {
                     log.error("GET: Value for key " + pair.key + " is null!");
                  }
               }
               return value;
            } else {
               int size = entrySize.next(random);
               Load load = loadForSize.get(size);
               if (load.scheduledKeys.size() < load.max) {
                  long keyIndex = nextKeyIndex;
                  nextKeyIndex += getTotalThreads();
                  pair = new KeyWithRemovalTime(keyGenerator.generateKey(keyIndex), getRandomTimestamp(timestamp, random));
                  load.scheduledKeys.add(pair);
                  updateMin();
               } else {
                  pair = getRandomPair(load.scheduledKeys, timestamp, random);
               }
               Object value = valueGenerator.generateValue(null, size, stressor.getRandom());
               try {
                  return stressor.makeRequest(new Invocations.Put(cache, pair.key, value));
               } catch (RequestException e) {
                  load.scheduledKeys.remove(pair);
                  while (!isTerminated()) {
                     try {
                        return stressor.makeRequest(new Invocations.Remove(cache, pair.key));
                     } catch (RequestException e1) {
                        // exception already logged in Stressor
                     }
                  }
                  return null;
               }
            }
         }
      }

      private void updateMin() {
         minRemoveTimestamp = Long.MAX_VALUE;
         for (Map.Entry<Integer, Load> entry : loadForSize.entrySet()) {
            if (!entry.getValue().scheduledKeys.isEmpty()) {
               long min = entry.getValue().scheduledKeys.first().removeTimestamp;
               if (min < minRemoveTimestamp) {
                  minRemoveTimestamp = min;
                  minRemoveSize = entry.getKey();
               }
            }
         }
      }

      private long getRandomTimestamp(long current, Random random) {
         // ~sqrt probability for 1 - maxRoot^2
         final long maxRoot = (long) Math.sqrt((double) entryLifespan);
         long rand = random.nextLong() % maxRoot;
         return current + rand * rand + random.nextLong() % (2*maxRoot - 2) + 1;
      }

      private KeyWithRemovalTime getRandomPair(TreeSet<KeyWithRemovalTime> scheduledKeys, long timestamp, Random random) {
         // we cannot get random access to PriorityQueue and there is no SortedList or another appropriate structure
         KeyWithRemovalTime pair = scheduledKeys.floor(new KeyWithRemovalTime(null, getRandomTimestamp(timestamp, random)));
         return pair != null ? pair : scheduledKeys.first();
      }
   }

   private static class KeyWithRemovalTime implements Comparable<KeyWithRemovalTime> {
      public final Object key;
      public final long removeTimestamp;

      public KeyWithRemovalTime(Object key, long removeTimestamp) {
         this.key = key;
         this.removeTimestamp = removeTimestamp;
      }

      @Override
      public int compareTo(KeyWithRemovalTime o) {
         if (removeTimestamp < o.removeTimestamp) return -1;
         if (removeTimestamp > o.removeTimestamp) return 1;
         if (key == null || o.key == null) return 0;
         if (key.equals(o.key)) return 0;
         return -1;
      }
   }

   private static class Load {
      public final long max;
      public final TreeSet<KeyWithRemovalTime> scheduledKeys = new TreeSet<>();

      private Load(long max) {
         this.max = max;
      }
   }
}
TOP

Related Classes of org.radargun.stages.cache.test.KeyExpirationTestStage$Load

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.