0,0 → 1,570 |
// FIXME |
// oneFile support |
// better speed unit convert |
// |
package ak.diskbench; |
|
import java.util.List; |
import java.util.ArrayList; |
import java.util.Random; |
|
import java.text.DecimalFormat; |
|
import org.apache.commons.logging.Log; |
import org.apache.commons.logging.LogFactory; |
|
import java.io.File; |
import java.io.RandomAccessFile; |
import java.io.IOException; |
|
public class DiskBench |
{ |
private static Log log = LogFactory.getLog(DiskBench.class); |
|
public static void main(String[] args) |
throws Exception |
{ |
DiskBench db = new DiskBench(); |
|
if(db.parseParams(args)) |
db.run(); |
} |
|
// config |
private int threadNumber = 1; |
private boolean oneFile = false; |
private long seed = 0; |
private long length = 1024 * 1024; // 1M |
private int bufLength = 8 * 1024; // 8K |
private int randomWrites = 1000; |
private int randomReads = 1000; |
private int sleepMin = 0; |
private int sleepMax = 0; |
|
private int parseInt(String s, String prefix, String errorMsg) |
throws Exception |
{ |
try { |
return Integer.parseInt(s.substring(prefix.length())); |
} |
catch(Exception ex) { |
log.error(errorMsg); |
throw ex; |
} |
} |
|
private long parseLong(String s, String prefix, String errorMsg) |
throws Exception |
{ |
try { |
return Long.parseLong(s.substring(prefix.length())); |
} |
catch(Exception ex) { |
log.error(errorMsg); |
throw ex; |
} |
} |
|
private boolean parseParams(String[] args) |
throws Exception |
{ |
try { |
for(int i = 0; i< args.length; i++) { |
String s = args[i]; |
if(s.startsWith("thr=")) { |
threadNumber = parseInt(s, "thr=", |
"Cannot get number of threads from " + s); |
} |
else if(s.startsWith("oneFile=")) { |
String s2 = s.substring("oneFile=".length()); |
if(s2.equals("true")) |
oneFile = true; |
else if(s2.equals("false")) |
oneFile = false; |
else { |
log.error("Cannot get oneFile parameter from " + s); |
throw new IllegalArgumentException("must be true or false"); |
} |
} |
else if(s.startsWith("seed=")) { |
seed = parseLong(s, "seed=", |
"Cannot get random seed from " + s); |
} |
else if(s.startsWith("length=")) { |
length = parseLong(s, "length=", |
"Cannot get length of file from " + s); |
} |
else if(s.startsWith("buf=")) { |
bufLength = parseInt(s, "buf=", |
"Cannot get length of buffer from " + s); |
} |
else if(s.startsWith("rwrite=")) { |
randomWrites = parseInt(s, "rwrite=", |
"Cannot get number of random writes from " + s); |
} |
else if(s.startsWith("rread=")) { |
randomReads = parseInt(s, "rread=", |
"Cannot get number of random reads from " + s); |
} |
else if(s.startsWith("sleepMin=")) { |
sleepMin = parseInt(s, "sleepMin=", |
"Cannot get min sleep from " + s); |
} |
else if(s.startsWith("sleepMax=")) { |
sleepMax = parseInt(s, "sleepMax=", |
"Cannot get max sleep from " + s); |
} |
else { |
log.error("Unrecognized option " + s); |
throw new IllegalArgumentException("unrecignized"); |
} |
} |
|
return true; |
} |
catch(Exception ex) { |
return false; |
} |
} |
|
private void run() |
throws Exception |
{ |
log.info("start " + threadNumber + "/" + oneFile + "/" + seed |
+ " " + length + "/" + bufLength + " " + randomWrites + "/" + randomReads |
+ " " + sleepMin + "/" + sleepMax); |
|
List threads = new ArrayList(); |
List workers = new ArrayList(); |
|
DiskThread.prepare(threadNumber); |
for(int i = 0; i < threadNumber; i++) { |
log.debug("create thread " + i); |
|
DiskThread dt = new DiskThread(); |
workers.add(dt); |
|
dt.setFileName("file" + dt.getId() + ".txt"); |
dt.setSeed(seed); |
dt.setCreateFile(!oneFile); |
dt.setLength(length); |
dt.setBufLength(bufLength); |
dt.setRandomWrites(randomWrites); |
dt.setRandomReads(randomReads); |
dt.setSleepMin(sleepMin); |
dt.setSleepMax(sleepMax); |
|
Thread t = new Thread(dt); |
threads.add(t); |
t.start(); |
} |
|
// wait for all |
for(int i = 0; i < threads.size(); i++) { |
log.debug("wait for thread " + i); |
|
Thread t = (Thread)threads.get(i); |
try { |
t.join(); |
} |
catch(InterruptedException ex) { |
} |
} |
|
// statistics |
int successes = 0; |
int fails = 0; |
long sequentialWriteStart = Long.MAX_VALUE; |
long sequentialWriteStop = 0; |
long sequentialReadStart = Long.MAX_VALUE; |
long sequentialReadStop = 0; |
long randomWriteStart = Long.MAX_VALUE; |
long randomWriteStop = 0; |
long randomReadStart = Long.MAX_VALUE; |
long randomReadStop = 0; |
for(int i = 0; i < workers.size(); i++) { |
DiskThread dt = (DiskThread)workers.get(i); |
ResultInfo r = dt.getResult(); |
if(r.success) { |
successes++; |
if(sequentialWriteStart > r.sequentialWriteStart) |
sequentialWriteStart = r.sequentialWriteStart; |
if(sequentialWriteStop < r.sequentialWriteStop) |
sequentialWriteStop = r.sequentialWriteStop; |
if(sequentialReadStart > r.sequentialReadStart) |
sequentialReadStart = r.sequentialReadStart; |
if(sequentialReadStop < r.sequentialReadStop) |
sequentialReadStop = r.sequentialReadStop; |
if(randomWriteStart > r.randomWriteStart) |
randomWriteStart = r.randomWriteStart; |
if(randomWriteStop < r.randomWriteStop) |
randomWriteStop = r.randomWriteStop; |
if(randomReadStart > r.randomReadStart) |
randomReadStart = r.randomReadStart; |
if(randomReadStop < r.randomReadStop) |
randomReadStop = r.randomReadStop; |
} |
else { |
fails++; |
} |
} |
|
log.info("done " |
+ "sequential write: " + (sequentialWriteStop - sequentialWriteStart) + "ms = " |
+ Utils.calcSpeed(length * successes, sequentialWriteStop - sequentialWriteStart) |
+ "; sequential read: " + (sequentialReadStop - sequentialReadStart) + "ms = " |
+ Utils.calcSpeed(length * successes, sequentialReadStop - sequentialReadStart) |
+ "; random write: " + (randomWriteStop - randomWriteStart) + "ms = " |
+ Utils.calcSpeed(bufLength * randomWrites * successes, |
randomWriteStop - randomWriteStart) |
+ "; random read: " + (randomReadStop - randomReadStart) + "ms = " |
+ Utils.calcSpeed(bufLength * randomReads * successes, |
randomReadStop - randomReadStart) |
+ "; " + fails + " failed"); |
} |
} |
|
class ResultInfo |
{ |
public boolean success = false; |
public long startTime; |
public long stopTime; |
public Exception exception; |
public long sequentialWriteStart; |
public long sequentialWriteStop; |
public long sequentialReadStart; |
public long sequentialReadStop; |
public long randomWriteStart; |
public long randomWriteStop; |
public long randomReadStart; |
public long randomReadStop; |
public long writeSleep; |
public long readSleep; |
} |
|
class DiskThread implements Runnable |
{ |
private static int threadCount = 0; |
private static Log log = LogFactory.getLog(DiskThread.class); |
private static CyclicBarrier barrier; |
|
public static void prepare(int threadNumber) |
{ |
barrier = new CyclicBarrier(threadNumber); |
} |
|
private int id; |
private String fileName; |
private long seed = 0; |
private boolean createFile = true; |
private long length = 1024 * 1024; // 1M |
private int bufLength = 8 * 1024; // 8K |
private int randomWrites; |
private int randomReads; |
private int sleepMin = 0; |
private int sleepMax = 0; |
|
private ResultInfo result = new ResultInfo(); |
|
public DiskThread() |
{ |
id = threadCount++; |
} |
|
public int getId() |
{ |
return id; |
} |
|
public String getFileName() |
{ |
return fileName; |
} |
|
public void setFileName(String fileName) |
{ |
this.fileName = fileName; |
} |
|
public boolean getCreateFile() |
{ |
return createFile; |
} |
|
public void setCreateFile(boolean createFile) |
{ |
this.createFile = createFile; |
} |
|
public long getSeed() |
{ |
return seed; |
} |
|
public void setSeed(long seed) |
{ |
this.seed = seed; |
} |
|
public long getLength() |
{ |
return length; |
} |
|
public void setLength(long length) |
{ |
this.length = length; |
} |
|
public int getBufLength() |
{ |
return bufLength; |
} |
|
public void setBufLength(int bufLength) |
{ |
this.bufLength = bufLength; |
} |
|
public int getRandomWrites() |
{ |
return randomWrites; |
} |
|
public void setRandomWrites(int randomWrites) |
{ |
this.randomWrites = randomWrites; |
} |
|
public int getRandomReads() |
{ |
return randomReads; |
} |
|
public void setRandomReads(int randomReads) |
{ |
this.randomReads = randomReads; |
} |
|
public int getSleepMin() |
{ |
return sleepMin; |
} |
|
public void setSleepMin(int sleepMin) |
{ |
this.sleepMin = sleepMin; |
} |
|
public int getSleepMax() |
{ |
return sleepMax; |
} |
|
public void setSleepMax(int sleepMax) |
{ |
this.sleepMax = sleepMax; |
} |
|
public ResultInfo getResult() |
{ |
return result; |
} |
|
private long sleep() |
throws InterruptedException |
{ |
if(sleepMax <= 0) return 0; |
|
long sleep = (long)(Math.random() * (sleepMax - sleepMin)) + sleepMin; |
log.trace(Integer.toString(id) + ": sleep for " + sleep + "ms"); |
Thread.sleep(sleep); |
|
return sleep; |
} |
|
public void run() |
{ |
log.debug(Integer.toString(id) + ": start"); |
result.startTime = System.currentTimeMillis(); |
result.writeSleep = 0; |
result.readSleep = 0; |
|
// try to delete the test file if it exists and abort execution on exception |
if(createFile) { |
try { |
if((new File(fileName)).delete()) |
log.debug(Integer.toString(id) + ": test file " |
+ fileName + " deleted"); |
} |
catch(Exception ex) { |
result.success = false; |
result.stopTime = System.currentTimeMillis(); |
result.exception = ex; |
log.warn(Integer.toString(id) |
+ ": cannot delete the test file, execution aborted", ex); |
return; |
} |
} |
|
try { |
RandomAccessFile file = new RandomAccessFile(fileName, "rw"); |
Random random = (seed > 0) ? new Random(seed) : new Random(); |
byte[] buf = new byte[bufLength]; |
|
random.nextBytes(buf); |
|
// sequential write |
if(createFile) { |
result.sequentialWriteStart = System.currentTimeMillis(); |
for(int j = 0; j < length / bufLength; j++) { |
file.write(buf); |
} |
result.sequentialWriteStop = System.currentTimeMillis(); |
log.info("sequential write: " |
+ (result.sequentialWriteStop - result.sequentialWriteStart) + "ms = " |
+ Utils.calcSpeed(length, |
result.sequentialWriteStop - result.sequentialWriteStart)); |
|
barrier.await(); |
} |
|
// sequential read |
file.seek(0); |
result.sequentialReadStart = System.currentTimeMillis(); |
while(file.read(buf) > 0); |
result.sequentialReadStop = System.currentTimeMillis(); |
log.info("sequential read: " |
+ (result.sequentialReadStop - result.sequentialReadStart) + "ms = " |
+ Utils.calcSpeed(length, |
result.sequentialReadStop - result.sequentialReadStart)); |
barrier.await(); |
|
// random write |
result.randomWriteStart = System.currentTimeMillis(); |
for(int j = 0; j < randomWrites; j++) { |
result.writeSleep += sleep(); |
file.seek((long)(random.nextDouble() * (length - bufLength))); |
file.write(buf); |
} |
result.randomWriteStop = System.currentTimeMillis(); |
log.info("random write: " + (result.randomWriteStop - result.randomWriteStart) + "ms = " |
+ Utils.calcSpeed(bufLength * randomWrites, |
result.randomWriteStop - result.randomWriteStart)); |
barrier.await(); |
|
// random read |
result.randomReadStart = System.currentTimeMillis(); |
for(int j = 0; j < randomReads; j++) { |
result.readSleep += sleep(); |
file.seek((long)(random.nextDouble() * length)); |
file.read(buf); |
} |
result.randomReadStop = System.currentTimeMillis(); |
log.info("random read: " + (result.randomReadStop - result.randomReadStart) + "ms = " |
+ Utils.calcSpeed(bufLength * randomReads, |
result.randomReadStop - result.randomReadStart)); |
|
result.success = true; |
result.stopTime = System.currentTimeMillis(); |
} |
catch(Exception ex) { |
result.success = false; |
result.stopTime = System.currentTimeMillis(); |
result.exception = ex; |
log.warn(Integer.toString(id), ex); |
} |
finally { |
if(createFile) { |
try { |
(new File(fileName)).delete(); |
} |
catch(Exception ex) { |
log.warn(Integer.toString(id) + ": cannot delete the test file", ex); |
} |
} |
} |
|
log.debug(Integer.toString(id) + ": done" + (result.success ? "" : " with error")); |
} |
} |
|
class Utils |
{ |
private static final String[] units = {"", "K", "M", "G", "T"}; |
|
public static String calcSpeed(long amount, long time) |
{ |
if(time == 0) return "-"; |
|
double speed = (double)amount / time * 1000; |
int power = 0; |
|
while(speed >= 10000 && power < units.length) { |
power++; |
speed /= 1024; |
} |
|
return (new DecimalFormat("0.00")).format(speed) + units[power] + "B/s"; |
} |
} |
|
// replacement of java.util.concurrent.CyclicBarrier from Java 1.5 |
class CyclicBarrier |
{ |
private int parties; |
private int waiting = 0; |
private Runnable barrierAction = null; |
|
public CyclicBarrier(int parties) |
{ |
this(parties, null); |
} |
|
public CyclicBarrier(int parties, Runnable barrierAction) |
{ |
this.parties = parties; |
this.barrierAction = barrierAction; |
} |
|
public int await() |
throws InterruptedException |
{ |
synchronized(this) { |
int n = parties - waiting - 1; |
|
waiting++; |
if(waiting == parties) { |
if(barrierAction != null) |
barrierAction.run(); |
|
waiting = 0; |
notifyAll(); |
} |
else { |
wait(); |
} |
|
return n; |
} |
} |
|
public void reset() |
{ |
synchronized(this) { |
waiting = 0; |
notifyAll(); |
} |
} |
|
public int getNumberWaiting() |
{ |
return waiting; |
} |
|
public int getParties() |
{ |
return parties; |
} |
} |
|