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