View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.IOException;
24  import java.io.PrintStream;
25  import java.io.File;
26  import java.text.SimpleDateFormat;
27  import java.util.ArrayList;
28  import java.util.Date;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Random;
32  import java.util.TreeMap;
33  import java.util.Arrays;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  import java.lang.reflect.Constructor;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.fs.FSDataInputStream;
42  import org.apache.hadoop.fs.FileStatus;
43  import org.apache.hadoop.fs.FileSystem;
44  import org.apache.hadoop.fs.Path;
45  import org.apache.hadoop.hbase.client.Get;
46  import org.apache.hadoop.hbase.client.HBaseAdmin;
47  import org.apache.hadoop.hbase.client.HTable;
48  import org.apache.hadoop.hbase.client.Put;
49  import org.apache.hadoop.hbase.client.Result;
50  import org.apache.hadoop.hbase.client.ResultScanner;
51  import org.apache.hadoop.hbase.client.Scan;
52  import org.apache.hadoop.hbase.client.Durability;
53  import org.apache.hadoop.hbase.filter.PageFilter;
54  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
55  import org.apache.hadoop.hbase.filter.Filter;
56  import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
57  import org.apache.hadoop.hbase.filter.CompareFilter;
58  import org.apache.hadoop.hbase.filter.BinaryComparator;
59  import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
60  import org.apache.hadoop.hbase.io.compress.Compression;
61  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
62  import org.apache.hadoop.hbase.util.Bytes;
63  import org.apache.hadoop.hbase.util.FSUtils;
64  import org.apache.hadoop.hbase.util.Hash;
65  import org.apache.hadoop.hbase.util.MurmurHash;
66  import org.apache.hadoop.hbase.util.Pair;
67  import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
68  import org.apache.hadoop.hdfs.MiniDFSCluster;
69  import org.apache.hadoop.conf.Configured;
70  import org.apache.hadoop.io.LongWritable;
71  import org.apache.hadoop.io.NullWritable;
72  import org.apache.hadoop.io.Text;
73  import org.apache.hadoop.io.Writable;
74  import org.apache.hadoop.mapreduce.InputSplit;
75  import org.apache.hadoop.mapreduce.Job;
76  import org.apache.hadoop.mapreduce.JobContext;
77  import org.apache.hadoop.mapreduce.Mapper;
78  import org.apache.hadoop.mapreduce.RecordReader;
79  import org.apache.hadoop.mapreduce.TaskAttemptContext;
80  import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
81  import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
82  import org.apache.hadoop.mapreduce.lib.reduce.LongSumReducer;
83  import org.apache.hadoop.util.Tool;
84  import org.apache.hadoop.util.ToolRunner;
85  import org.apache.hadoop.util.LineReader;
86  
87  
88  /**
89   * Script used evaluating HBase performance and scalability.  Runs a HBase
90   * client that steps through one of a set of hardcoded tests or 'experiments'
91   * (e.g. a random reads test, a random writes test, etc.). Pass on the
92   * command-line which test to run and how many clients are participating in
93   * this experiment. Run <code>java PerformanceEvaluation --help</code> to
94   * obtain usage.
95   *
96   * <p>This class sets up and runs the evaluation programs described in
97   * Section 7, <i>Performance Evaluation</i>, of the <a
98   * href="http://labs.google.com/papers/bigtable.html">Bigtable</a>
99   * paper, pages 8-10.
100  *
101  * <p>If number of clients > 1, we start up a MapReduce job. Each map task
102  * runs an individual client. Each client does about 1GB of data.
103  */
104 public class PerformanceEvaluation extends Configured implements Tool {
105   protected static final Log LOG = LogFactory.getLog(PerformanceEvaluation.class.getName());
106 
107   private static final int DEFAULT_ROW_PREFIX_LENGTH = 16;
108   private static final int VALUE_LENGTH = 1000;
109   private static final int ONE_GB = 1024 * 1024 * 1000;
110   private static final int ROWS_PER_GB = ONE_GB / VALUE_LENGTH;
111 
112   public static final byte[] COMPRESSION = Bytes.toBytes("NONE");
113   public static final TableName TABLE_NAME =
114       TableName.valueOf("TestTable");
115   public static final byte[] FAMILY_NAME = Bytes.toBytes("info");
116   public static final byte[] QUALIFIER_NAME = Bytes.toBytes("data");
117 
118   protected HTableDescriptor TABLE_DESCRIPTOR;
119 
120   protected Map<String, CmdDescriptor> commands = new TreeMap<String, CmdDescriptor>();
121 
122   private boolean miniCluster = false;
123   private boolean nomapred = false;
124   private int rowPrefixLength = DEFAULT_ROW_PREFIX_LENGTH;
125   private int N = 1;
126   private int R = ROWS_PER_GB;
127   private TableName tableName = TABLE_NAME;
128   private Compression.Algorithm compression = Compression.Algorithm.NONE;
129   private DataBlockEncoding blockEncoding = DataBlockEncoding.NONE;
130   private boolean flushCommits = true;
131   private boolean writeToWAL = true;
132   private boolean inMemoryCF = false;
133   private int presplitRegions = 0;
134 
135   private static final Path PERF_EVAL_DIR = new Path("performance_evaluation");
136   /**
137    * Regex to parse lines in input file passed to mapreduce task.
138    */
139   public static final Pattern LINE_PATTERN =
140     Pattern.compile("startRow=(\\d+),\\s+" +
141         "perClientRunRows=(\\d+),\\s+" +
142         "totalRows=(\\d+),\\s+" +
143         "clients=(\\d+),\\s+" +
144         "flushCommits=(\\w+),\\s+" +
145         "writeToWAL=(\\w+)");
146 
147   /**
148    * Enum for map metrics.  Keep it out here rather than inside in the Map
149    * inner-class so we can find associated properties.
150    */
151   protected static enum Counter {
152     /** elapsed time */
153     ELAPSED_TIME,
154     /** number of rows */
155     ROWS}
156 
157 
158   /**
159    * Constructor
160    * @param conf Configuration object
161    */
162   public PerformanceEvaluation(final Configuration conf) {
163     super(conf);
164 
165     addCommandDescriptor(RandomReadTest.class, "randomRead",
166         "Run random read test");
167     addCommandDescriptor(RandomSeekScanTest.class, "randomSeekScan",
168         "Run random seek and scan 100 test");
169     addCommandDescriptor(RandomScanWithRange10Test.class, "scanRange10",
170         "Run random seek scan with both start and stop row (max 10 rows)");
171     addCommandDescriptor(RandomScanWithRange100Test.class, "scanRange100",
172         "Run random seek scan with both start and stop row (max 100 rows)");
173     addCommandDescriptor(RandomScanWithRange1000Test.class, "scanRange1000",
174         "Run random seek scan with both start and stop row (max 1000 rows)");
175     addCommandDescriptor(RandomScanWithRange10000Test.class, "scanRange10000",
176         "Run random seek scan with both start and stop row (max 10000 rows)");
177     addCommandDescriptor(RandomWriteTest.class, "randomWrite",
178         "Run random write test");
179     addCommandDescriptor(SequentialReadTest.class, "sequentialRead",
180         "Run sequential read test");
181     addCommandDescriptor(SequentialWriteTest.class, "sequentialWrite",
182         "Run sequential write test");
183     addCommandDescriptor(ScanTest.class, "scan",
184         "Run scan test (read every row)");
185     addCommandDescriptor(FilteredScanTest.class, "filterScan",
186         "Run scan test using a filter to find a specific row based on it's value (make sure to use --rows=20)");
187   }
188 
189   protected void addCommandDescriptor(Class<? extends Test> cmdClass,
190       String name, String description) {
191     CmdDescriptor cmdDescriptor =
192       new CmdDescriptor(cmdClass, name, description);
193     commands.put(name, cmdDescriptor);
194   }
195 
196   /**
197    * Implementations can have their status set.
198    */
199   interface Status {
200     /**
201      * Sets status
202      * @param msg status message
203      * @throws IOException
204      */
205     void setStatus(final String msg) throws IOException;
206   }
207 
208   /**
209    *  This class works as the InputSplit of Performance Evaluation
210    *  MapReduce InputFormat, and the Record Value of RecordReader.
211    *  Each map task will only read one record from a PeInputSplit,
212    *  the record value is the PeInputSplit itself.
213    */
214   public static class PeInputSplit extends InputSplit implements Writable {
215     private int startRow = 0;
216     private int rows = 0;
217     private int totalRows = 0;
218     private int clients = 0;
219     private boolean flushCommits = false;
220     private boolean writeToWAL = true;
221 
222     public PeInputSplit() {
223       this.startRow = 0;
224       this.rows = 0;
225       this.totalRows = 0;
226       this.clients = 0;
227       this.flushCommits = false;
228       this.writeToWAL = true;
229     }
230 
231     public PeInputSplit(int startRow, int rows, int totalRows, int clients,
232         boolean flushCommits, boolean writeToWAL) {
233       this.startRow = startRow;
234       this.rows = rows;
235       this.totalRows = totalRows;
236       this.clients = clients;
237       this.flushCommits = flushCommits;
238       this.writeToWAL = writeToWAL;
239     }
240 
241     @Override
242     public void readFields(DataInput in) throws IOException {
243       this.startRow = in.readInt();
244       this.rows = in.readInt();
245       this.totalRows = in.readInt();
246       this.clients = in.readInt();
247       this.flushCommits = in.readBoolean();
248       this.writeToWAL = in.readBoolean();
249     }
250 
251     @Override
252     public void write(DataOutput out) throws IOException {
253       out.writeInt(startRow);
254       out.writeInt(rows);
255       out.writeInt(totalRows);
256       out.writeInt(clients);
257       out.writeBoolean(flushCommits);
258       out.writeBoolean(writeToWAL);
259     }
260 
261     @Override
262     public long getLength() throws IOException, InterruptedException {
263       return 0;
264     }
265 
266     @Override
267     public String[] getLocations() throws IOException, InterruptedException {
268       return new String[0];
269     }
270 
271     public int getStartRow() {
272       return startRow;
273     }
274 
275     public int getRows() {
276       return rows;
277     }
278 
279     public int getTotalRows() {
280       return totalRows;
281     }
282 
283     public int getClients() {
284       return clients;
285     }
286 
287     public boolean isFlushCommits() {
288       return flushCommits;
289     }
290 
291     public boolean isWriteToWAL() {
292       return writeToWAL;
293     }
294   }
295 
296   /**
297    *  InputFormat of Performance Evaluation MapReduce job.
298    *  It extends from FileInputFormat, want to use it's methods such as setInputPaths().
299    */
300   public static class PeInputFormat extends FileInputFormat<NullWritable, PeInputSplit> {
301 
302     @Override
303     public List<InputSplit> getSplits(JobContext job) throws IOException {
304       // generate splits
305       List<InputSplit> splitList = new ArrayList<InputSplit>();
306 
307       for (FileStatus file: listStatus(job)) {
308         if (file.isDir()) {
309           continue;
310         }
311         Path path = file.getPath();
312         FileSystem fs = path.getFileSystem(job.getConfiguration());
313         FSDataInputStream fileIn = fs.open(path);
314         LineReader in = new LineReader(fileIn, job.getConfiguration());
315         int lineLen = 0;
316         while(true) {
317           Text lineText = new Text();
318           lineLen = in.readLine(lineText);
319           if(lineLen <= 0) {
320           break;
321           }
322           Matcher m = LINE_PATTERN.matcher(lineText.toString());
323           if((m != null) && m.matches()) {
324             int startRow = Integer.parseInt(m.group(1));
325             int rows = Integer.parseInt(m.group(2));
326             int totalRows = Integer.parseInt(m.group(3));
327             int clients = Integer.parseInt(m.group(4));
328             boolean flushCommits = Boolean.parseBoolean(m.group(5));
329             boolean writeToWAL = Boolean.parseBoolean(m.group(6));
330 
331             LOG.debug("split["+ splitList.size() + "] " +
332                      " startRow=" + startRow +
333                      " rows=" + rows +
334                      " totalRows=" + totalRows +
335                      " clients=" + clients +
336                      " flushCommits=" + flushCommits +
337                      " writeToWAL=" + writeToWAL);
338 
339             PeInputSplit newSplit =
340               new PeInputSplit(startRow, rows, totalRows, clients,
341                 flushCommits, writeToWAL);
342             splitList.add(newSplit);
343           }
344         }
345         in.close();
346       }
347 
348       LOG.info("Total # of splits: " + splitList.size());
349       return splitList;
350     }
351 
352     @Override
353     public RecordReader<NullWritable, PeInputSplit> createRecordReader(InputSplit split,
354                             TaskAttemptContext context) {
355       return new PeRecordReader();
356     }
357 
358     public static class PeRecordReader extends RecordReader<NullWritable, PeInputSplit> {
359       private boolean readOver = false;
360       private PeInputSplit split = null;
361       private NullWritable key = null;
362       private PeInputSplit value = null;
363 
364       @Override
365       public void initialize(InputSplit split, TaskAttemptContext context)
366                   throws IOException, InterruptedException {
367         this.readOver = false;
368         this.split = (PeInputSplit)split;
369       }
370 
371       @Override
372       public boolean nextKeyValue() throws IOException, InterruptedException {
373         if(readOver) {
374           return false;
375         }
376 
377         key = NullWritable.get();
378         value = (PeInputSplit)split;
379 
380         readOver = true;
381         return true;
382       }
383 
384       @Override
385       public NullWritable getCurrentKey() throws IOException, InterruptedException {
386         return key;
387       }
388 
389       @Override
390       public PeInputSplit getCurrentValue() throws IOException, InterruptedException {
391         return value;
392       }
393 
394       @Override
395       public float getProgress() throws IOException, InterruptedException {
396         if(readOver) {
397           return 1.0f;
398         } else {
399           return 0.0f;
400         }
401       }
402 
403       @Override
404       public void close() throws IOException {
405         // do nothing
406       }
407     }
408   }
409 
410   /**
411    * MapReduce job that runs a performance evaluation client in each map task.
412    */
413   public static class EvaluationMapTask
414       extends Mapper<NullWritable, PeInputSplit, LongWritable, LongWritable> {
415 
416     /** configuration parameter name that contains the command */
417     public final static String CMD_KEY = "EvaluationMapTask.command";
418     /** configuration parameter name that contains the PE impl */
419     public static final String PE_KEY = "EvaluationMapTask.performanceEvalImpl";
420 
421     private Class<? extends Test> cmd;
422     private PerformanceEvaluation pe;
423 
424     @Override
425     protected void setup(Context context) throws IOException, InterruptedException {
426       this.cmd = forName(context.getConfiguration().get(CMD_KEY), Test.class);
427 
428       // this is required so that extensions of PE are instantiated within the
429       // map reduce task...
430       Class<? extends PerformanceEvaluation> peClass =
431           forName(context.getConfiguration().get(PE_KEY), PerformanceEvaluation.class);
432       try {
433         this.pe = peClass.getConstructor(Configuration.class)
434             .newInstance(context.getConfiguration());
435       } catch (Exception e) {
436         throw new IllegalStateException("Could not instantiate PE instance", e);
437       }
438     }
439 
440     private <Type> Class<? extends Type> forName(String className, Class<Type> type) {
441       Class<? extends Type> clazz = null;
442       try {
443         clazz = Class.forName(className).asSubclass(type);
444       } catch (ClassNotFoundException e) {
445         throw new IllegalStateException("Could not find class for name: " + className, e);
446       }
447       return clazz;
448     }
449 
450     protected void map(NullWritable key, PeInputSplit value, final Context context)
451            throws IOException, InterruptedException {
452 
453       Status status = new Status() {
454         public void setStatus(String msg) {
455            context.setStatus(msg);
456         }
457       };
458 
459       // Evaluation task
460       long elapsedTime = this.pe.runOneClient(this.cmd, value.getStartRow(),
461                                   value.getRows(), value.getTotalRows(),
462                                   value.isFlushCommits(), value.isWriteToWAL(),
463                                   status);
464       // Collect how much time the thing took. Report as map output and
465       // to the ELAPSED_TIME counter.
466       context.getCounter(Counter.ELAPSED_TIME).increment(elapsedTime);
467       context.getCounter(Counter.ROWS).increment(value.rows);
468       context.write(new LongWritable(value.startRow), new LongWritable(elapsedTime));
469       context.progress();
470     }
471   }
472 
473   /*
474    * If table does not already exist, create.
475    * @param c Client to use checking.
476    * @return True if we created the table.
477    * @throws IOException
478    */
479   private boolean checkTable(HBaseAdmin admin) throws IOException {
480     HTableDescriptor tableDescriptor = getTableDescriptor();
481     if (this.presplitRegions > 0) {
482       // presplit requested
483       if (admin.tableExists(tableDescriptor.getTableName())) {
484         admin.disableTable(tableDescriptor.getTableName());
485         admin.deleteTable(tableDescriptor.getTableName());
486       }
487 
488       byte[][] splits = getSplits();
489       for (int i=0; i < splits.length; i++) {
490         LOG.debug(" split " + i + ": " + Bytes.toStringBinary(splits[i]));
491       }
492       admin.createTable(tableDescriptor, splits);
493       LOG.info ("Table created with " + this.presplitRegions + " splits");
494     }
495     else {
496       boolean tableExists = admin.tableExists(tableDescriptor.getTableName());
497       if (!tableExists) {
498         admin.createTable(tableDescriptor);
499         LOG.info("Table " + tableDescriptor + " created");
500       }
501     }
502     boolean tableExists = admin.tableExists(tableDescriptor.getTableName());
503     return tableExists;
504   }
505 
506   protected HTableDescriptor getTableDescriptor() {
507     if (TABLE_DESCRIPTOR == null) {
508       TABLE_DESCRIPTOR = new HTableDescriptor(tableName);
509       HColumnDescriptor family = new HColumnDescriptor(FAMILY_NAME);
510       family.setDataBlockEncoding(blockEncoding);
511       family.setCompressionType(compression);
512       if (inMemoryCF) {
513         family.setInMemory(true);
514       }
515       TABLE_DESCRIPTOR.addFamily(family);
516     }
517     return TABLE_DESCRIPTOR;
518   }
519 
520   /**
521    * generates splits based on total number of rows and specified split regions
522    *
523    * @return splits : array of byte []
524    */
525   protected  byte[][] getSplits() {
526     if (this.presplitRegions == 0)
527       return new byte [0][];
528 
529     int numSplitPoints = presplitRegions - 1;
530     byte[][] splits = new byte[numSplitPoints][];
531     int jump = this.R  / this.presplitRegions;
532     for (int i=0; i < numSplitPoints; i++) {
533       int rowkey = jump * (1 + i);
534       splits[i] = format(rowkey);
535     }
536     return splits;
537   }
538 
539   /*
540    * We're to run multiple clients concurrently.  Setup a mapreduce job.  Run
541    * one map per client.  Then run a single reduce to sum the elapsed times.
542    * @param cmd Command to run.
543    * @throws IOException
544    */
545   private void runNIsMoreThanOne(final Class<? extends Test> cmd)
546   throws IOException, InterruptedException, ClassNotFoundException {
547     checkTable(new HBaseAdmin(getConf()));
548     if (this.nomapred) {
549       doMultipleClients(cmd);
550     } else {
551       doMapReduce(cmd);
552     }
553   }
554 
555   /*
556    * Run all clients in this vm each to its own thread.
557    * @param cmd Command to run.
558    * @throws IOException
559    */
560   private void doMultipleClients(final Class<? extends Test> cmd) throws IOException {
561     final List<Thread> threads = new ArrayList<Thread>(this.N);
562     final long[] timings = new long[this.N];
563     final int perClientRows = R/N;
564     final TableName tableName = this.tableName;
565     final DataBlockEncoding encoding = this.blockEncoding;
566     final boolean flushCommits = this.flushCommits;
567     final Compression.Algorithm compression = this.compression;
568     final boolean writeToWal = this.writeToWAL;
569     final int preSplitRegions = this.presplitRegions;
570     for (int i = 0; i < this.N; i++) {
571       final int index = i;
572       Thread t = new Thread ("TestClient-" + i) {
573         @Override
574         public void run() {
575           super.run();
576           PerformanceEvaluation pe = new PerformanceEvaluation(getConf());
577           pe.tableName = tableName;
578           pe.blockEncoding = encoding;
579           pe.flushCommits = flushCommits;
580           pe.compression = compression;
581           pe.writeToWAL = writeToWal;
582           pe.presplitRegions = preSplitRegions;
583           pe.N = N;
584           try {
585             long elapsedTime = pe.runOneClient(cmd, index * perClientRows,
586                perClientRows, R,
587                 flushCommits, writeToWAL, new Status() {
588                   public void setStatus(final String msg) throws IOException {
589                     LOG.info("client-" + getName() + " " + msg);
590                   }
591                 });
592             timings[index] = elapsedTime;
593             LOG.info("Finished " + getName() + " in " + elapsedTime +
594               "ms writing " + perClientRows + " rows");
595           } catch (IOException e) {
596             throw new RuntimeException(e);
597           }
598         }
599       };
600       threads.add(t);
601     }
602     for (Thread t: threads) {
603       t.start();
604     }
605     for (Thread t: threads) {
606       while(t.isAlive()) {
607         try {
608           t.join();
609         } catch (InterruptedException e) {
610           LOG.debug("Interrupted, continuing" + e.toString());
611         }
612       }
613     }
614     final String test = cmd.getSimpleName();
615     LOG.info("[" + test + "] Summary of timings (ms): "
616              + Arrays.toString(timings));
617     Arrays.sort(timings);
618     long total = 0;
619     for (int i = 0; i < this.N; i++) {
620       total += timings[i];
621     }
622     LOG.info("[" + test + "]"
623              + "\tMin: " + timings[0] + "ms"
624              + "\tMax: " + timings[this.N - 1] + "ms"
625              + "\tAvg: " + (total / this.N) + "ms");
626   }
627 
628   /*
629    * Run a mapreduce job.  Run as many maps as asked-for clients.
630    * Before we start up the job, write out an input file with instruction
631    * per client regards which row they are to start on.
632    * @param cmd Command to run.
633    * @throws IOException
634    */
635   private void doMapReduce(final Class<? extends Test> cmd) throws IOException,
636         InterruptedException, ClassNotFoundException {
637     Configuration conf = getConf();
638     Path inputDir = writeInputFile(conf);
639     conf.set(EvaluationMapTask.CMD_KEY, cmd.getName());
640     conf.set(EvaluationMapTask.PE_KEY, getClass().getName());
641     Job job = new Job(conf);
642     job.setJarByClass(PerformanceEvaluation.class);
643     job.setJobName("HBase Performance Evaluation");
644 
645     job.setInputFormatClass(PeInputFormat.class);
646     PeInputFormat.setInputPaths(job, inputDir);
647 
648     job.setOutputKeyClass(LongWritable.class);
649     job.setOutputValueClass(LongWritable.class);
650 
651     job.setMapperClass(EvaluationMapTask.class);
652     job.setReducerClass(LongSumReducer.class);
653 
654     job.setNumReduceTasks(1);
655 
656     job.setOutputFormatClass(TextOutputFormat.class);
657     TextOutputFormat.setOutputPath(job, new Path(inputDir.getParent(), "outputs"));
658 
659     TableMapReduceUtil.addDependencyJars(job);
660     // Add a Class from the hbase.jar so it gets registered too.
661     TableMapReduceUtil.addDependencyJars(job.getConfiguration(),
662       org.apache.hadoop.hbase.util.Bytes.class);
663 
664     TableMapReduceUtil.initCredentials(job);
665 
666     job.waitForCompletion(true);
667   }
668 
669   /*
670    * Write input file of offsets-per-client for the mapreduce job.
671    * @param c Configuration
672    * @return Directory that contains file written.
673    * @throws IOException
674    */
675   private Path writeInputFile(final Configuration c) throws IOException {
676     SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
677     Path jobdir = new Path(PERF_EVAL_DIR, formatter.format(new Date()));
678     Path inputDir = new Path(jobdir, "inputs");
679 
680     FileSystem fs = FileSystem.get(c);
681     fs.mkdirs(inputDir);
682 
683     Path inputFile = new Path(inputDir, "input.txt");
684     PrintStream out = new PrintStream(fs.create(inputFile));
685     // Make input random.
686     Map<Integer, String> m = new TreeMap<Integer, String>();
687     Hash h = MurmurHash.getInstance();
688     int perClientRows = (this.R / this.N);
689     try {
690       for (int i = 0; i < 10; i++) {
691         for (int j = 0; j < N; j++) {
692           String s = "startRow=" + ((j * perClientRows) + (i * (perClientRows/10))) +
693           ", perClientRunRows=" + (perClientRows / 10) +
694           ", totalRows=" + this.R +
695           ", clients=" + this.N +
696           ", flushCommits=" + this.flushCommits +
697           ", writeToWAL=" + this.writeToWAL;
698           int hash = h.hash(Bytes.toBytes(s));
699           m.put(hash, s);
700         }
701       }
702       for (Map.Entry<Integer, String> e: m.entrySet()) {
703         out.println(e.getValue());
704       }
705     } finally {
706       out.close();
707     }
708     return inputDir;
709   }
710 
711   /**
712    * Describes a command.
713    */
714   static class CmdDescriptor {
715     private Class<? extends Test> cmdClass;
716     private String name;
717     private String description;
718 
719     CmdDescriptor(Class<? extends Test> cmdClass, String name, String description) {
720       this.cmdClass = cmdClass;
721       this.name = name;
722       this.description = description;
723     }
724 
725     public Class<? extends Test> getCmdClass() {
726       return cmdClass;
727     }
728 
729     public String getName() {
730       return name;
731     }
732 
733     public String getDescription() {
734       return description;
735     }
736   }
737 
738   /**
739    * Wraps up options passed to {@link org.apache.hadoop.hbase.PerformanceEvaluation.Test
740    * tests}.  This makes the reflection logic a little easier to understand...
741    */
742   static class TestOptions {
743     private int startRow;
744     private int perClientRunRows;
745     private int totalRows;
746     private int numClientThreads;
747     private TableName tableName;
748     private boolean flushCommits;
749     private boolean writeToWAL = true;
750 
751     TestOptions() {
752     }
753 
754     TestOptions(int startRow, int perClientRunRows, int totalRows,
755                 int numClientThreads, TableName tableName,
756                 boolean flushCommits, boolean writeToWAL) {
757       this.startRow = startRow;
758       this.perClientRunRows = perClientRunRows;
759       this.totalRows = totalRows;
760       this.numClientThreads = numClientThreads;
761       this.tableName = tableName;
762       this.flushCommits = flushCommits;
763       this.writeToWAL = writeToWAL;
764     }
765 
766     public int getStartRow() {
767       return startRow;
768     }
769 
770     public int getPerClientRunRows() {
771       return perClientRunRows;
772     }
773 
774     public int getTotalRows() {
775       return totalRows;
776     }
777 
778     public int getNumClientThreads() {
779       return numClientThreads;
780     }
781 
782     public TableName getTableName() {
783       return tableName;
784     }
785 
786     public boolean isFlushCommits() {
787       return flushCommits;
788     }
789 
790     public boolean isWriteToWAL() {
791       return writeToWAL;
792     }
793   }
794 
795   /*
796    * A test.
797    * Subclass to particularize what happens per row.
798    */
799   static abstract class Test {
800     // Below is make it so when Tests are all running in the one
801     // jvm, that they each have a differently seeded Random.
802     private static final Random randomSeed =
803       new Random(System.currentTimeMillis());
804     private static long nextRandomSeed() {
805       return randomSeed.nextLong();
806     }
807     protected final Random rand = new Random(nextRandomSeed());
808 
809     protected final int startRow;
810     protected final int perClientRunRows;
811     protected final int totalRows;
812     private final Status status;
813     protected TableName tableName;
814     protected HTable table;
815     protected volatile Configuration conf;
816     protected boolean flushCommits;
817     protected boolean writeToWAL;
818 
819     /**
820      * Note that all subclasses of this class must provide a public contructor
821      * that has the exact same list of arguments.
822      */
823     Test(final Configuration conf, final TestOptions options, final Status status) {
824       super();
825       this.startRow = options.getStartRow();
826       this.perClientRunRows = options.getPerClientRunRows();
827       this.totalRows = options.getTotalRows();
828       this.status = status;
829       this.tableName = options.getTableName();
830       this.table = null;
831       this.conf = conf;
832       this.flushCommits = options.isFlushCommits();
833       this.writeToWAL = options.isWriteToWAL();
834     }
835 
836     private String generateStatus(final int sr, final int i, final int lr) {
837       return sr + "/" + i + "/" + lr;
838     }
839 
840     protected int getReportingPeriod() {
841       int period = this.perClientRunRows / 10;
842       return period == 0? this.perClientRunRows: period;
843     }
844 
845     void testSetup() throws IOException {
846       this.table = new HTable(conf, tableName);
847       this.table.setAutoFlush(false);
848       this.table.setScannerCaching(30);
849     }
850 
851     void testTakedown() throws IOException {
852       if (flushCommits) {
853         this.table.flushCommits();
854       }
855       table.close();
856     }
857 
858     /*
859      * Run test
860      * @return Elapsed time.
861      * @throws IOException
862      */
863     long test() throws IOException {
864       testSetup();
865       LOG.info("Timed test starting in thread " + Thread.currentThread().getName());
866       final long startTime = System.nanoTime();
867       try {
868         testTimed();
869       } finally {
870         testTakedown();
871       }
872       return (System.nanoTime() - startTime) / 1000000;
873     }
874 
875     /**
876      * Provides an extension point for tests that don't want a per row invocation.
877      */
878     void testTimed() throws IOException {
879       int lastRow = this.startRow + this.perClientRunRows;
880       // Report on completion of 1/10th of total.
881       for (int i = this.startRow; i < lastRow; i++) {
882         testRow(i);
883         if (status != null && i > 0 && (i % getReportingPeriod()) == 0) {
884           status.setStatus(generateStatus(this.startRow, i, lastRow));
885         }
886       }
887     }
888 
889     /*
890     * Test for individual row.
891     * @param i Row index.
892     */
893     abstract void testRow(final int i) throws IOException;
894   }
895 
896 
897   @SuppressWarnings("unused")
898   static class RandomSeekScanTest extends Test {
899     RandomSeekScanTest(Configuration conf, TestOptions options, Status status) {
900       super(conf, options, status);
901     }
902 
903     @Override
904     void testRow(final int i) throws IOException {
905       Scan scan = new Scan(getRandomRow(this.rand, this.totalRows));
906       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
907       scan.setFilter(new WhileMatchFilter(new PageFilter(120)));
908       ResultScanner s = this.table.getScanner(scan);
909       //int count = 0;
910       for (Result rr = null; (rr = s.next()) != null;) {
911         // LOG.info("" + count++ + " " + rr.toString());
912       }
913       s.close();
914     }
915 
916     @Override
917     protected int getReportingPeriod() {
918       int period = this.perClientRunRows / 100;
919       return period == 0? this.perClientRunRows: period;
920     }
921 
922   }
923 
924   @SuppressWarnings("unused")
925   static abstract class RandomScanWithRangeTest extends Test {
926     RandomScanWithRangeTest(Configuration conf, TestOptions options, Status status) {
927       super(conf, options, status);
928     }
929 
930     @Override
931     void testRow(final int i) throws IOException {
932       Pair<byte[], byte[]> startAndStopRow = getStartAndStopRow();
933       Scan scan = new Scan(startAndStopRow.getFirst(), startAndStopRow.getSecond());
934       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
935       ResultScanner s = this.table.getScanner(scan);
936       int count = 0;
937       for (Result rr = null; (rr = s.next()) != null;) {
938         count++;
939       }
940 
941       if (i % 100 == 0) {
942         LOG.info(String.format("Scan for key range %s - %s returned %s rows",
943             Bytes.toString(startAndStopRow.getFirst()),
944             Bytes.toString(startAndStopRow.getSecond()), count));
945       }
946 
947       s.close();
948     }
949 
950     protected abstract Pair<byte[],byte[]> getStartAndStopRow();
951 
952     protected Pair<byte[], byte[]> generateStartAndStopRows(int maxRange) {
953       int start = this.rand.nextInt(Integer.MAX_VALUE) % totalRows;
954       int stop = start + maxRange;
955       return new Pair<byte[],byte[]>(format(start), format(stop));
956     }
957 
958     @Override
959     protected int getReportingPeriod() {
960       int period = this.perClientRunRows / 100;
961       return period == 0? this.perClientRunRows: period;
962     }
963   }
964 
965   static class RandomScanWithRange10Test extends RandomScanWithRangeTest {
966     RandomScanWithRange10Test(Configuration conf, TestOptions options, Status status) {
967       super(conf, options, status);
968     }
969 
970     @Override
971     protected Pair<byte[], byte[]> getStartAndStopRow() {
972       return generateStartAndStopRows(10);
973     }
974   }
975 
976   static class RandomScanWithRange100Test extends RandomScanWithRangeTest {
977     RandomScanWithRange100Test(Configuration conf, TestOptions options, Status status) {
978       super(conf, options, status);
979     }
980 
981     @Override
982     protected Pair<byte[], byte[]> getStartAndStopRow() {
983       return generateStartAndStopRows(100);
984     }
985   }
986 
987   static class RandomScanWithRange1000Test extends RandomScanWithRangeTest {
988     RandomScanWithRange1000Test(Configuration conf, TestOptions options, Status status) {
989       super(conf, options, status);
990     }
991 
992     @Override
993     protected Pair<byte[], byte[]> getStartAndStopRow() {
994       return generateStartAndStopRows(1000);
995     }
996   }
997 
998   static class RandomScanWithRange10000Test extends RandomScanWithRangeTest {
999     RandomScanWithRange10000Test(Configuration conf, TestOptions options, Status status) {
1000       super(conf, options, status);
1001     }
1002 
1003     @Override
1004     protected Pair<byte[], byte[]> getStartAndStopRow() {
1005       return generateStartAndStopRows(10000);
1006     }
1007   }
1008 
1009   static class RandomReadTest extends Test {
1010     RandomReadTest(Configuration conf, TestOptions options, Status status) {
1011       super(conf, options, status);
1012     }
1013 
1014     @Override
1015     void testRow(final int i) throws IOException {
1016       Get get = new Get(getRandomRow(this.rand, this.totalRows));
1017       get.addColumn(FAMILY_NAME, QUALIFIER_NAME);
1018       this.table.get(get);
1019     }
1020 
1021     @Override
1022     protected int getReportingPeriod() {
1023       int period = this.perClientRunRows / 100;
1024       return period == 0? this.perClientRunRows: period;
1025     }
1026 
1027   }
1028 
1029   static class RandomWriteTest extends Test {
1030     RandomWriteTest(Configuration conf, TestOptions options, Status status) {
1031       super(conf, options, status);
1032     }
1033 
1034     @Override
1035     void testRow(final int i) throws IOException {
1036       byte [] row = getRandomRow(this.rand, this.totalRows);
1037       Put put = new Put(row);
1038       byte[] value = generateValue(this.rand);
1039       put.add(FAMILY_NAME, QUALIFIER_NAME, value);
1040       put.setDurability(writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1041       table.put(put);
1042     }
1043   }
1044 
1045 
1046   static class ScanTest extends Test {
1047     private ResultScanner testScanner;
1048 
1049     ScanTest(Configuration conf, TestOptions options, Status status) {
1050       super(conf, options, status);
1051     }
1052 
1053     @Override
1054     void testTakedown() throws IOException {
1055       if (this.testScanner != null) {
1056         this.testScanner.close();
1057       }
1058       super.testTakedown();
1059     }
1060 
1061 
1062     @Override
1063     void testRow(final int i) throws IOException {
1064       if (this.testScanner == null) {
1065         Scan scan = new Scan(format(this.startRow));
1066         scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
1067         this.testScanner = table.getScanner(scan);
1068       }
1069       testScanner.next();
1070     }
1071 
1072   }
1073 
1074   static class SequentialReadTest extends Test {
1075     SequentialReadTest(Configuration conf, TestOptions options, Status status) {
1076       super(conf, options, status);
1077     }
1078 
1079     @Override
1080     void testRow(final int i) throws IOException {
1081       Get get = new Get(format(i));
1082       get.addColumn(FAMILY_NAME, QUALIFIER_NAME);
1083       table.get(get);
1084     }
1085 
1086   }
1087 
1088   static class SequentialWriteTest extends Test {
1089     SequentialWriteTest(Configuration conf, TestOptions options, Status status) {
1090       super(conf, options, status);
1091     }
1092 
1093     @Override
1094     void testRow(final int i) throws IOException {
1095       Put put = new Put(format(i));
1096       byte[] value = generateValue(this.rand);
1097       put.add(FAMILY_NAME, QUALIFIER_NAME, value);
1098       put.setDurability(writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1099       table.put(put);
1100     }
1101 
1102   }
1103 
1104   static class FilteredScanTest extends Test {
1105     protected static final Log LOG = LogFactory.getLog(FilteredScanTest.class.getName());
1106 
1107     FilteredScanTest(Configuration conf, TestOptions options, Status status) {
1108       super(conf, options, status);
1109     }
1110 
1111     @Override
1112     void testRow(int i) throws IOException {
1113       byte[] value = generateValue(this.rand);
1114       Scan scan = constructScan(value);
1115       ResultScanner scanner = null;
1116       try {
1117         scanner = this.table.getScanner(scan);
1118         while (scanner.next() != null) {
1119         }
1120       } finally {
1121         if (scanner != null) scanner.close();
1122       }
1123     }
1124 
1125     protected Scan constructScan(byte[] valuePrefix) throws IOException {
1126       Filter filter = new SingleColumnValueFilter(
1127           FAMILY_NAME, QUALIFIER_NAME, CompareFilter.CompareOp.EQUAL,
1128           new BinaryComparator(valuePrefix)
1129       );
1130       Scan scan = new Scan();
1131       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
1132       scan.setFilter(filter);
1133       return scan;
1134     }
1135   }
1136 
1137   /*
1138    * Format passed integer.
1139    * @param number
1140    * @return Returns zero-prefixed 10-byte wide decimal version of passed
1141    * number (Does absolute in case number is negative).
1142    */
1143   public static byte [] format(final int number) {
1144     byte [] b = new byte[DEFAULT_ROW_PREFIX_LENGTH + 10];
1145     int d = Math.abs(number);
1146     for (int i = b.length - 1; i >= 0; i--) {
1147       b[i] = (byte)((d % 10) + '0');
1148       d /= 10;
1149     }
1150     return b;
1151   }
1152 
1153   /*
1154    * This method takes some time and is done inline uploading data.  For
1155    * example, doing the mapfile test, generation of the key and value
1156    * consumes about 30% of CPU time.
1157    * @return Generated random value to insert into a table cell.
1158    */
1159   public static byte[] generateValue(final Random r) {
1160     byte [] b = new byte [VALUE_LENGTH];
1161     int i = 0;
1162 
1163     for(i = 0; i < (VALUE_LENGTH-8); i += 8) {
1164       b[i] = (byte) (65 + r.nextInt(26));
1165       b[i+1] = b[i];
1166       b[i+2] = b[i];
1167       b[i+3] = b[i];
1168       b[i+4] = b[i];
1169       b[i+5] = b[i];
1170       b[i+6] = b[i];
1171       b[i+7] = b[i];
1172     }
1173 
1174     byte a = (byte) (65 + r.nextInt(26));
1175     for(; i < VALUE_LENGTH; i++) {
1176       b[i] = a;
1177     }
1178     return b;
1179   }
1180 
1181   static byte [] getRandomRow(final Random random, final int totalRows) {
1182     return format(random.nextInt(Integer.MAX_VALUE) % totalRows);
1183   }
1184 
1185   long runOneClient(final Class<? extends Test> cmd, final int startRow,
1186                     final int perClientRunRows, final int totalRows,
1187                     boolean flushCommits, boolean writeToWAL,
1188                     final Status status)
1189   throws IOException {
1190     status.setStatus("Start " + cmd + " at offset " + startRow + " for " +
1191       perClientRunRows + " rows");
1192     long totalElapsedTime = 0;
1193 
1194     TestOptions options = new TestOptions(startRow, perClientRunRows,
1195         totalRows, N, tableName, flushCommits, writeToWAL);
1196     final Test t;
1197     try {
1198       Constructor<? extends Test> constructor = cmd.getDeclaredConstructor(
1199           Configuration.class, TestOptions.class, Status.class);
1200       t = constructor.newInstance(getConf(), options, status);
1201     } catch (NoSuchMethodException e) {
1202       throw new IllegalArgumentException("Invalid command class: " +
1203           cmd.getName() + ".  It does not provide a constructor as described by" +
1204           "the javadoc comment.  Available constructors are: " +
1205           Arrays.toString(cmd.getConstructors()));
1206     } catch (Exception e) {
1207       throw new IllegalStateException("Failed to construct command class", e);
1208     }
1209     totalElapsedTime = t.test();
1210 
1211     status.setStatus("Finished " + cmd + " in " + totalElapsedTime +
1212       "ms at offset " + startRow + " for " + perClientRunRows + " rows");
1213     return totalElapsedTime;
1214   }
1215 
1216   private void runNIsOne(final Class<? extends Test> cmd) {
1217     Status status = new Status() {
1218       public void setStatus(String msg) throws IOException {
1219         LOG.info(msg);
1220       }
1221     };
1222 
1223     HBaseAdmin admin = null;
1224     try {
1225       admin = new HBaseAdmin(getConf());
1226       checkTable(admin);
1227       runOneClient(cmd, 0, this.R, this.R, this.flushCommits, this.writeToWAL,
1228         status);
1229     } catch (Exception e) {
1230       LOG.error("Failed", e);
1231     }
1232   }
1233 
1234   private void runTest(final Class<? extends Test> cmd) throws IOException,
1235           InterruptedException, ClassNotFoundException {
1236     MiniHBaseCluster hbaseMiniCluster = null;
1237     MiniDFSCluster dfsCluster = null;
1238     MiniZooKeeperCluster zooKeeperCluster = null;
1239     Configuration conf = getConf();
1240     if (this.miniCluster) {
1241       dfsCluster = new MiniDFSCluster(conf, 2, true, (String[])null);
1242       zooKeeperCluster = new MiniZooKeeperCluster(conf);
1243       int zooKeeperPort = zooKeeperCluster.startup(new File(System.getProperty("java.io.tmpdir")));
1244 
1245       // mangle the conf so that the fs parameter points to the minidfs we
1246       // just started up
1247       FileSystem fs = dfsCluster.getFileSystem();
1248       FSUtils.setFsDefault(conf, new Path(fs.getUri()));
1249       conf.set(HConstants.ZOOKEEPER_CLIENT_PORT, Integer.toString(zooKeeperPort));
1250       Path parentdir = fs.getHomeDirectory();
1251       FSUtils.setRootDir(conf, parentdir);
1252       fs.mkdirs(parentdir);
1253       FSUtils.setVersion(fs, parentdir);
1254       hbaseMiniCluster = new MiniHBaseCluster(conf, N);
1255     }
1256 
1257     try {
1258       if (N == 1) {
1259         // If there is only one client and one HRegionServer, we assume nothing
1260         // has been set up at all.
1261         runNIsOne(cmd);
1262       } else {
1263         // Else, run
1264         runNIsMoreThanOne(cmd);
1265       }
1266     } finally {
1267       if(this.miniCluster) {
1268         if (hbaseMiniCluster != null) hbaseMiniCluster.shutdown();
1269         if (zooKeeperCluster != null) zooKeeperCluster.shutdown();
1270         HBaseTestCase.shutdownDfs(dfsCluster);
1271       }
1272     }
1273   }
1274 
1275   protected void printUsage() {
1276     printUsage(null);
1277   }
1278 
1279   protected void printUsage(final String message) {
1280     if (message != null && message.length() > 0) {
1281       System.err.println(message);
1282     }
1283     System.err.println("Usage: java " + this.getClass().getName() + " \\");
1284     System.err.println("  [--miniCluster] [--nomapred] [--rows=ROWS] [--table=NAME] \\");
1285     System.err.println("  [--compress=TYPE] [--blockEncoding=TYPE] [-D<property=value>]* <command> <nclients>");
1286     System.err.println();
1287     System.err.println("Options:");
1288     System.err.println(" miniCluster     Run the test on an HBaseMiniCluster");
1289     System.err.println(" nomapred        Run multiple clients using threads " +
1290       "(rather than use mapreduce)");
1291     System.err.println(" rows            Rows each client runs. Default: One million");
1292     System.err.println(" table           Alternate table name. Default: 'TestTable'");
1293     System.err.println(" compress        Compression type to use (GZ, LZO, ...). Default: 'NONE'");
1294     System.err.println(" flushCommits    Used to determine if the test should flush the table.  Default: false");
1295     System.err.println(" writeToWAL      Set writeToWAL on puts. Default: True");
1296     System.err.println(" presplit        Create presplit table. Recommended for accurate perf analysis (see guide).  Default: disabled");
1297     System.err
1298         .println(" inmemory        Tries to keep the HFiles of the CF inmemory as far as possible.  Not " +
1299         		"guaranteed that reads are always served from inmemory.  Default: false");
1300     System.err.println();
1301     System.err.println(" Note: -D properties will be applied to the conf used. ");
1302     System.err.println("  For example: ");
1303     System.err.println("   -Dmapred.output.compress=true");
1304     System.err.println("   -Dmapreduce.task.timeout=60000");
1305     System.err.println();
1306     System.err.println("Command:");
1307     for (CmdDescriptor command : commands.values()) {
1308       System.err.println(String.format(" %-15s %s", command.getName(), command.getDescription()));
1309     }
1310     System.err.println();
1311     System.err.println("Args:");
1312     System.err.println(" nclients        Integer. Required. Total number of " +
1313       "clients (and HRegionServers)");
1314     System.err.println("                 running: 1 <= value <= 500");
1315     System.err.println("Examples:");
1316     System.err.println(" To run a single evaluation client:");
1317     System.err.println(" $ bin/hbase " + this.getClass().getName()
1318         + " sequentialWrite 1");
1319   }
1320 
1321   private void getArgs(final int start, final String[] args) {
1322     if(start + 1 > args.length) {
1323       throw new IllegalArgumentException("must supply the number of clients");
1324     }
1325     N = Integer.parseInt(args[start]);
1326     if (N < 1) {
1327       throw new IllegalArgumentException("Number of clients must be > 1");
1328     }
1329     // Set total number of rows to write.
1330     this.R = this.R * N;
1331   }
1332 
1333   public int run(String[] args) throws Exception {
1334     // Process command-line args. TODO: Better cmd-line processing
1335     // (but hopefully something not as painful as cli options).
1336     int errCode = -1;
1337     if (args.length < 1) {
1338       printUsage();
1339       return errCode;
1340     }
1341 
1342     try {
1343       for (int i = 0; i < args.length; i++) {
1344         String cmd = args[i];
1345         if (cmd.equals("-h") || cmd.startsWith("--h")) {
1346           printUsage();
1347           errCode = 0;
1348           break;
1349         }
1350 
1351         final String miniClusterArgKey = "--miniCluster";
1352         if (cmd.startsWith(miniClusterArgKey)) {
1353           this.miniCluster = true;
1354           continue;
1355         }
1356 
1357         final String nmr = "--nomapred";
1358         if (cmd.startsWith(nmr)) {
1359           this.nomapred = true;
1360           continue;
1361         }
1362 
1363         final String rows = "--rows=";
1364         if (cmd.startsWith(rows)) {
1365           this.R = Integer.parseInt(cmd.substring(rows.length()));
1366           continue;
1367         }
1368 
1369         final String table = "--table=";
1370         if (cmd.startsWith(table)) {
1371           this.tableName = TableName.valueOf(cmd.substring(table.length()));
1372           continue;
1373         }
1374 
1375         final String compress = "--compress=";
1376         if (cmd.startsWith(compress)) {
1377           this.compression = Compression.Algorithm.valueOf(cmd.substring(compress.length()));
1378           continue;
1379         }
1380 
1381         final String blockEncoding = "--blockEncoding=";
1382         if (cmd.startsWith(blockEncoding)) {
1383           this.blockEncoding = DataBlockEncoding.valueOf(cmd.substring(blockEncoding.length()));
1384           continue;
1385         }
1386 
1387         final String flushCommits = "--flushCommits=";
1388         if (cmd.startsWith(flushCommits)) {
1389           this.flushCommits = Boolean.parseBoolean(cmd.substring(flushCommits.length()));
1390           continue;
1391         }
1392 
1393         final String writeToWAL = "--writeToWAL=";
1394         if (cmd.startsWith(writeToWAL)) {
1395           this.writeToWAL = Boolean.parseBoolean(cmd.substring(writeToWAL.length()));
1396           continue;
1397         }
1398 
1399         final String presplit = "--presplit=";
1400         if (cmd.startsWith(presplit)) {
1401           this.presplitRegions = Integer.parseInt(cmd.substring(presplit.length()));
1402           continue;
1403         }
1404         
1405         final String inMemory = "--inmemory=";
1406         if (cmd.startsWith(inMemory)) {
1407           this.inMemoryCF = Boolean.parseBoolean(cmd.substring(inMemory.length()));
1408           continue;
1409         }
1410         
1411         Class<? extends Test> cmdClass = determineCommandClass(cmd);
1412         if (cmdClass != null) {
1413           getArgs(i + 1, args);
1414           runTest(cmdClass);
1415           errCode = 0;
1416           break;
1417         }
1418 
1419         printUsage();
1420         break;
1421       }
1422     } catch (Exception e) {
1423       e.printStackTrace();
1424     }
1425 
1426     return errCode;
1427   }
1428 
1429   private Class<? extends Test> determineCommandClass(String cmd) {
1430     CmdDescriptor descriptor = commands.get(cmd);
1431     return descriptor != null ? descriptor.getCmdClass() : null;
1432   }
1433 
1434   /**
1435    * @param args
1436    */
1437   public static void main(final String[] args) throws Exception {
1438     int res = ToolRunner.run(new PerformanceEvaluation(HBaseConfiguration.create()), args);
1439     System.exit(res);
1440   }
1441 }