/**
* Licensed to the zk1931 under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.github.zk1931.jzab;
// Need to import logback here to set the root log level to INFO.
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import com.github.zk1931.jzab.proto.ZabMessage.Message;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.LoggerFactory;
/**
* Tests the performance of SyncProposalProcessor with different implementations
* of the Log interfaces.
*/
@RunWith(Parameterized.class)
public class SyncProposalProcessorTest extends TestBase {
// Don't log DEBUG since this test is for performance benchmark.
private static final Logger LOG =
(Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
static {
LOG.setLevel(Level.INFO);
}
Class<Log> logClass;
String fileName;
@Parameterized.Parameters
public static Collection<Object[]> instancesToTest() throws Exception {
return Arrays.asList(
new Object[][] {
{SimpleLog.class, "transaction.log"},
{RollingLog.class, ""}
});
}
public SyncProposalProcessorTest(Class<Log> logClass, String fileName) {
LOG.debug("Testting impelementation of {}", logClass.getName());
this.logClass = logClass;
this.fileName = fileName;
}
Log getLog(String subdir) throws Exception {
File fSub = new File(getDirectory(), subdir);
fSub.mkdir();
File f = new File(fSub, this.fileName);
if (logClass == (Class<?>)RollingLog.class) {
// For testing purpose, set the rolling size be small.
return logClass.getConstructor(File.class, Long.TYPE)
.newInstance(f, 10 * 1024 * 1024);
} else {
return logClass.getConstructor(File.class).newInstance(f);
}
}
public void benchmark(int epoch, int transactionSize, int numTransactions,
int batchSize)
throws Exception {
LOG.info("Starting the test for {}", this.logClass.getName());
LOG.info("epoch = {}", epoch);
LOG.info("txn size = {}", transactionSize);
LOG.info("num_ops = {}", numTransactions);
LOG.info("batch_size = {}", batchSize);
final String leader = getUniqueHostPort();
int count = numTransactions;
final Zxid expectedZxid = new Zxid(epoch, count - 1);
final CountDownLatch latch = new CountDownLatch(1);
final int ackCount = 0;
class TestReceiver implements Transport.Receiver {
int ackCount = 0;
@Override
public void onReceived(String source, Message msg) {
try {
Zxid zxid = MessageBuilder.fromProtoZxid(msg.getAck().getZxid());
ackCount++;
if (zxid.equals(expectedZxid)) {
latch.countDown();
} else {
LOG.debug("received {} {}", zxid, expectedZxid);
}
} catch (Exception ex) {
Assert.fail("Unexpected exception: " + ex.getMessage());
}
}
@Override
public void onDisconnected(String source) {
}
}
Log log = getLog(String.format("%d_%d_%d_%d", epoch, transactionSize,
numTransactions, batchSize));
PersistentState persistence =
new PersistentState(getDirectory(), log);
TestReceiver receiver = new TestReceiver();
Transport transport = new NettyTransport(leader, receiver, getDirectory());
ClusterConfiguration cnf =
new ClusterConfiguration(new Zxid(0, 0), new ArrayList<String>(), "");
persistence.setLastSeenConfig(cnf);
SyncProposalProcessor processor =
new SyncProposalProcessor(persistence, transport, batchSize);
long startTime = System.nanoTime();
String message = new String(new char[transactionSize]).replace('\0', 'a');
for (int i = 0; i < count; i++) {
Transaction txn = new Transaction(new Zxid(epoch, i),
ByteBuffer.wrap(message.getBytes()));
Message proposal = MessageBuilder.buildProposal(txn);
MessageTuple request = new MessageTuple(leader, proposal);
processor.processRequest(request);
}
latch.await();
double endTimeSec = (double) (System.nanoTime() - startTime) /
(1000 * 1000 * 1000);
LOG.info("runtime = {} seconds", endTimeSec);
LOG.info("throughput = {} ops/sec", (int)(numTransactions / endTimeSec));
LOG.info("throughput = {} MB/sec",
numTransactions * transactionSize / endTimeSec / 1024 / 1024);
LOG.info("ack count = {}", receiver.ackCount);
processor.shutdown();
transport.shutdown();
}
@Test
public void testPerformance() throws Exception {
benchmark(0, 1024, 1000, 1);
benchmark(1, 128, 100000, 1000);
benchmark(2, 1024, 100000, 1000);
}
}