package org.radargun.stages.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import org.radargun.DistStageAck;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.AbstractDistStage;
import org.radargun.utils.TimeConverter;
/**
* @author Radim Vansa <rvansa@redhat.com>
*/
@Stage(doc = "Manages thread for data loading")
public abstract class LoadStage extends AbstractDistStage {
@Property(doc = "Number of loaded entries after which a log entry should be written. Default is 10000.")
protected long logPeriod = 10000;
@Property(doc = "The number of threads that should load the entries on one slave. Default is 10.")
protected int numThreads = 10;
@Property(doc = "Seed used for initialization of random generators - with same seed (and other arguments)," +
" the stage guarantees same entries added to the cache. By default the seed is not set.")
protected Long seed;
@Property(doc = "During loading phase, if the insert fails, try it again. This is the maximum number of attempts. Default is 10.")
protected int maxLoadAttempts = 10;
@Property(doc = "When an attempt to load an entry fails, wait this period to reduce the chances of failing again. Default is one second.",
converter = TimeConverter.class)
protected long waitOnError = 1000;
protected AtomicLong entryCounter = new AtomicLong(0);
protected AtomicLong sizeSum = new AtomicLong(0);
protected void logLoaded(long entries, long size, boolean remove) {
long prevEntryCount, currentEntryCount;
do {
prevEntryCount = entryCounter.get();
currentEntryCount = prevEntryCount + entries;
} while (!entryCounter.compareAndSet(prevEntryCount, currentEntryCount));
// just for logs - don't worry about those two not in sync
long totalSize = sizeSum.addAndGet(size);
if (prevEntryCount / logPeriod < currentEntryCount / logPeriod) {
log.infof("This node %s %d entries (~%d bytes)",
remove ? "removed" : "loaded", currentEntryCount, totalSize);
}
}
protected List<Loader> startLoaders() {
int threadBase = getExecutingSlaveIndex() * numThreads;
List<Loader> loaders = new ArrayList<>();
for (int i = 0; i < numThreads; ++i) {
Loader loader = createLoader(threadBase, i);
loaders.add(loader);
loader.start(); // no special synchronization needed
}
return loaders;
}
public DistStageAck executeOnSlave() {
if (!isServiceRunning()) {
log.info("Not running test on this slave as service is not running.");
return successfulResponse();
}
prepare();
List<Loader> loaders = startLoaders();
try {
stopLoaders(loaders);
} catch (InterruptedException e) {
return errorResponse("Interrupted when waiting for the loader to finish");
} catch (Exception e) {
return errorResponse("Loader failed with exception", e);
}
destroy();
return successfulResponse();
}
/**
* To be overridden in inheritors.
*/
protected void prepare() {
}
/**
* To be overridden in inheritors.
*/
protected void destroy() {
}
protected abstract Loader createLoader(int threadBase, int threadIndex);
protected void stopLoaders(List<Loader> loaders) throws Exception {
for (Loader loader : loaders) {
loader.join();
if (loader.getException() != null) {
throw loader.getException();
}
}
}
protected abstract class Loader extends Thread {
protected final Random random;
protected final int threadIndex;
protected Throwable throwable;
protected Loader(int index) {
super("Loader-" + index);
threadIndex = slaveState.getSlaveIndex() * numThreads + index;
random = seed == null ? new Random() : new Random(seed + threadIndex);
}
public Exception getException() {
return throwable == null ? null : new ExecutionException(throwable);
}
@Override
public void run() {
try {
for (; ; ) {
if (!loadDataUnit()) return;
}
} catch (Throwable t) {
log.error("Exception in Loader", t);
throwable = t;
}
}
protected abstract boolean loadDataUnit();
}
}