package org.radargun.stages.cache.test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import org.radargun.Operation;
import org.radargun.config.Namespace;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.test.OperationLogic;
import org.radargun.stages.test.OperationSelector;
import org.radargun.stages.test.RatioOperationSelector;
import org.radargun.stages.test.Stressor;
import org.radargun.stages.test.TestStage;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.ConditionalOperations;
import org.radargun.traits.InjectTrait;
/**
* @author Radim Vansa <rvansa@redhat.com>
*/
@Namespace(name = TestStage.NAMESPACE, deprecatedName = TestStage.DEPRECATED_NAMESPACE)
@Stage(doc = "Tests (atomic) conditional operations. Note that there is no put-if-absent-ratio" +
"- this operation is executed anytime the selected key does not have value.")
public class ConditionalOperationsTestStage extends CacheOperationsTestStage {
@Property(doc = "Ratio of REMOVE requests. Default is 1.")
protected int removeRatio = 1;
@Property(doc = "Ratio of REPLACE requests. Default is 1.")
protected int replaceRatio = 1;
@Property(doc = "Ratio of REPLACE_ANY requests. Default is 1.")
protected int replaceAnyRatio = 1;
@Property(doc = "Ratio of GET_AND_REPLACE requests. Default is 1.")
protected int getAndReplaceRatio = 1;
@Property(doc = "Percentage of requests in which the condition should be satisfied. Default is 50%.")
protected int matchPercentage = 50;
@InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
protected BasicOperations basicOperations;
@InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
protected ConditionalOperations conditionalOperations;
@Override
protected OperationSelector createOperationSelector() {
statisticsPrototype.registerOperationsGroup(BasicOperations.class.getSimpleName() + ".Total",
new HashSet<>(Arrays.asList(
ConditionalOperations.REMOVE,
ConditionalOperations.REPLACE,
ConditionalOperations.REPLACE_ANY,
ConditionalOperations.GET_AND_REPLACE)));
return new RatioOperationSelector.Builder()
.add(ConditionalOperations.REMOVE, removeRatio)
.add(ConditionalOperations.REPLACE, replaceRatio)
.add(ConditionalOperations.REPLACE_ANY, replaceAnyRatio)
.add(ConditionalOperations.GET_AND_REPLACE, getAndReplaceRatio)
.build();
}
@Override
public OperationLogic getLogic() {
return new Logic();
}
protected class MatchSelector {
long matching = 0;
long failing = 0;
public void record(boolean matching) {
if (matching) this.matching++;
else this.failing++;
}
public boolean shouldMatch() {
return matching * 100 / (matching + failing) <= matchPercentage;
}
}
protected class Logic extends OperationLogic {
protected MatchSelector matchSelector = new MatchSelector();
protected BasicOperations.Cache nonTxBasicCache, basicCache;
protected ConditionalOperations.Cache nonTxConditionalCache, conditionalCache;
protected KeySelector keySelector;
@Override
public void init(Stressor stressor) {
super.init(stressor);
String cacheName = cacheSelector.getCacheName(stressor.getGlobalThreadIndex());
nonTxBasicCache = basicOperations.getCache(cacheName);
nonTxConditionalCache = conditionalOperations.getCache(cacheName);
if (useTransactions(cacheName)) {
basicCache = new Delegates.BasicOperationsCache();
conditionalCache = new Delegates.ConditionalOperationsCache();
} else {
basicCache = nonTxBasicCache;
conditionalCache = nonTxConditionalCache;
}
stressor.setUseTransactions(useTransactions(cacheName));
keySelector = getKeySelector(stressor);
}
@Override
public void transactionStarted() {
((Delegates.BasicOperationsCache) basicCache).setDelegate(stressor.wrap(nonTxBasicCache));
((Delegates.ConditionalOperationsCache) conditionalCache).setDelegate(stressor.wrap(nonTxConditionalCache));
}
@Override
public void transactionEnded() {
((Delegates.BasicOperationsCache) basicCache).setDelegate(null);
((Delegates.ConditionalOperationsCache) conditionalCache).setDelegate(null);
}
@Override
public void run(Operation operation) throws RequestException {
Random random = stressor.getRandom();
Object key = keyGenerator.generateKey(keySelector.next());
Object newValue = valueGenerator.generateValue(key, entrySize.next(random), random);
boolean shouldMatch = matchSelector.shouldMatch();
Object oldValue = stressor.makeRequest(new CacheInvocations.Get(basicCache, key));
if (oldValue == null) {
Object prevValue;
if (shouldMatch) {
prevValue = stressor.makeRequest(new CacheInvocations.PutIfAbsent(conditionalCache, key, newValue));
matchSelector.record(prevValue == null);
} else {
prevValue = stressor.makeRequest(new CacheInvocations.GetAndReplace(conditionalCache, key, newValue));
matchSelector.record(prevValue != null);
}
} else if (operation == ConditionalOperations.REMOVE) {
Boolean removed = (Boolean) stressor.makeRequest(new CacheInvocations.RemoveConditionally(conditionalCache, key,
shouldMatch ? oldValue : newValue));
matchSelector.record(removed);
} else if (operation == ConditionalOperations.REPLACE) {
Object wrongValue = valueGenerator.generateValue(key, entrySize.next(random), random);
Boolean replaced = (Boolean) stressor.makeRequest(new CacheInvocations.Replace(conditionalCache, key,
shouldMatch ? oldValue : wrongValue, newValue));
matchSelector.record(replaced);
} else if (operation == ConditionalOperations.REPLACE_ANY) {
Object prevValue;
if (shouldMatch) {
prevValue = stressor.makeRequest(new CacheInvocations.ReplaceAny(conditionalCache, key, newValue));
matchSelector.record((Boolean) prevValue);
} else {
prevValue = stressor.makeRequest(new CacheInvocations.PutIfAbsent(conditionalCache, key, newValue));
matchSelector.record(prevValue == null);
}
} else if (operation == ConditionalOperations.GET_AND_REPLACE) {
Object prevValue;
if (shouldMatch) {
prevValue = stressor.makeRequest(new CacheInvocations.GetAndReplace(conditionalCache, key, newValue));
matchSelector.record(oldValue.equals(prevValue));
} else {
prevValue = stressor.makeRequest(new CacheInvocations.PutIfAbsent(conditionalCache, key, newValue));
matchSelector.record(prevValue == null);
}
} else throw new IllegalStateException("Unable to execute operation: " + operation.name);
}
}
}