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