package org.radargun.stages.cache;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.state.SlaveState;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.InjectTrait;
import org.radargun.utils.Projections;
import org.radargun.utils.Utils;
/**
* Loads the contents of the specified file into the cache using the specified sized values. All
* slaves are used to read from the file and write keys to the cache.
*
* @author Alan Field <afield@redhat.com>
*/
@Stage(doc = "Loads the contents of a file into the cache.")
public class LoadFileStage extends AbstractDistStage {
@Property(optional = false, doc = "Full pathname to the file.")
private String filePath;
@Property(doc = "The size of the values to put into the cache from the contents"
+ " of the file. The default size is 1MB (1024 * 1024).")
private int valueSize = 1024 * 1024;
@Property(doc = "The name of the bucket where keys are written. The default is null.")
private String bucket = null;
@Property(doc = "If true, then the time for each put operation is written to the logs. The default is false.")
private boolean printWriteStatistics = false;
@Property(doc = "If true, then String objects are written to the cache. The default is false.")
private boolean stringData = false;
@InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
private BasicOperations basicOperations;
@Override
public StageResult processAckOnMaster(List<DistStageAck> acks) {
StageResult result = super.processAckOnMaster(acks);
if (result.isError()) return result;
long fileSize = new File(filePath).length();
log.info("--------------------");
log.info("Size of file '" + filePath + "' is " + fileSize + " bytes");
log.info("Value size is '" + valueSize + "' which will produce " + (int) Math.ceil((double) fileSize / valueSize)
+ " keys");
for (ResultAck ack : Projections.instancesOf(acks, ResultAck.class)) {
log.info("Slave " + ack.getSlaveIndex() + " wrote " + ack.putCount
+ " values to the cache with a total size of " + ack.totalBytesRead + " bytes");
}
log.info("--------------------");
return StageResult.SUCCESS;
}
@Override
public DistStageAck executeOnSlave() {
int totalWriters = slaveState.getClusterSize();
long fileOffset = valueSize * slaveState.getSlaveIndex();// index starts at 0
RandomAccessFile file = null;
FileChannel fileChannel = null;
long totalBytesRead = 0;
long putCount = 0;
try {
file = new RandomAccessFile(filePath, "r");
fileChannel = file.getChannel();
fileChannel.position(fileOffset);
ByteBuffer buffer = ByteBuffer.allocate(valueSize);
BasicOperations.Cache cache = basicOperations.getCache(bucket);
while (true) {
long initPos = fileChannel.position();
String key = Integer.toString(slaveState.getSlaveIndex()) + "-" + Long.toString(initPos);
int bytesRead = fileChannel.read(buffer);
if (bytesRead != -1) {
while (bytesRead != valueSize) {
int readBytes = fileChannel.read(buffer);
if (readBytes == -1) {
break;
} else {
bytesRead += readBytes;
}
}
totalBytesRead += bytesRead;
if (putCount % 5000 == 0) {
log.info("Writing " + bytesRead + " bytes to cache key: " + key + " at position "
+ fileChannel.position());
}
buffer.rewind();
long start = System.nanoTime();
if (stringData) {
String cacheData = buffer.asCharBuffer().toString();
start = System.nanoTime();
cache.put(key, cacheData);
} else {
cache.put(key, buffer.array());
}
if (printWriteStatistics) {
log.info("Put on slave-" + slaveState.getSlaveIndex() + " took "
+ Utils.prettyPrintTime(System.nanoTime() - start, TimeUnit.NANOSECONDS));
}
putCount++;
fileChannel.position(initPos + (valueSize * totalWriters));
buffer.clear();
} else {
file.close();
file = null;
break;
}
}
return new ResultAck(slaveState, putCount, totalBytesRead);
} catch (FileNotFoundException e) {
return errorResponse("File not find at path: " + filePath, e);
} catch (Exception e) {
return errorResponse("An exception occurred", e);
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
log.fatal("An exception occurred closing the file", e);
}
}
}
}
private static class ResultAck extends DistStageAck {
final long putCount;
final long totalBytesRead;
private ResultAck(SlaveState slaveState, long putCount, long totalBytesRead) {
super(slaveState);
this.putCount = putCount;
this.totalBytesRead = totalBytesRead;
}
@Override
public String toString() {
return "ResultAck{" +
"putCount=" + putCount +
", totalBytesRead=" + totalBytesRead +
"} " + super.toString();
}
}
}