/**
* Copyright 2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit.stress.unit;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import com.persistit.Exchange;
import com.persistit.PersistitUnitTestCase;
import com.persistit.Transaction;
import com.persistit.Transaction.CommitPolicy;
import com.persistit.exception.RollbackException;
import com.persistit.unit.UnitTestProperties;
import com.persistit.util.ArgParser;
/**
* Synthetic benchmark of Commit durability processing. This class tests
* performance of the transaction commit method under the SOFT, HARD and GROUP
* policies for varying numbers of threads. Transaction deliberately (a) do not
* read any pages and (b) create a limited numbers of dirty pages. The purpose
* is to isolate and study just the I/O required to commit transactions.
*
* @author peter
*
*/
public class CommitBench extends PersistitUnitTestCase {
final static Pattern PATH_PATTERN = Pattern.compile("(.+)\\.(\\d{12})");
/*
* Want about 1000 pages worth of data (no evictions). Each record is about
* 50 bytes, so 1000 * 16384 * 60% / 50 ~= 200000.
*/
private final int RECORDS = 200000;
private final int RECORDS_PER_TXN = 10;
private final String[] ARG_TEMPLATE = new String[] { "threads|int:1:1:1000|Number of threads",
"duration|int:10:10:86400|Duration of test in seconds",
"policy|String:HARD|Commit policy: SOFT, HARD or GROUP", "datapath|String|Datapath property",
"_flag|P|Reuse journal file" };
volatile long stopTime;
final ArgParser ap;
AtomicInteger commitCount = new AtomicInteger();
AtomicInteger rollbackCount = new AtomicInteger();
CommitBench(final String[] args) {
ap = new ArgParser("CommitBench", args, ARG_TEMPLATE).strict();
}
@Override
protected Properties getProperties(final boolean cleanup) {
final Properties p = UnitTestProperties.getBiggerProperties(false);
if (ap.isSpecified("datapath")) {
p.setProperty("datapath", ap.getStringValue("datapath"));
}
/*
* Custom data directory cleanup - leaving the journal file behind if
*/
final String path = p.getProperty("datapath");
final File dir = new File(path);
assert dir.isDirectory() : "Data path does not specify a directory: " + path;
final File[] files = dir.listFiles();
for (final File file : files) {
if (ap.isFlag('P') && PATH_PATTERN.matcher(file.getName()).matches()) {
try {
/*
* Damage the file so that there's no keystone checkpoint
*/
final RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.seek(0);
raf.write(new byte[256]);
raf.close();
} catch (final IOException e) {
throw new RuntimeException(e);
}
} else {
if (file.isDirectory()) {
UnitTestProperties.cleanUpDirectory(file);
} else {
file.delete();
}
}
}
return p;
}
public void bench() throws Exception {
final int threadCount = ap.getIntValue("threads");
final int duration = ap.getIntValue("duration");
final String policy = ap.getStringValue("policy");
_persistit.setDefaultTransactionCommitPolicy(CommitPolicy.valueOf(policy));
final TransactionRun[] runs = new TransactionRun[threadCount];
final Thread[] threads = new Thread[threadCount];
for (int index = 0; index < threadCount; index++) {
runs[index] = new TransactionRun(index, threadCount);
threads[index] = new Thread(runs[index]);
}
System.out.printf("Starting %,d threads for %,d seconds with policy %s\n", threadCount, duration, policy);
final long startTime = System.currentTimeMillis();
stopTime = startTime + (duration * 1000);
for (int index = 0; index < threadCount; index++) {
threads[index].start();
}
for (int index = 0; index < threadCount; index++) {
threads[index].join();
}
final long elapsed = System.currentTimeMillis() - startTime;
System.out.printf("Joined %,d threads after %,d seconds\n", threadCount, (elapsed / 1000));
final long committed = commitCount.get();
final long rate = (committed * 60000) / elapsed;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (final TransactionRun run : runs) {
min = Math.min(min, run.countPerThread);
max = Math.max(max, run.countPerThread);
}
System.out.printf("%,d commited at rate %,d per minute with maximum difference %,d; %,d rollbacks\n",
committed, rate, max - min, rollbackCount.get());
}
private class TransactionRun implements Runnable {
final int threadId;
final int threadCount;
Transaction txn;
Exchange exchange;
final Random random = new Random();
int countPerThread = 0;
TransactionRun(final int index, final int count) {
threadId = index;
threadCount = count;
}
@Override
public void run() {
try {
if (txn == null) {
exchange = _persistit.getExchange("persistit", "CommitBench", true);
txn = exchange.getTransaction();
}
while (System.currentTimeMillis() < stopTime) {
try {
txn.begin();
final int key = (random.nextInt(RECORDS / threadCount / RECORDS_PER_TXN) * threadCount + threadId)
* RECORDS_PER_TXN;
exchange.getValue().put(
RED_FOX + " " + Thread.currentThread().getName() + " " + ++countPerThread);
for (int index = 0; index < RECORDS_PER_TXN; index++) {
exchange.to((key + index) % RECORDS).store();
}
txn.commit();
commitCount.incrementAndGet();
} catch (final RollbackException e) {
rollbackCount.incrementAndGet();
} finally {
txn.end();
}
}
} catch (final Exception e) {
e.printStackTrace();
}
}
}
public static void main(final String[] args) throws Exception {
final CommitBench bench = new CommitBench(args);
try {
bench.setUp();
bench.bench();
} finally {
bench.tearDown();
}
}
}