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 static org.codehaus.jackson.map.SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY;
22  
23  import java.io.IOException;
24  import java.io.PrintStream;
25  import java.lang.reflect.Constructor;
26  import java.math.BigDecimal;
27  import java.math.MathContext;
28  import java.text.DecimalFormat;
29  import java.text.SimpleDateFormat;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Date;
33  import java.util.Map;
34  import java.util.Random;
35  import java.util.TreeMap;
36  import java.util.concurrent.Callable;
37  import java.util.concurrent.ExecutionException;
38  import java.util.concurrent.ExecutorService;
39  import java.util.concurrent.Executors;
40  import java.util.concurrent.Future;
41  
42  import com.google.common.util.concurrent.ThreadFactoryBuilder;
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
46  import org.apache.hadoop.conf.Configuration;
47  import org.apache.hadoop.conf.Configured;
48  import org.apache.hadoop.fs.FileSystem;
49  import org.apache.hadoop.fs.Path;
50  import org.apache.hadoop.hbase.client.Durability;
51  import org.apache.hadoop.hbase.client.Get;
52  import org.apache.hadoop.hbase.client.HBaseAdmin;
53  import org.apache.hadoop.hbase.client.HConnection;
54  import org.apache.hadoop.hbase.client.HConnectionManager;
55  import org.apache.hadoop.hbase.client.HTableInterface;
56  import org.apache.hadoop.hbase.client.Put;
57  import org.apache.hadoop.hbase.client.Result;
58  import org.apache.hadoop.hbase.client.ResultScanner;
59  import org.apache.hadoop.hbase.client.Scan;
60  import org.apache.hadoop.hbase.filter.BinaryComparator;
61  import org.apache.hadoop.hbase.filter.CompareFilter;
62  import org.apache.hadoop.hbase.filter.Filter;
63  import org.apache.hadoop.hbase.filter.FilterAllFilter;
64  import org.apache.hadoop.hbase.filter.FilterList;
65  import org.apache.hadoop.hbase.filter.PageFilter;
66  import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
67  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
68  import org.apache.hadoop.hbase.io.compress.Compression;
69  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
70  import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
71  import org.apache.hadoop.hbase.regionserver.BloomType;
72  import org.apache.hadoop.hbase.util.Bytes;
73  import org.apache.hadoop.hbase.util.Hash;
74  import org.apache.hadoop.hbase.util.MurmurHash;
75  import org.apache.hadoop.hbase.util.Pair;
76  import org.apache.hadoop.io.LongWritable;
77  import org.apache.hadoop.io.Text;
78  import org.apache.hadoop.mapreduce.Job;
79  import org.apache.hadoop.mapreduce.Mapper;
80  import org.apache.hadoop.mapreduce.lib.input.NLineInputFormat;
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.codehaus.jackson.map.ObjectMapper;
86  
87  import com.google.common.util.concurrent.ThreadFactoryBuilder;
88  import com.yammer.metrics.core.Histogram;
89  import com.yammer.metrics.core.MetricsRegistry;
90  
91  /**
92   * Script used evaluating HBase performance and scalability.  Runs a HBase
93   * client that steps through one of a set of hardcoded tests or 'experiments'
94   * (e.g. a random reads test, a random writes test, etc.). Pass on the
95   * command-line which test to run and how many clients are participating in
96   * this experiment. Run <code>java PerformanceEvaluation --help</code> to
97   * obtain usage.
98   *
99   * <p>This class sets up and runs the evaluation programs described in
100  * Section 7, <i>Performance Evaluation</i>, of the <a
101  * href="http://labs.google.com/papers/bigtable.html">Bigtable</a>
102  * paper, pages 8-10.
103  *
104  * <p>If number of clients > 1, we start up a MapReduce job. Each map task
105  * runs an individual client. Each client does about 1GB of data.
106  */
107 public class PerformanceEvaluation extends Configured implements Tool {
108   protected static final Log LOG = LogFactory.getLog(PerformanceEvaluation.class.getName());
109 
110   public static final String TABLE_NAME = "TestTable";
111   public static final byte[] FAMILY_NAME = Bytes.toBytes("info");
112   public static final byte[] QUALIFIER_NAME = Bytes.toBytes("data");
113   public static final int VALUE_LENGTH = 1000;
114   public static final int ROW_LENGTH = 26;
115 
116   private static final int ONE_GB = 1024 * 1024 * 1000;
117   private static final int ROWS_PER_GB = ONE_GB / VALUE_LENGTH;
118   // TODO : should we make this configurable
119   private static final int TAG_LENGTH = 256;
120   private static final DecimalFormat FMT = new DecimalFormat("0.##");
121   private static final MathContext CXT = MathContext.DECIMAL64;
122   private static final BigDecimal MS_PER_SEC = BigDecimal.valueOf(1000);
123   private static final BigDecimal BYTES_PER_MB = BigDecimal.valueOf(1024 * 1024);
124   private static final TestOptions DEFAULT_OPTS = new TestOptions();
125 
126   protected Map<String, CmdDescriptor> commands = new TreeMap<String, CmdDescriptor>();
127 
128   private static final Path PERF_EVAL_DIR = new Path("performance_evaluation");
129 
130   /**
131    * Enum for map metrics.  Keep it out here rather than inside in the Map
132    * inner-class so we can find associated properties.
133    */
134   protected static enum Counter {
135     /** elapsed time */
136     ELAPSED_TIME,
137     /** number of rows */
138     ROWS
139   }
140 
141   /**
142    * Constructor
143    * @param conf Configuration object
144    */
145   public PerformanceEvaluation(final Configuration conf) {
146     super(conf);
147 
148     addCommandDescriptor(RandomReadTest.class, "randomRead",
149         "Run random read test");
150     addCommandDescriptor(RandomSeekScanTest.class, "randomSeekScan",
151         "Run random seek and scan 100 test");
152     addCommandDescriptor(RandomScanWithRange10Test.class, "scanRange10",
153         "Run random seek scan with both start and stop row (max 10 rows)");
154     addCommandDescriptor(RandomScanWithRange100Test.class, "scanRange100",
155         "Run random seek scan with both start and stop row (max 100 rows)");
156     addCommandDescriptor(RandomScanWithRange1000Test.class, "scanRange1000",
157         "Run random seek scan with both start and stop row (max 1000 rows)");
158     addCommandDescriptor(RandomScanWithRange10000Test.class, "scanRange10000",
159         "Run random seek scan with both start and stop row (max 10000 rows)");
160     addCommandDescriptor(RandomWriteTest.class, "randomWrite",
161         "Run random write test");
162     addCommandDescriptor(SequentialReadTest.class, "sequentialRead",
163         "Run sequential read test");
164     addCommandDescriptor(SequentialWriteTest.class, "sequentialWrite",
165         "Run sequential write test");
166     addCommandDescriptor(ScanTest.class, "scan",
167         "Run scan test (read every row)");
168     addCommandDescriptor(FilteredScanTest.class, "filterScan",
169         "Run scan test using a filter to find a specific row based on it's value (make sure to use --rows=20)");
170   }
171 
172   protected void addCommandDescriptor(Class<? extends Test> cmdClass,
173       String name, String description) {
174     CmdDescriptor cmdDescriptor =
175       new CmdDescriptor(cmdClass, name, description);
176     commands.put(name, cmdDescriptor);
177   }
178 
179   /**
180    * Implementations can have their status set.
181    */
182   interface Status {
183     /**
184      * Sets status
185      * @param msg status message
186      * @throws IOException
187      */
188     void setStatus(final String msg) throws IOException;
189   }
190 
191   /**
192    * MapReduce job that runs a performance evaluation client in each map task.
193    */
194   public static class EvaluationMapTask
195       extends Mapper<LongWritable, Text, LongWritable, LongWritable> {
196 
197     /** configuration parameter name that contains the command */
198     public final static String CMD_KEY = "EvaluationMapTask.command";
199     /** configuration parameter name that contains the PE impl */
200     public static final String PE_KEY = "EvaluationMapTask.performanceEvalImpl";
201 
202     private Class<? extends Test> cmd;
203     private PerformanceEvaluation pe;
204 
205     @Override
206     protected void setup(Context context) throws IOException, InterruptedException {
207       this.cmd = forName(context.getConfiguration().get(CMD_KEY), Test.class);
208 
209       // this is required so that extensions of PE are instantiated within the
210       // map reduce task...
211       Class<? extends PerformanceEvaluation> peClass =
212           forName(context.getConfiguration().get(PE_KEY), PerformanceEvaluation.class);
213       try {
214         this.pe = peClass.getConstructor(Configuration.class)
215             .newInstance(context.getConfiguration());
216       } catch (Exception e) {
217         throw new IllegalStateException("Could not instantiate PE instance", e);
218       }
219     }
220 
221     private <Type> Class<? extends Type> forName(String className, Class<Type> type) {
222       try {
223         return Class.forName(className).asSubclass(type);
224       } catch (ClassNotFoundException e) {
225         throw new IllegalStateException("Could not find class for name: " + className, e);
226       }
227     }
228 
229     protected void map(LongWritable key, Text value, final Context context)
230            throws IOException, InterruptedException {
231 
232       Status status = new Status() {
233         public void setStatus(String msg) {
234            context.setStatus(msg);
235         }
236       };
237 
238       ObjectMapper mapper = new ObjectMapper();
239       TestOptions opts = mapper.readValue(value.toString(), TestOptions.class);
240       Configuration conf = HBaseConfiguration.create(context.getConfiguration());
241 
242       // Evaluation task
243       long elapsedTime = this.pe.runOneClient(this.cmd, conf, opts, status);
244       // Collect how much time the thing took. Report as map output and
245       // to the ELAPSED_TIME counter.
246       context.getCounter(Counter.ELAPSED_TIME).increment(elapsedTime);
247       context.getCounter(Counter.ROWS).increment(opts.perClientRunRows);
248       context.write(new LongWritable(opts.startRow), new LongWritable(elapsedTime));
249       context.progress();
250     }
251   }
252 
253   /*
254    * If table does not already exist, create.
255    * @param c Client to use checking.
256    * @return True if we created the table.
257    * @throws IOException
258    */
259   private static boolean checkTable(HBaseAdmin admin, TestOptions opts) throws IOException {
260     HTableDescriptor tableDescriptor = getTableDescriptor(opts);
261     if (opts.presplitRegions > 0) {
262       // presplit requested
263       if (admin.tableExists(tableDescriptor.getTableName())) {
264         admin.disableTable(tableDescriptor.getTableName());
265         admin.deleteTable(tableDescriptor.getTableName());
266       }
267 
268       byte[][] splits = getSplits(opts);
269       for (int i=0; i < splits.length; i++) {
270         LOG.debug(" split " + i + ": " + Bytes.toStringBinary(splits[i]));
271       }
272       admin.createTable(tableDescriptor, splits);
273       LOG.info ("Table created with " + opts.presplitRegions + " splits");
274     }
275     else {
276       boolean tableExists = admin.tableExists(tableDescriptor.getTableName());
277       if (!tableExists) {
278         admin.createTable(tableDescriptor);
279         LOG.info("Table " + tableDescriptor + " created");
280       }
281     }
282     return admin.tableExists(tableDescriptor.getTableName());
283   }
284 
285   /**
286    * Create an HTableDescriptor from provided TestOptions.
287    */
288   protected static HTableDescriptor getTableDescriptor(TestOptions opts) {
289     HTableDescriptor desc = new HTableDescriptor(opts.tableName);
290     HColumnDescriptor family = new HColumnDescriptor(FAMILY_NAME);
291     family.setDataBlockEncoding(opts.blockEncoding);
292     family.setCompressionType(opts.compression);
293     family.setBloomFilterType(opts.bloomType);
294     if (opts.inMemoryCF) {
295       family.setInMemory(true);
296     }
297     desc.addFamily(family);
298     return desc;
299   }
300 
301   /**
302    * generates splits based on total number of rows and specified split regions
303    */
304   protected static byte[][] getSplits(TestOptions opts) {
305     if (opts.presplitRegions == 0)
306       return new byte [0][];
307 
308     int numSplitPoints = opts.presplitRegions - 1;
309     byte[][] splits = new byte[numSplitPoints][];
310     int jump = opts.totalRows / opts.presplitRegions;
311     for (int i = 0; i < numSplitPoints; i++) {
312       int rowkey = jump * (1 + i);
313       splits[i] = format(rowkey);
314     }
315     return splits;
316   }
317 
318   /*
319    * Run all clients in this vm each to its own thread.
320    * @param cmd Command to run.
321    * @throws IOException
322    */
323   private void doLocalClients(final Class<? extends Test> cmd, final TestOptions opts)
324       throws IOException, InterruptedException {
325     Future<Long>[] threads = new Future[opts.numClientThreads];
326     long[] timings = new long[opts.numClientThreads];
327     ExecutorService pool = Executors.newFixedThreadPool(opts.numClientThreads,
328       new ThreadFactoryBuilder().setNameFormat("TestClient-%s").build());
329     for (int i = 0; i < threads.length; i++) {
330       final int index = i;
331       threads[i] = pool.submit(new Callable<Long>() {
332         @Override
333         public Long call() throws Exception {
334           TestOptions threadOpts = new TestOptions(opts);
335           threadOpts.startRow = index * threadOpts.perClientRunRows;
336           long elapsedTime = runOneClient(cmd, getConf(), threadOpts, new Status() {
337             public void setStatus(final String msg) throws IOException {
338               LOG.info("client-" + Thread.currentThread().getName() + " " + msg);
339             }
340           });
341           LOG.info("Finished " + Thread.currentThread().getName() + " in " + elapsedTime +
342             "ms over " + threadOpts.perClientRunRows + " rows");
343           return elapsedTime;
344         }
345       });
346     }
347     pool.shutdown();
348     for (int i = 0; i < threads.length; i++) {
349       try {
350         timings[i] = threads[i].get();
351       } catch (ExecutionException e) {
352         throw new IOException(e.getCause());
353       }
354     }
355     final String test = cmd.getSimpleName();
356     LOG.info("[" + test + "] Summary of timings (ms): "
357              + Arrays.toString(timings));
358     Arrays.sort(timings);
359     long total = 0;
360     for (int i = 0; i < timings.length; i++) {
361       total += timings[i];
362     }
363     LOG.info("[" + test + "]"
364              + "\tMin: " + timings[0] + "ms"
365              + "\tMax: " + timings[timings.length - 1] + "ms"
366              + "\tAvg: " + (total / timings.length) + "ms");
367   }
368 
369   /*
370    * Run a mapreduce job.  Run as many maps as asked-for clients.
371    * Before we start up the job, write out an input file with instruction
372    * per client regards which row they are to start on.
373    * @param cmd Command to run.
374    * @throws IOException
375    */
376   private void doMapReduce(final Class<? extends Test> cmd, TestOptions opts) throws IOException,
377         InterruptedException, ClassNotFoundException {
378     Configuration conf = getConf();
379     Path inputDir = writeInputFile(conf, opts);
380     conf.set(EvaluationMapTask.CMD_KEY, cmd.getName());
381     conf.set(EvaluationMapTask.PE_KEY, getClass().getName());
382     Job job = new Job(conf);
383     job.setJarByClass(PerformanceEvaluation.class);
384     job.setJobName("HBase Performance Evaluation");
385 
386     job.setInputFormatClass(NLineInputFormat.class);
387     NLineInputFormat.setInputPaths(job, inputDir);
388     // this is default, but be explicit about it just in case.
389     NLineInputFormat.setNumLinesPerSplit(job, 1);
390 
391     job.setOutputKeyClass(LongWritable.class);
392     job.setOutputValueClass(LongWritable.class);
393 
394     job.setMapperClass(EvaluationMapTask.class);
395     job.setReducerClass(LongSumReducer.class);
396 
397     job.setNumReduceTasks(1);
398 
399     job.setOutputFormatClass(TextOutputFormat.class);
400     TextOutputFormat.setOutputPath(job, new Path(inputDir.getParent(), "outputs"));
401 
402     TableMapReduceUtil.addDependencyJars(job);
403     TableMapReduceUtil.addDependencyJars(job.getConfiguration(),
404       DescriptiveStatistics.class, // commons-math
405       ObjectMapper.class);         // jackson-mapper-asl
406 
407     TableMapReduceUtil.initCredentials(job);
408 
409     job.waitForCompletion(true);
410   }
411 
412   /*
413    * Write input file of offsets-per-client for the mapreduce job.
414    * @param c Configuration
415    * @return Directory that contains file written.
416    * @throws IOException
417    */
418   private Path writeInputFile(final Configuration c, final TestOptions opts) throws IOException {
419     SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
420     Path jobdir = new Path(PERF_EVAL_DIR, formatter.format(new Date()));
421     Path inputDir = new Path(jobdir, "inputs");
422 
423     FileSystem fs = FileSystem.get(c);
424     fs.mkdirs(inputDir);
425 
426     Path inputFile = new Path(inputDir, "input.txt");
427     PrintStream out = new PrintStream(fs.create(inputFile));
428     // Make input random.
429     Map<Integer, String> m = new TreeMap<Integer, String>();
430     Hash h = MurmurHash.getInstance();
431     int perClientRows = (opts.totalRows / opts.numClientThreads);
432     ObjectMapper mapper = new ObjectMapper();
433     mapper.configure(SORT_PROPERTIES_ALPHABETICALLY, true);
434     try {
435       for (int i = 0; i < 10; i++) {
436         for (int j = 0; j < opts.numClientThreads; j++) {
437           TestOptions next = new TestOptions(opts);
438           next.startRow = (j * perClientRows) + (i * (perClientRows/10));
439           next.perClientRunRows = perClientRows / 10;
440           String s = mapper.writeValueAsString(next);
441           int hash = h.hash(Bytes.toBytes(s));
442           m.put(hash, s);
443         }
444       }
445       for (Map.Entry<Integer, String> e: m.entrySet()) {
446         out.println(e.getValue());
447       }
448     } finally {
449       out.close();
450     }
451     return inputDir;
452   }
453 
454   /**
455    * Describes a command.
456    */
457   static class CmdDescriptor {
458     private Class<? extends Test> cmdClass;
459     private String name;
460     private String description;
461 
462     CmdDescriptor(Class<? extends Test> cmdClass, String name, String description) {
463       this.cmdClass = cmdClass;
464       this.name = name;
465       this.description = description;
466     }
467 
468     public Class<? extends Test> getCmdClass() {
469       return cmdClass;
470     }
471 
472     public String getName() {
473       return name;
474     }
475 
476     public String getDescription() {
477       return description;
478     }
479   }
480 
481   /**
482    * Wraps up options passed to {@link org.apache.hadoop.hbase.PerformanceEvaluation}.
483    * This makes tracking all these arguments a little easier.
484    */
485   static class TestOptions {
486 
487     public TestOptions() {}
488 
489     public TestOptions(TestOptions that) {
490       this.nomapred = that.nomapred;
491       this.startRow = that.startRow;
492       this.perClientRunRows = that.perClientRunRows;
493       this.numClientThreads = that.numClientThreads;
494       this.totalRows = that.totalRows;
495       this.sampleRate = that.sampleRate;
496       this.tableName = that.tableName;
497       this.flushCommits = that.flushCommits;
498       this.writeToWAL = that.writeToWAL;
499       this.useTags = that.useTags;
500       this.noOfTags = that.noOfTags;
501       this.reportLatency = that.reportLatency;
502       this.multiGet = that.multiGet;
503       this.inMemoryCF = that.inMemoryCF;
504       this.presplitRegions = that.presplitRegions;
505       this.compression = that.compression;
506       this.blockEncoding = that.blockEncoding;
507       this.filterAll = that.filterAll;
508       this.bloomType = that.bloomType;
509     }
510 
511     public boolean nomapred = false;
512     public boolean filterAll = false;
513     public int startRow = 0;
514     public int perClientRunRows = ROWS_PER_GB;
515     public int numClientThreads = 1;
516     public int totalRows = ROWS_PER_GB;
517     public float sampleRate = 1.0f;
518     public String tableName = TABLE_NAME;
519     public boolean flushCommits = true;
520     public boolean writeToWAL = true;
521     public boolean useTags = false;
522     public int noOfTags = 1;
523     public boolean reportLatency = false;
524     public int multiGet = 0;
525     boolean inMemoryCF = false;
526     int presplitRegions = 0;
527     public Compression.Algorithm compression = Compression.Algorithm.NONE;
528     public BloomType bloomType = BloomType.ROW;
529     public DataBlockEncoding blockEncoding = DataBlockEncoding.NONE;
530   }
531 
532   /*
533    * A test.
534    * Subclass to particularize what happens per row.
535    */
536   static abstract class Test {
537     // Below is make it so when Tests are all running in the one
538     // jvm, that they each have a differently seeded Random.
539     private static final Random randomSeed = new Random(System.currentTimeMillis());
540     private static long nextRandomSeed() {
541       return randomSeed.nextLong();
542     }
543     protected final Random rand = new Random(nextRandomSeed());
544     protected final Configuration conf;
545     protected final TestOptions opts;
546 
547     private final Status status;
548     protected HConnection connection;
549     protected HTableInterface table;
550 
551     /**
552      * Note that all subclasses of this class must provide a public contructor
553      * that has the exact same list of arguments.
554      */
555     Test(final Configuration conf, final TestOptions options, final Status status) {
556       this.conf = conf;
557       this.opts = options;
558       this.status = status;
559     }
560 
561     private String generateStatus(final int sr, final int i, final int lr) {
562       return sr + "/" + i + "/" + lr;
563     }
564 
565     protected int getReportingPeriod() {
566       int period = opts.perClientRunRows / 10;
567       return period == 0 ? opts.perClientRunRows : period;
568     }
569 
570     void testSetup() throws IOException {
571       this.connection = HConnectionManager.createConnection(conf);
572       this.table = connection.getTable(opts.tableName);
573       this.table.setAutoFlush(false, true);
574     }
575 
576     void testTakedown() throws IOException {
577       if (opts.flushCommits) {
578         this.table.flushCommits();
579       }
580       table.close();
581       connection.close();
582     }
583 
584     /*
585      * Run test
586      * @return Elapsed time.
587      * @throws IOException
588      */
589     long test() throws IOException {
590       testSetup();
591       LOG.info("Timed test starting in thread " + Thread.currentThread().getName());
592       final long startTime = System.nanoTime();
593       try {
594         testTimed();
595       } finally {
596         testTakedown();
597       }
598       return (System.nanoTime() - startTime) / 1000000;
599     }
600 
601     /**
602      * Provides an extension point for tests that don't want a per row invocation.
603      */
604     void testTimed() throws IOException {
605       int lastRow = opts.startRow + opts.perClientRunRows;
606       // Report on completion of 1/10th of total.
607       for (int i = opts.startRow; i < lastRow; i++) {
608         testRow(i);
609         if (status != null && i > 0 && (i % getReportingPeriod()) == 0) {
610           status.setStatus(generateStatus(opts.startRow, i, lastRow));
611         }
612       }
613     }
614 
615     /*
616     * Test for individual row.
617     * @param i Row index.
618     */
619     abstract void testRow(final int i) throws IOException;
620   }
621 
622 
623   @SuppressWarnings("unused")
624   static class RandomSeekScanTest extends Test {
625     RandomSeekScanTest(Configuration conf, TestOptions options, Status status) {
626       super(conf, options, status);
627     }
628 
629     @Override
630     void testRow(final int i) throws IOException {
631       Scan scan = new Scan(getRandomRow(this.rand, opts.totalRows));
632       FilterList list = new FilterList();
633       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
634       if (opts.filterAll) {
635         list.addFilter(new FilterAllFilter());
636       }
637       list.addFilter(new WhileMatchFilter(new PageFilter(120)));
638       scan.setFilter(list);
639       ResultScanner s = this.table.getScanner(scan);
640       for (Result rr; (rr = s.next()) != null;) ;
641       s.close();
642     }
643 
644     @Override
645     protected int getReportingPeriod() {
646       int period = opts.perClientRunRows / 100;
647       return period == 0 ? opts.perClientRunRows : period;
648     }
649 
650   }
651 
652   @SuppressWarnings("unused")
653   static abstract class RandomScanWithRangeTest extends Test {
654     RandomScanWithRangeTest(Configuration conf, TestOptions options, Status status) {
655       super(conf, options, status);
656     }
657 
658     @Override
659     void testRow(final int i) throws IOException {
660       Pair<byte[], byte[]> startAndStopRow = getStartAndStopRow();
661       Scan scan = new Scan(startAndStopRow.getFirst(), startAndStopRow.getSecond());
662       if (opts.filterAll) {
663         scan.setFilter(new FilterAllFilter());
664       }
665       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
666       ResultScanner s = this.table.getScanner(scan);
667       int count = 0;
668       for (Result rr; (rr = s.next()) != null;) {
669         count++;
670       }
671 
672       if (i % 100 == 0) {
673         LOG.info(String.format("Scan for key range %s - %s returned %s rows",
674             Bytes.toString(startAndStopRow.getFirst()),
675             Bytes.toString(startAndStopRow.getSecond()), count));
676       }
677 
678       s.close();
679     }
680 
681     protected abstract Pair<byte[],byte[]> getStartAndStopRow();
682 
683     protected Pair<byte[], byte[]> generateStartAndStopRows(int maxRange) {
684       int start = this.rand.nextInt(Integer.MAX_VALUE) % opts.totalRows;
685       int stop = start + maxRange;
686       return new Pair<byte[],byte[]>(format(start), format(stop));
687     }
688 
689     @Override
690     protected int getReportingPeriod() {
691       int period = opts.perClientRunRows / 100;
692       return period == 0? opts.perClientRunRows: period;
693     }
694   }
695 
696   static class RandomScanWithRange10Test extends RandomScanWithRangeTest {
697     RandomScanWithRange10Test(Configuration conf, TestOptions options, Status status) {
698       super(conf, options, status);
699     }
700 
701     @Override
702     protected Pair<byte[], byte[]> getStartAndStopRow() {
703       return generateStartAndStopRows(10);
704     }
705   }
706 
707   static class RandomScanWithRange100Test extends RandomScanWithRangeTest {
708     RandomScanWithRange100Test(Configuration conf, TestOptions options, Status status) {
709       super(conf, options, status);
710     }
711 
712     @Override
713     protected Pair<byte[], byte[]> getStartAndStopRow() {
714       return generateStartAndStopRows(100);
715     }
716   }
717 
718   static class RandomScanWithRange1000Test extends RandomScanWithRangeTest {
719     RandomScanWithRange1000Test(Configuration conf, TestOptions options, Status status) {
720       super(conf, options, status);
721     }
722 
723     @Override
724     protected Pair<byte[], byte[]> getStartAndStopRow() {
725       return generateStartAndStopRows(1000);
726     }
727   }
728 
729   static class RandomScanWithRange10000Test extends RandomScanWithRangeTest {
730     RandomScanWithRange10000Test(Configuration conf, TestOptions options, Status status) {
731       super(conf, options, status);
732     }
733 
734     @Override
735     protected Pair<byte[], byte[]> getStartAndStopRow() {
736       return generateStartAndStopRows(10000);
737     }
738   }
739 
740   static class RandomReadTest extends Test {
741     private final int everyN;
742     private final double[] times;
743     private ArrayList<Get> gets;
744     int idx = 0;
745 
746     RandomReadTest(Configuration conf, TestOptions options, Status status) {
747       super(conf, options, status);
748       everyN = (int) (opts.totalRows / (opts.totalRows * opts.sampleRate));
749       LOG.info("Sampling 1 every " + everyN + " out of " + opts.perClientRunRows + " total rows.");
750       if (opts.multiGet > 0) {
751         LOG.info("MultiGet enabled. Sending GETs in batches of " + opts.multiGet + ".");
752         this.gets = new ArrayList<Get>(opts.multiGet);
753       }
754       if (opts.reportLatency) {
755         this.times = new double[(int) Math.ceil(opts.perClientRunRows * opts.sampleRate / Math.max(1, opts.multiGet))];
756       } else {
757         this.times = null;
758       }
759     }
760 
761     @Override
762     void testRow(final int i) throws IOException {
763       if (i % everyN == 0) {
764         Get get = new Get(getRandomRow(this.rand, opts.totalRows));
765         get.addColumn(FAMILY_NAME, QUALIFIER_NAME);
766         if (opts.filterAll) {
767           get.setFilter(new FilterAllFilter());
768         }
769         if (opts.multiGet > 0) {
770           this.gets.add(get);
771           if (this.gets.size() == opts.multiGet) {
772             long start = System.nanoTime();
773             this.table.get(this.gets);
774             if (opts.reportLatency) {
775               times[idx++] = (System.nanoTime() - start) / 1e6;
776             }
777             this.gets.clear();
778           }
779         } else {
780           long start = System.nanoTime();
781           this.table.get(get);
782           if (opts.reportLatency) {
783             times[idx++] = (System.nanoTime() - start) / 1e6;
784           }
785         }
786       }
787     }
788 
789     @Override
790     protected int getReportingPeriod() {
791       int period = opts.perClientRunRows / 100;
792       return period == 0 ? opts.perClientRunRows : period;
793     }
794 
795     @Override
796     protected void testTakedown() throws IOException {
797       if (this.gets != null && this.gets.size() > 0) {
798         this.table.get(gets);
799         this.gets.clear();
800       }
801       super.testTakedown();
802       if (opts.reportLatency) {
803         Arrays.sort(times);
804         DescriptiveStatistics ds = new DescriptiveStatistics();
805         for (double t : times) {
806           ds.addValue(t);
807         }
808         LOG.info("randomRead latency log (ms), on " + times.length + " measures");
809         LOG.info("99.9999% = " + ds.getPercentile(99.9999d));
810         LOG.info(" 99.999% = " + ds.getPercentile(99.999d));
811         LOG.info("  99.99% = " + ds.getPercentile(99.99d));
812         LOG.info("   99.9% = " + ds.getPercentile(99.9d));
813         LOG.info("     99% = " + ds.getPercentile(99d));
814         LOG.info("     95% = " + ds.getPercentile(95d));
815         LOG.info("     90% = " + ds.getPercentile(90d));
816         LOG.info("     80% = " + ds.getPercentile(80d));
817         LOG.info("Standard Deviation = " + ds.getStandardDeviation());
818         LOG.info("Mean = " + ds.getMean());
819       }
820     }
821   }
822 
823   static class RandomWriteTest extends Test {
824     RandomWriteTest(Configuration conf, TestOptions options, Status status) {
825       super(conf, options, status);
826     }
827 
828     @Override
829     void testRow(final int i) throws IOException {
830       byte[] row = getRandomRow(this.rand, opts.totalRows);
831       Put put = new Put(row);
832       byte[] value = generateData(this.rand, VALUE_LENGTH);
833       if (opts.useTags) {
834         byte[] tag = generateData(this.rand, TAG_LENGTH);
835         Tag[] tags = new Tag[opts.noOfTags];
836         for (int n = 0; n < opts.noOfTags; n++) {
837           Tag t = new Tag((byte) n, tag);
838           tags[n] = t;
839         }
840         KeyValue kv = new KeyValue(row, FAMILY_NAME, QUALIFIER_NAME, HConstants.LATEST_TIMESTAMP,
841             value, tags);
842         put.add(kv);
843       } else {
844         put.add(FAMILY_NAME, QUALIFIER_NAME, value);
845       }
846       put.setDurability(opts.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
847       table.put(put);
848     }
849   }
850 
851 
852   static class ScanTest extends Test {
853     private ResultScanner testScanner;
854 
855     ScanTest(Configuration conf, TestOptions options, Status status) {
856       super(conf, options, status);
857     }
858 
859     @Override
860     void testTakedown() throws IOException {
861       if (this.testScanner != null) {
862         this.testScanner.close();
863       }
864       super.testTakedown();
865     }
866 
867 
868     @Override
869     void testRow(final int i) throws IOException {
870       if (this.testScanner == null) {
871         Scan scan = new Scan(format(opts.startRow));
872         scan.setCaching(30);
873         scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
874         if (opts.filterAll) {
875           scan.setFilter(new FilterAllFilter());
876         }
877        this.testScanner = table.getScanner(scan);
878       }
879       testScanner.next();
880     }
881 
882   }
883 
884   static class SequentialReadTest extends Test {
885     SequentialReadTest(Configuration conf, TestOptions options, Status status) {
886       super(conf, options, status);
887     }
888 
889     @Override
890     void testRow(final int i) throws IOException {
891       Get get = new Get(format(i));
892       get.addColumn(FAMILY_NAME, QUALIFIER_NAME);
893       if (opts.filterAll) {
894         get.setFilter(new FilterAllFilter());
895       }
896       table.get(get);
897     }
898   }
899 
900   static class SequentialWriteTest extends Test {
901     SequentialWriteTest(Configuration conf, TestOptions options, Status status) {
902       super(conf, options, status);
903     }
904 
905     @Override
906     void testRow(final int i) throws IOException {
907       byte[] row = format(i);
908       Put put = new Put(row);
909       byte[] value = generateData(this.rand, VALUE_LENGTH);
910       if (opts.useTags) {
911         byte[] tag = generateData(this.rand, TAG_LENGTH);
912         Tag[] tags = new Tag[opts.noOfTags];
913         for (int n = 0; n < opts.noOfTags; n++) {
914           Tag t = new Tag((byte) n, tag);
915           tags[n] = t;
916         }
917         KeyValue kv = new KeyValue(row, FAMILY_NAME, QUALIFIER_NAME, HConstants.LATEST_TIMESTAMP,
918             value, tags);
919         put.add(kv);
920       } else {
921         put.add(FAMILY_NAME, QUALIFIER_NAME, value);
922       }
923       put.setDurability(opts.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
924       table.put(put);
925     }
926   }
927 
928   static class FilteredScanTest extends Test {
929     protected static final Log LOG = LogFactory.getLog(FilteredScanTest.class.getName());
930 
931     FilteredScanTest(Configuration conf, TestOptions options, Status status) {
932       super(conf, options, status);
933     }
934 
935     @Override
936     void testRow(int i) throws IOException {
937       byte[] value = generateData(this.rand, VALUE_LENGTH);
938       Scan scan = constructScan(value);
939       ResultScanner scanner = null;
940       try {
941         scanner = this.table.getScanner(scan);
942         while (scanner.next() != null) {
943         }
944       } finally {
945         if (scanner != null) scanner.close();
946       }
947     }
948 
949     protected Scan constructScan(byte[] valuePrefix) throws IOException {
950       FilterList list = new FilterList();
951       Filter filter = new SingleColumnValueFilter(
952           FAMILY_NAME, QUALIFIER_NAME, CompareFilter.CompareOp.EQUAL,
953           new BinaryComparator(valuePrefix)
954       );
955       list.addFilter(filter);
956       if(opts.filterAll) {
957         list.addFilter(new FilterAllFilter());
958       }
959       Scan scan = new Scan();
960       scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);
961       scan.setFilter(list);
962       return scan;
963     }
964   }
965 
966   /**
967    * Compute a throughput rate in MB/s.
968    * @param rows Number of records consumed.
969    * @param timeMs Time taken in milliseconds.
970    * @return String value with label, ie '123.76 MB/s'
971    */
972   private static String calculateMbps(int rows, long timeMs) {
973     // MB/s = ((totalRows * ROW_SIZE_BYTES) / totalTimeMS)
974     //        * 1000 MS_PER_SEC / (1024 * 1024) BYTES_PER_MB
975     BigDecimal rowSize =
976       BigDecimal.valueOf(ROW_LENGTH + VALUE_LENGTH + FAMILY_NAME.length + QUALIFIER_NAME.length);
977     BigDecimal mbps = BigDecimal.valueOf(rows).multiply(rowSize, CXT)
978       .divide(BigDecimal.valueOf(timeMs), CXT).multiply(MS_PER_SEC, CXT)
979       .divide(BYTES_PER_MB, CXT);
980     return FMT.format(mbps) + " MB/s";
981   }
982 
983   /*
984    * Format passed integer.
985    * @param number
986    * @return Returns zero-prefixed ROW_LENGTH-byte wide decimal version of passed
987    * number (Does absolute in case number is negative).
988    */
989   public static byte [] format(final int number) {
990     byte [] b = new byte[ROW_LENGTH];
991     int d = Math.abs(number);
992     for (int i = b.length - 1; i >= 0; i--) {
993       b[i] = (byte)((d % 10) + '0');
994       d /= 10;
995     }
996     return b;
997   }
998 
999   /*
1000    * This method takes some time and is done inline uploading data.  For
1001    * example, doing the mapfile test, generation of the key and value
1002    * consumes about 30% of CPU time.
1003    * @return Generated random value to insert into a table cell.
1004    */
1005   public static byte[] generateData(final Random r, int length) {
1006     byte [] b = new byte [length];
1007     int i = 0;
1008 
1009     for(i = 0; i < (length-8); i += 8) {
1010       b[i] = (byte) (65 + r.nextInt(26));
1011       b[i+1] = b[i];
1012       b[i+2] = b[i];
1013       b[i+3] = b[i];
1014       b[i+4] = b[i];
1015       b[i+5] = b[i];
1016       b[i+6] = b[i];
1017       b[i+7] = b[i];
1018     }
1019 
1020     byte a = (byte) (65 + r.nextInt(26));
1021     for(; i < length; i++) {
1022       b[i] = a;
1023     }
1024     return b;
1025   }
1026 
1027   /**
1028    * @deprecated Use {@link #generateData(java.util.Random, int)} instead.
1029    * @return Generated random value to insert into a table cell.
1030    */
1031   @Deprecated
1032   public static byte[] generateValue(final Random r) {
1033     return generateData(r, VALUE_LENGTH);
1034   }
1035 
1036   static byte [] getRandomRow(final Random random, final int totalRows) {
1037     return format(random.nextInt(Integer.MAX_VALUE) % totalRows);
1038   }
1039 
1040   static long runOneClient(final Class<? extends Test> cmd, Configuration conf, TestOptions opts,
1041     final Status status)
1042       throws IOException {
1043     status.setStatus("Start " + cmd + " at offset " + opts.startRow + " for " +
1044       opts.perClientRunRows + " rows");
1045     long totalElapsedTime = 0;
1046 
1047     final Test t;
1048     try {
1049       Constructor<? extends Test> constructor =
1050         cmd.getDeclaredConstructor(Configuration.class, TestOptions.class, Status.class);
1051       t = constructor.newInstance(conf, opts, status);
1052     } catch (NoSuchMethodException e) {
1053       throw new IllegalArgumentException("Invalid command class: " +
1054           cmd.getName() + ".  It does not provide a constructor as described by " +
1055           "the javadoc comment.  Available constructors are: " +
1056           Arrays.toString(cmd.getConstructors()));
1057     } catch (Exception e) {
1058       throw new IllegalStateException("Failed to construct command class", e);
1059     }
1060     totalElapsedTime = t.test();
1061 
1062     status.setStatus("Finished " + cmd + " in " + totalElapsedTime +
1063       "ms at offset " + opts.startRow + " for " + opts.perClientRunRows + " rows" +
1064       " (" + calculateMbps((int)(opts.perClientRunRows * opts.sampleRate), totalElapsedTime) + ")");
1065     return totalElapsedTime;
1066   }
1067 
1068   private void runTest(final Class<? extends Test> cmd, TestOptions opts) throws IOException,
1069       InterruptedException, ClassNotFoundException {
1070     HBaseAdmin admin = null;
1071     try {
1072       admin = new HBaseAdmin(getConf());
1073       checkTable(admin, opts);
1074     } finally {
1075       if (admin != null) admin.close();
1076     }
1077     if (opts.nomapred) {
1078       doLocalClients(cmd, opts);
1079     } else {
1080       doMapReduce(cmd, opts);
1081     }
1082   }
1083 
1084   protected void printUsage() {
1085     printUsage(null);
1086   }
1087 
1088   protected void printUsage(final String message) {
1089     if (message != null && message.length() > 0) {
1090       System.err.println(message);
1091     }
1092     System.err.println("Usage: java " + this.getClass().getName() + " \\");
1093     System.err.println("  [--nomapred] [--rows=ROWS] [--table=NAME] \\");
1094     System.err.println("  [--compress=TYPE] [--blockEncoding=TYPE] " +
1095       "[-D<property=value>]* <command> <nclients>");
1096     System.err.println();
1097     System.err.println("Options:");
1098     System.err.println(" nomapred        Run multiple clients using threads " +
1099       "(rather than use mapreduce)");
1100     System.err.println(" rows            Rows each client runs. Default: One million");
1101     System.err.println(" sampleRate      Execute test on a sample of total " +
1102       "rows. Only supported by randomRead. Default: 1.0");
1103     System.err.println(" table           Alternate table name. Default: 'TestTable'");
1104     System.err.println(" compress        Compression type to use (GZ, LZO, ...). Default: 'NONE'");
1105     System.err.println(" flushCommits    Used to determine if the test should flush the table. " +
1106       "Default: false");
1107     System.err.println(" writeToWAL      Set writeToWAL on puts. Default: True");
1108     System.err.println(" presplit        Create presplit table. Recommended for accurate perf " +
1109       "analysis (see guide).  Default: disabled");
1110     System.err.println(" inmemory        Tries to keep the HFiles of the CF " +
1111       "inmemory as far as possible. Not guaranteed that reads are always served " +
1112       "from memory.  Default: false");
1113     System.err.println(" usetags         Writes tags along with KVs. Use with HFile V3. " +
1114       "Default: false");
1115     System.err.println(" numoftags       Specify the no of tags that would be needed. " +
1116        "This works only if usetags is true.");
1117     System.err.println(" filterAll       Helps to filter out all the rows on the server side"
1118         + " there by not returning any thing back to the client.  Helps to check the server side"
1119         + " performance.  Uses FilterAllFilter internally. ");
1120     System.err.println(" latency         Set to report operation latencies. " +
1121       "Currently only supported by randomRead test. Default: False");
1122     System.err.println(" bloomFilter      Bloom filter type, one of " + Arrays.toString(BloomType.values()));
1123     System.err.println();
1124     System.err.println(" Note: -D properties will be applied to the conf used. ");
1125     System.err.println("  For example: ");
1126     System.err.println("   -Dmapred.output.compress=true");
1127     System.err.println("   -Dmapreduce.task.timeout=60000");
1128     System.err.println();
1129     System.err.println("Command:");
1130     for (CmdDescriptor command : commands.values()) {
1131       System.err.println(String.format(" %-15s %s", command.getName(), command.getDescription()));
1132     }
1133     System.err.println();
1134     System.err.println("Args:");
1135     System.err.println(" nclients        Integer. Required. Total number of " +
1136       "clients (and HRegionServers)");
1137     System.err.println("                 running: 1 <= value <= 500");
1138     System.err.println("Examples:");
1139     System.err.println(" To run a single evaluation client:");
1140     System.err.println(" $ bin/hbase " + this.getClass().getName()
1141         + " sequentialWrite 1");
1142   }
1143 
1144   private static int getNumClients(final int start, final String[] args) {
1145     if(start + 1 > args.length) {
1146       throw new IllegalArgumentException("must supply the number of clients");
1147     }
1148     int N = Integer.parseInt(args[start]);
1149     if (N < 1) {
1150       throw new IllegalArgumentException("Number of clients must be > 1");
1151     }
1152     return N;
1153   }
1154 
1155   public int run(String[] args) throws Exception {
1156     // Process command-line args. TODO: Better cmd-line processing
1157     // (but hopefully something not as painful as cli options).
1158     int errCode = -1;
1159     if (args.length < 1) {
1160       printUsage();
1161       return errCode;
1162     }
1163 
1164     try {
1165       // MR-NOTE: if you are adding a property that is used to control an operation
1166       // like put(), get(), scan(), ... you must also add it as part of the MR 
1167       // input, take a look at writeInputFile().
1168       // Then you must adapt the LINE_PATTERN input regex,
1169       // and parse the argument, take a look at PEInputFormat.getSplits().
1170 
1171       TestOptions opts = new TestOptions();
1172 
1173       for (int i = 0; i < args.length; i++) {
1174         String cmd = args[i];
1175         if (cmd.equals("-h") || cmd.startsWith("--h")) {
1176           printUsage();
1177           errCode = 0;
1178           break;
1179         }
1180 
1181         final String nmr = "--nomapred";
1182         if (cmd.startsWith(nmr)) {
1183           opts.nomapred = true;
1184           continue;
1185         }
1186 
1187         final String rows = "--rows=";
1188         if (cmd.startsWith(rows)) {
1189           opts.perClientRunRows = Integer.parseInt(cmd.substring(rows.length()));
1190           continue;
1191         }
1192 
1193         final String sampleRate = "--sampleRate=";
1194         if (cmd.startsWith(sampleRate)) {
1195           opts.sampleRate = Float.parseFloat(cmd.substring(sampleRate.length()));
1196           continue;
1197         }
1198 
1199         final String table = "--table=";
1200         if (cmd.startsWith(table)) {
1201           opts.tableName = cmd.substring(table.length());
1202           continue;
1203         }
1204 
1205         final String compress = "--compress=";
1206         if (cmd.startsWith(compress)) {
1207           opts.compression = Compression.Algorithm.valueOf(cmd.substring(compress.length()));
1208           continue;
1209         }
1210 
1211         final String blockEncoding = "--blockEncoding=";
1212         if (cmd.startsWith(blockEncoding)) {
1213           opts.blockEncoding = DataBlockEncoding.valueOf(cmd.substring(blockEncoding.length()));
1214           continue;
1215         }
1216 
1217         final String flushCommits = "--flushCommits=";
1218         if (cmd.startsWith(flushCommits)) {
1219           opts.flushCommits = Boolean.parseBoolean(cmd.substring(flushCommits.length()));
1220           continue;
1221         }
1222 
1223         final String writeToWAL = "--writeToWAL=";
1224         if (cmd.startsWith(writeToWAL)) {
1225           opts.writeToWAL = Boolean.parseBoolean(cmd.substring(writeToWAL.length()));
1226           continue;
1227         }
1228 
1229         final String presplit = "--presplit=";
1230         if (cmd.startsWith(presplit)) {
1231           opts.presplitRegions = Integer.parseInt(cmd.substring(presplit.length()));
1232           continue;
1233         }
1234 
1235         final String inMemory = "--inmemory=";
1236         if (cmd.startsWith(inMemory)) {
1237           opts.inMemoryCF = Boolean.parseBoolean(cmd.substring(inMemory.length()));
1238           continue;
1239         }
1240 
1241         final String latency = "--latency";
1242         if (cmd.startsWith(latency)) {
1243           opts.reportLatency = true;
1244           continue;
1245         }
1246 
1247         final String multiGet = "--multiGet=";
1248         if (cmd.startsWith(multiGet)) {
1249           opts.multiGet = Integer.parseInt(cmd.substring(multiGet.length()));
1250           continue;
1251         }
1252 
1253         final String useTags = "--usetags=";
1254         if (cmd.startsWith(useTags)) {
1255           opts.useTags = Boolean.parseBoolean(cmd.substring(useTags.length()));
1256           continue;
1257         }
1258         
1259         final String noOfTags = "--nooftags=";
1260         if (cmd.startsWith(noOfTags)) {
1261           opts.noOfTags = Integer.parseInt(cmd.substring(noOfTags.length()));
1262           continue;
1263         }
1264 
1265         final String filterOutAll = "--filterAll";
1266         if (cmd.startsWith(filterOutAll)) {
1267           opts.filterAll = true;
1268           continue;
1269         }
1270 
1271         final String bloomFilter = "--bloomFilter";
1272         if (cmd.startsWith(bloomFilter)) {
1273           opts.bloomType = BloomType.valueOf(cmd.substring(bloomFilter.length()));
1274           continue;
1275         }
1276 
1277         Class<? extends Test> cmdClass = determineCommandClass(cmd);
1278         if (cmdClass != null) {
1279           opts.numClientThreads = getNumClients(i + 1, args);
1280           // number of rows specified
1281           opts.totalRows = opts.perClientRunRows * opts.numClientThreads;
1282           runTest(cmdClass, opts);
1283           errCode = 0;
1284           break;
1285         }
1286 
1287         printUsage();
1288         break;
1289       }
1290     } catch (Exception e) {
1291       e.printStackTrace();
1292     }
1293 
1294     return errCode;
1295   }
1296 
1297   private Class<? extends Test> determineCommandClass(String cmd) {
1298     CmdDescriptor descriptor = commands.get(cmd);
1299     return descriptor != null ? descriptor.getCmdClass() : null;
1300   }
1301 
1302   public static void main(final String[] args) throws Exception {
1303     int res = ToolRunner.run(new PerformanceEvaluation(HBaseConfiguration.create()), args);
1304     System.exit(res);
1305   }
1306 }