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