/**
* Copyright 2010 The Apache Software Foundation
*
* Licensed to the Apache Software Foundation (ASF) 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 org.apache.hadoop.hbase.regionserver.wal;
import java.io.IOException;
import java.util.Random;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.hadoop.hbase.*;
import org.apache.log4j.Level;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.ipc.HBaseRPC;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@Category(MediumTests.class)
public class TestHLogBench extends Configured implements Tool {
static final Log LOG = LogFactory.getLog(TestHLogBench.class);
private static final Random r = new Random();
private static final byte [] FAMILY = Bytes.toBytes("hlogbenchFamily");
// accumulate time here
private static int totalTime = 0;
private static Object lock = new Object();
// the file system where to create the Hlog file
protected FileSystem fs;
// the number of threads and the number of iterations per thread
private int numThreads = 300;
private int numIterationsPerThread = 10000;
private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private Path regionRootDir =TEST_UTIL.getDataTestDir("TestHLogBench") ;
private boolean appendNoSync = false;
public TestHLogBench() {
this(null);
}
private TestHLogBench(Configuration conf) {
super(conf);
fs = null;
}
/**
* Initialize file system object
*/
public void init() throws IOException {
getConf().setQuietMode(true);
if (this.fs == null) {
this.fs = FileSystem.get(getConf());
}
}
/**
* Close down file system
*/
public void close() throws IOException {
if (fs != null) {
fs.close();
fs = null;
}
}
/**
* The main run method of TestHLogBench
*/
public int run(String argv[]) throws Exception {
int exitCode = -1;
int i = 0;
// verify that we have enough command line parameters
if (argv.length < 4) {
printUsage("");
return exitCode;
}
// initialize LogBench
try {
init();
} catch (HBaseRPC.VersionMismatch v) {
LOG.warn("Version Mismatch between client and server" +
"... command aborted.");
return exitCode;
} catch (IOException e) {
LOG.warn("Bad connection to FS. command aborted.");
return exitCode;
}
try {
for (; i < argv.length; i++) {
if ("-numThreads".equals(argv[i])) {
i++;
this.numThreads = Integer.parseInt(argv[i]);
} else if ("-numIterationsPerThread".equals(argv[i])) {
i++;
this.numIterationsPerThread = Integer.parseInt(argv[i]);
} else if ("-path".equals(argv[i])) {
// get an absolute path using the default file system
i++;
this.regionRootDir = new Path(argv[i]);
this.regionRootDir = regionRootDir.makeQualified(this.fs);
} else if ("-nosync".equals(argv[i])) {
this.appendNoSync = true;
} else {
printUsage(argv[i]);
return exitCode;
}
}
} catch (NumberFormatException nfe) {
LOG.warn("Illegal numThreads or numIterationsPerThread, " +
" a positive integer expected");
throw nfe;
}
go();
return 0;
}
private void go() throws IOException, InterruptedException {
long start = System.currentTimeMillis();
log("Running TestHLogBench with " + numThreads + " threads each doing " +
numIterationsPerThread + " HLog appends " +
(appendNoSync ? "nosync" : "sync") +
" at rootDir " + regionRootDir);
// Mock an HRegion
byte [] tableName = Bytes.toBytes("table");
byte [][] familyNames = new byte [][] { FAMILY };
HTableDescriptor htd = new HTableDescriptor();
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("f1")));
HRegion region = mockRegion(tableName, familyNames, regionRootDir);
HLog hlog = region.getLog();
// Spin up N threads to each perform M log operations
LogWriter [] incrementors = new LogWriter[numThreads];
for (int i=0; i<numThreads; i++) {
incrementors[i] = new LogWriter(region, tableName, hlog, i,
numIterationsPerThread,
appendNoSync);
incrementors[i].start();
}
// Wait for threads to finish
for (int i=0; i<numThreads; i++) {
//log("Waiting for #" + i + " to finish");
incrementors[i].join();
}
// Output statistics
long totalOps = numThreads * numIterationsPerThread;
log("Operations per second " + ((totalOps * 1000L)/totalTime));
log("Average latency in ms " + ((totalTime * 1000L)/totalOps));
}
/**
* Displays format of commands.
*/
private static void printUsage(String cmd) {
String prefix = "Usage: java " + TestHLogBench.class.getSimpleName();
System.err.println(prefix + cmd +
" [-numThreads <number>] " +
" [-numIterationsPerThread <number>] " +
" [-path <path where region's root directory is created>]" +
" [-nosync]");
}
/**
* A thread that writes data to an HLog
*/
public static class LogWriter extends Thread {
private final HRegion region;
private final int threadNumber;
private final int numIncrements;
private final HLog hlog;
private boolean appendNoSync;
private byte[] tableName;
private int count;
public LogWriter(HRegion region, byte[] tableName,
HLog log, int threadNumber,
int numIncrements, boolean appendNoSync) {
this.region = region;
this.threadNumber = threadNumber;
this.numIncrements = numIncrements;
this.hlog = log;
this.count = 0;
this.appendNoSync = appendNoSync;
this.tableName = tableName;
setDaemon(true);
//log("LogWriter[" + threadNumber + "] instantiated");
}
@Override
public void run() {
long now = System.currentTimeMillis();
byte [] key = Bytes.toBytes("thisisakey");
KeyValue kv = new KeyValue(key, now);
WALEdit walEdit = new WALEdit();
walEdit.add(kv);
HRegionInfo hri = region.getRegionInfo();
HTableDescriptor htd = new HTableDescriptor();
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("f1")));
boolean isMetaRegion = false;
long start = System.currentTimeMillis();
for (int i=0; i<numIncrements; i++) {
try {
if (appendNoSync) {
hlog.appendNoSync(hri, tableName, walEdit,
HConstants.DEFAULT_CLUSTER_ID, now, htd);
} else {
hlog.append(hri, tableName, walEdit, now, htd);
}
} catch (IOException e) {
log("Fatal exception: " + e);
e.printStackTrace();
}
count++;
}
long tot = System.currentTimeMillis() - start;
synchronized (lock) {
totalTime += tot; // update global statistics
}
}
}
private static void log(String string) {
LOG.info(string);
}
private byte[][] makeBytes(int numArrays, int arraySize) {
byte [][] bytes = new byte[numArrays][];
for (int i=0; i<numArrays; i++) {
bytes[i] = new byte[arraySize];
r.nextBytes(bytes[i]);
}
return bytes;
}
/**
* Create a dummy region
*/
private HRegion mockRegion(byte[] tableName, byte[][] familyNames,
Path rootDir) throws IOException {
HBaseTestingUtility htu = new HBaseTestingUtility();
Configuration conf = htu.getConfiguration();
conf.setBoolean("hbase.rs.cacheblocksonwrite", true);
conf.setBoolean("hbase.hregion.use.incrementnew", true);
conf.setBoolean("dfs.support.append", true);
FileSystem fs = FileSystem.get(conf);
int numQualifiers = 10;
byte [][] qualifiers = new byte [numQualifiers][];
for (int i=0; i<numQualifiers; i++) qualifiers[i] = Bytes.toBytes("qf" + i);
int numRows = 10;
byte [][] rows = new byte [numRows][];
for (int i=0; i<numRows; i++) rows[i] = Bytes.toBytes("r" + i);
// switch off debug message from Region server
((Log4JLogger)HRegion.LOG).getLogger().setLevel(Level.WARN);
HTableDescriptor htd = new HTableDescriptor(tableName);
for (byte [] family : familyNames)
htd.addFamily(new HColumnDescriptor(family));
HRegionInfo hri = new HRegionInfo(tableName, Bytes.toBytes(0L),
Bytes.toBytes(0xffffffffL));
if (fs.exists(rootDir)) {
if (!fs.delete(rootDir, true)) {
throw new IOException("Failed delete of " + rootDir);
}
}
return HRegion.createHRegion(hri, rootDir, conf, htd);
}
@Test
public void testLogPerformance() throws Exception {
TestHLogBench bench = new TestHLogBench();
int res;
String[] argv = new String[7];
argv[0] = "-numThreads";
argv[1] = Integer.toString(100);
argv[2] = "-numIterationsPerThread";
argv[3] = Integer.toString(1000);
argv[4] = "-path";
argv[5] = TEST_UTIL.getDataTestDir() + "/HlogPerformance";
argv[6] = "-nosync";
try {
res = ToolRunner.run(bench, argv);
} finally {
bench.close();
}
}
public static void main(String[] argv) throws Exception {
TestHLogBench bench = new TestHLogBench();
int res;
try {
res = ToolRunner.run(bench, argv);
} finally {
bench.close();
}
System.exit(res);
}
@org.junit.Rule
public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
}