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