Package org.radargun.stages.cache

Source Code of org.radargun.stages.cache.CheckCacheDataStage$GeneratedValueChecker

package org.radargun.stages.cache;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.radargun.DistStageAck;
import org.radargun.StageResult;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.AbstractDistStage;
import org.radargun.stages.cache.generators.KeyGenerator;
import org.radargun.stages.cache.generators.ValueGenerator;
import org.radargun.stages.helpers.CacheSelector;
import org.radargun.stages.helpers.Range;
import org.radargun.state.SlaveState;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.CacheInformation;
import org.radargun.traits.Debugable;
import org.radargun.traits.InMemoryBasicOperations;
import org.radargun.traits.InjectTrait;
import org.radargun.utils.Utils;

/**
* @author Radim Vansa <rvansa@redhat.com>
*/
@Stage(doc = "Stage for checking presence or absence of data entered in other stages.")
public class CheckCacheDataStage extends AbstractDistStage {

   @Property(optional = false, doc = "Number of entries with key in form specified by the last used key generator, in the cache.")
   private int numEntries;

   @Property(doc = "Index of key of first entry. This number will be multiplied by slaveIndex. Default is 0.")
   private int firstEntryOffset = 0;

   @Property(doc = "Number of entries that will be checked in each step. Default is 1.")
   private int checkEntryCount = 1;

   @Property(doc = "Number of entries stepped in each step. Default is 1.")
   private int stepEntryCount = 1;

   @Property(optional = false, doc = "Number of bytes carried in single entry.")
   private int entrySize;

   @Property(doc = "Entries that do not have the expected form but occur in the cluster. This string specifies " +
         "a polynomial in number of slaves: 1,2,3 with 4 slaves would result in 1 + 2*4 + 3*4*4 = 57 extra entries." +
         "Defaults to 0.")
   private String extraEntries;

   @Property(doc = "Number of thread per node which check data validity. Default is 1.")
   private int checkThreads = 1;

   @Property(doc = "Usually the test checks that sum of local nodes = numOwners * numEntries + extraEntries." +
         "This option disables such behaviour. Default is false.")
   private boolean ignoreSum = false;

   @Property(doc = "If true, the entries are not retrieved, this stage only checks that the sum of entries from local nodes is correct. Default is false.")
   private boolean sizeOnly = false;

   @Property(doc = "Hint how many slaves are currently alive - if set to > 0 then the query for amount of entries in " +
         "this cache is postponed until the cache appears to be fully replicated. By default this is disabled.")
   private int liveSlavesHint = -1;

   @Property(doc = "If set to true, we are checking that the data are NOT in the cluster anymore. Default is false.")
   private boolean deleted = false;

   @Property(doc = "Number of queries after which a DEBUG log message is printed. Default is 10000.")
   private int logChecksCount = 10000;

   @Property(doc = "If the GET request results in null response, call wrapper-specific functions to show debug info. " +
         "Default is false.")
   private boolean debugNull = false;

   @Property(doc = "If entry is null, fail immediatelly. Default is false.")
   private boolean failOnNull = false;

   @Property(doc = "If the cache wrapper supports persistent storage and this is set to true, the check " +
         "will be executed only against in-memory data. Default is false.")
   private boolean memoryOnly = false;

   @Property(doc = "Full class name of the key generator. By default the generator is retrieved from slave state.")
   protected String keyGeneratorClass = null;

   @Property(doc = "Used to initialize the key generator. Null by default.")
   protected String keyGeneratorParam = null;

   @Property(doc = "Full class name of the value generator. By default the generator is retrieved from slave state.")
   protected String valueGeneratorClass = null;

   @Property(doc = "Used to initialize the value generator. Null by default.")
   protected String valueGeneratorParam = null;

   // TODO: better names, even when these are kind of hacks
   @Property(doc = "Check whether the sum of subparts sizes is the same as local size. Default is false.")
   private boolean checkSubpartsSumLocal = false;

   @Property(doc = "Check whether the same subparts from each cache have the same size. Default is false.")
   private boolean checkSubpartsEqual = false;

   @Property(doc = "Check that number of non-zero subparts is equal to number of replicas. Default is false.")
   private boolean checkSubpartsAreReplicas = false;

   private transient KeyGenerator keyGenerator;

   @InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
   protected BasicOperations basicOperations;
   @InjectTrait
   protected InMemoryBasicOperations inMemoryBasicOperations;
   @InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
   protected CacheInformation cacheInformation;
   @InjectTrait
   protected Debugable debugable;

   protected BasicOperations.Cache basicCache;
   protected Debugable.Cache debugableCache;

   @Override
   public DistStageAck executeOnSlave() {
      if (!shouldExecute()) {
         return successfulResponse();
      }
      if (!isServiceRunning()) {
         // this slave is dead and does not participate on check
         return successfulResponse();
      }
      if (!sizeOnly) {
         if (keyGeneratorClass != null) {
            keyGenerator = Utils.instantiateAndInit(slaveState.getClassLoader(), keyGeneratorClass, keyGeneratorParam);
         } else {
            keyGenerator = (KeyGenerator) slaveState.get(KeyGenerator.KEY_GENERATOR);
         }
         CheckResult result = new CheckResult();
         if (memoryOnly && inMemoryBasicOperations != null) {
            basicCache = inMemoryBasicOperations.getMemoryOnlyCache(getCacheName());
         } else {
            basicCache = basicOperations.getCache(getCacheName());
         }
         if (debugable != null){
            debugableCache = debugable.getCache(getCacheName());
         }

         try {
            if (checkThreads <= 1) {
               ValueChecker checker = new GeneratedValueChecker((ValueGenerator) slaveState.get(ValueGenerator.VALUE_GENERATOR));
               int entriesToCheck = numEntries;
               for (int i = firstEntryOffset * slaveState.getSlaveIndex(); entriesToCheck > 0; i += stepEntryCount) {
                  int checkAmount = Math.min(checkEntryCount, entriesToCheck);
                  for (int j = 0; j < checkAmount; ++j) {
                     if (!checkKey(basicCache, debugableCache, i + j, result, checker)) {
                        entriesToCheck = 0;
                        break;
                     }
                  }
                  entriesToCheck -= checkAmount;
               }
            } else {
               ExecutorService executor = Executors.newFixedThreadPool(checkThreads);
               List<Callable<CheckResult>> tasks = new ArrayList<Callable<CheckResult>>();
               for (int i = 0; i < checkThreads; ++i) {
                  Range range = Range.divideRange(numEntries, checkThreads, i);
                  tasks.add(new CheckRangeTask(range.getStart(), range.getEnd()));
               }
               for (Future<CheckResult> future : executor.invokeAll(tasks)) {
                  CheckResult value = future.get();
                  result.merge(value);
               }
               executor.shutdown();
            }
         } catch (Exception e) {
            return errorResponse("Failed to check entries", e);
         }

         if (!isDeleted()) {
            if (result.found != getExpectedNumEntries()) {
               return new InfoAck(slaveState, result).error("Found " + result.found + " entries while " + getExpectedNumEntries() + " should be loaded.");
            }
         } else {
            if (result.found > 0) {
               return new InfoAck(slaveState, result).error("Found " + result.found + " entries while these should be deleted.");
            }
         }
      }
      CacheInformation.Cache info = cacheInformation.getCache(getCacheName());
      if (liveSlavesHint > 0) {
         // try to wait until data are properly replicated
         int extraEntries = getExtraEntries();
         int commonEntries = isDeleted() ? 0 : numEntries;
         long myExpectedOwnedSize = (commonEntries + extraEntries) / liveSlavesHint;
         int numOwners = info.getNumReplicas();
         long myExpectedLocalSize = myExpectedOwnedSize * (numOwners < 0 ? -numOwners * slaveState.getClusterSize() : numOwners);
         for (int attempt = 0; attempt < 5; ++attempt) {
            long owned = info.getOwnedSize();
            long local = info.getLocallyStoredSize();
            double ratioOwned = (double) owned / (double) myExpectedOwnedSize;
            double ratioLocal = (double) local / (double) myExpectedLocalSize;
            if (ratioOwned < 0.9 || ratioOwned > 1.1) {
               log.warn("Owned size (" + owned + ") differs substantially from expected size (" + myExpectedOwnedSize + "), waiting 30s to let it replicate");
            } else if (ratioLocal < 0.9 || ratioLocal > 1.1) {
               log.warn("Locally stored size (" + local + ") differs substantially from expected size (" + myExpectedLocalSize + "), waiting 30s to let it replicate");
            } else {
               break;
            }
            try {
               Thread.sleep(30000);
            } catch (InterruptedException e) {
               break;
            }
         }
      }
      return new InfoAck(slaveState, info.getOwnedSize(), info.getLocallyStoredSize(), info.getTotalSize(), info.getStructuredSize(), info.getNumReplicas());
   }

   private String getCacheName() {
      CacheSelector selector = (CacheSelector) slaveState.get(CacheSelector.CACHE_SELECTOR);
      return selector == null ? null : selector.getCacheName(-1);
   }

   private class CheckRangeTask implements Callable<CheckResult> {
      private int from, to;
     
      public CheckRangeTask(int from, int to) {
         this.from = from;
         this.to = to;
      }
     
      @Override
      public CheckResult call() throws Exception {
         try {
            CheckResult result = new CheckResult();
            ValueChecker checker = new GeneratedValueChecker(getValueGenerator());
            int entriesToCheck = to - from;
            for (int i = from * (stepEntryCount / checkEntryCount) + firstEntryOffset * slaveState.getSlaveIndex(); entriesToCheck > 0; i += stepEntryCount) {
               int checkAmount = Math.min(checkEntryCount, entriesToCheck);
               for (int j = 0; j < checkAmount; ++j) {
                  if (!checkKey(basicCache, debugableCache, i + j, result, checker)) {
                     entriesToCheck = 0;
                     break;
                  }
               }
               entriesToCheck -= checkAmount;
            }
            return result;
         } catch (Exception e) {
            log.error("Failed to check entries", e);
            return null;
         }
      }
   }

   protected ValueGenerator getValueGenerator() {
      ValueGenerator valueGenerator;
      if (valueGeneratorClass != null) {
         valueGenerator = Utils.instantiateAndInit(slaveState.getClassLoader(), valueGeneratorClass, valueGeneratorParam);
      } else {
         valueGenerator = (ValueGenerator) slaveState.get(ValueGenerator.VALUE_GENERATOR);
      }
      return valueGenerator;
   }

   protected int getExpectedNumEntries() {
      return numEntries;
   }

   protected boolean checkKey(BasicOperations.Cache basicCache, Debugable.Cache debugableCache, int keyIndex, CheckResult result, ValueChecker checker) {
      Object key = keyGenerator.generateKey(keyIndex);
      try {
         Object value = basicCache.get(key);
         if (!isDeleted()) {
            if (value != null && checker.check(keyIndex, value)) {
               result.found++;
            } else {
               if (value == null) {
                  result.nullValues++;
                  if (debugNull && debugableCache != null) {
                     debugableCache.debugInfo();
                     debugableCache.debugKey(key);
                  }
                  if (failOnNull) {
                     return false;
                  }
               } else {
                  result.invalidValues++;
               }
               unexpected(key, value);
            }
         } else {
            if (value != null) {
               result.found++;
               shouldBeDeleted(key, value);
            } else {
               result.nullValues++;
            }
         }
      } catch (Exception e) {
         if (result.exceptions == 0) {
            log.error("Error retrieving value for key " + key, e);
         } else if (log.isTraceEnabled()) {
            log.trace("Error retrieving value for key " + key, e);
         }
         result.exceptions++;
      } finally {
         result.checked++;
         if (result.checked % logChecksCount == 0) {
            log.debug("Checked so far: " + result);
         }
      }
      return true;
   }

   protected void shouldBeDeleted(Object key, Object value) {
      if (log.isTraceEnabled()) {
         log.trace("Key " + key + " still has value " + value);
      }
   }

   protected void unexpected(Object key, Object value) {
      if (log.isTraceEnabled()) {
         log.trace("Key " + key + " has unexpected value " + value);
      }
   }

   @Override
   public StageResult processAckOnMaster(List<DistStageAck> acks) {
      StageResult result = super.processAckOnMaster(acks);
      if (result.isError()) return result;

      long sumOwnedSize = 0, sumLocalSize = 0;
      Long totalSize = null;
      Integer numReplicas = null;
      Map<Object, Map<Integer, Long>> subparts = new HashMap<>();
      for (DistStageAck ack : acks) {
         if (!(ack instanceof InfoAck)) {
            continue;
         }
         InfoAck info = (InfoAck) ack;
         log.debugf("Slave %d has owned size %d, local size %d and total size %d", ack.getSlaveIndex(), info.ownedSize, info.localSize, info.totalSize);
         sumLocalSize += info.localSize;
         sumOwnedSize += info.ownedSize;
         if (info.totalSize >= 0) {
            if (totalSize == null) totalSize = info.totalSize;
            else if (totalSize != info.totalSize) {
               log.errorf("Slave %d reports total size %d but other slave reported %d", ack.getSlaveIndex(), info.totalSize, totalSize);
               result = errorResult();
            }
         } else if (totalSize != null) {
            log.errorf("Slave %d does not report any total size but other slave reported %d", ack.getSlaveIndex(), totalSize);
            result = errorResult();
         }
         if (numReplicas == null) numReplicas = info.numReplicas;
         else if (numReplicas != info.numReplicas) {
            log.errorf("Slave %d reports %d replicas but other slave reported %d replicas", ack.getSlaveIndex(), info.numReplicas, numReplicas);
            result = errorResult();
         }
         int sumSubpartSize = 0;
         for (Map.Entry<?, Long> subpart : info.structuredSize.entrySet()) {
            log.tracef("Subpart %s = %d", subpart.getKey(), subpart.getValue());
            if (subpart.getValue() == 0) continue;

            sumSubpartSize += subpart.getValue();
            Map<Integer, Long> otherSubparts = subparts.get(subpart.getKey());
            if (otherSubparts == null) {
               subparts.put(subpart.getKey(), new HashMap<>(Collections.singletonMap(info.getSlaveIndex(), subpart.getValue())));
            } else if (checkSubpartsEqual) {
               for (Map.Entry<Integer, Long> os : otherSubparts.entrySet()) {
                  if (Long.compare(subpart.getValue(), os.getValue()) != 0) {
                     log.errorf("Slave %d reports %s = %d but slave %d reported size %d",
                           info.getSlaveIndex(), subpart.getKey(), subpart.getValue(), os.getKey(), os.getValue());
                     result = errorResult();
                  }
               }
               otherSubparts.put(info.getSlaveIndex(), subpart.getValue());
            }
         }
         if (checkSubpartsSumLocal && sumSubpartSize != info.localSize) {
            log.errorf("On slave %d sum of subparts sizes (%d) is not the same as local size (%d)",
                  info.getSlaveIndex(), sumSubpartSize, info.localSize);
            result = errorResult();
         }
      }
      if (checkSubpartsAreReplicas) {
         for (Map.Entry<Object, Map<Integer, Long>> subpart : subparts.entrySet()) {
            if (subpart.getValue().size() != numReplicas) {
               log.errorf("Subpart %s was found in %s, should have %d replicas.", subpart.getKey(), subpart.getValue().keySet(), numReplicas);
               result = errorResult();
            }
         }
      }
      if (ignoreSum) {
         log.infof("The sum of owned sizes is %d, sum of local sizes is %d", sumOwnedSize, sumLocalSize);
      } else {
         int extraEntries = getExtraEntries();
         int commonEntries = isDeleted() ? 0 : numEntries;
         long expectedOwnedSize = extraEntries + commonEntries;
         if (sumLocalSize >= 0) {
            long expectedLocalSize = expectedOwnedSize * (numReplicas < 0 ? -numReplicas * masterState.getClusterSize() : numReplicas);
            if (expectedLocalSize != sumLocalSize) {
               log.errorf("The cache should contain %d entries (including backups, %d replicas) but contains %d entries.", expectedLocalSize, numReplicas, sumLocalSize);
               result = errorResult();
            } else {
               log.tracef("The sum of local sizes is %d entries as expected", sumLocalSize);
            }
         }
         if (sumOwnedSize >= 0) {
            if (expectedOwnedSize != sumOwnedSize) {
               log.errorf("The cache should contain %d entries but contains %d entries.", expectedOwnedSize, sumOwnedSize);
               result = errorResult();
            } else {
               log.tracef("The sum of owned sizes is %d entries as expected", sumOwnedSize);
            }
         }
         if (totalSize != null) {
            if (expectedOwnedSize != totalSize) {
               log.errorf("The cache should contain %d entries but total size is %d.", expectedOwnedSize, totalSize);
               result = errorResult();
            } else {
               log.tracef("The total size is %d as expected", totalSize);
            }
         }
      }
      return result;
   }

   public int getNumEntries() {
      return this.numEntries;
   }

   private int getExtraEntries() {
      if (extraEntries == null) return 0;
     
      int sum = 0;
      int multiplicator = 1;     
      try {
         for (String entries : extraEntries.split(",")) {
            int count = Integer.parseInt(entries);
            sum += count * multiplicator;
            multiplicator *= slaveState.getClusterSize();
         }
      } catch (NumberFormatException e) {
         log.error("Cannot parse " + extraEntries);
      }
      return sum;
   }

   public boolean isDeleted() {
      return deleted;
   }

   protected static class InfoAck extends DistStageAck {
      final long ownedSize, localSize, totalSize;
      final Map<?, Long> structuredSize;
      final int numReplicas;
      final CheckResult checkResult;

      public InfoAck(SlaveState slaveState, long ownedSize, long localSize, long totalSize, Map<?, Long> structuredSize, int numReplicas) {
         super(slaveState);
         this.ownedSize = ownedSize;
         this.localSize = localSize;
         this.totalSize = totalSize;
         this.structuredSize = structuredSize;
         this.numReplicas = numReplicas;
         checkResult = null;
      }

      public InfoAck(SlaveState slaveState, CheckResult checkResult) {
         super(slaveState);
         this.checkResult = checkResult;
         ownedSize = localSize = totalSize = -1;
         structuredSize = null;
         numReplicas = -1;
      }
   }

   protected static class CheckResult implements Serializable {
      public long checked;
      public long found;
      public long nullValues;
      public long invalidValues;
      public long exceptions;

      public void merge(CheckResult value) {
         if (value == null) return;
         checked += value.checked;
         found += value.found;
         nullValues += value.nullValues;
         invalidValues += value.invalidValues;
         exceptions += value.invalidValues;
      }

      @Override
      public String toString() {
         return String.format("[checked=%d, found=%d, nullValues=%d, invalidValues=%d, exceptions=%d]",
                              checked, found, nullValues, invalidValues, exceptions);
      }
   }

   protected interface ValueChecker {
      boolean check(int keyIndex, Object value);
   }

   protected class GeneratedValueChecker implements ValueChecker {
      private final ValueGenerator valueGenerator;

      public GeneratedValueChecker(ValueGenerator valueGenerator) {
         this.valueGenerator = valueGenerator;
      }

      @Override
      public boolean check(int keyIndex, Object value) {
         return valueGenerator.checkValue(value, entrySize);
      }
   }
}
TOP

Related Classes of org.radargun.stages.cache.CheckCacheDataStage$GeneratedValueChecker

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.