1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.hadoop.hbase.regionserver;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Random;
25 import java.util.SortedSet;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.ConcurrentSkipListSet;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.ExecutorCompletionService;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.Future;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.atomic.AtomicLong;
35
36 import org.apache.commons.cli.CommandLine;
37 import org.apache.commons.cli.CommandLineParser;
38 import org.apache.commons.cli.HelpFormatter;
39 import org.apache.commons.cli.Option;
40 import org.apache.commons.cli.OptionGroup;
41 import org.apache.commons.cli.Options;
42 import org.apache.commons.cli.ParseException;
43 import org.apache.commons.cli.PosixParser;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.apache.hadoop.conf.Configuration;
47 import org.apache.hadoop.fs.FileSystem;
48 import org.apache.hadoop.fs.Path;
49 import org.apache.hadoop.hbase.HBaseConfiguration;
50 import org.apache.hadoop.hbase.HColumnDescriptor;
51 import org.apache.hadoop.hbase.HRegionInfo;
52 import org.apache.hadoop.hbase.HTableDescriptor;
53 import org.apache.hadoop.hbase.KeyValue;
54 import org.apache.hadoop.hbase.TableName;
55 import org.apache.hadoop.hbase.client.Scan;
56 import org.apache.hadoop.hbase.io.compress.Compression;
57 import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
58 import org.apache.hadoop.hbase.io.hfile.BlockCache;
59 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
60 import org.apache.hadoop.hbase.io.hfile.HFile;
61 import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
62 import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl;
63 import org.apache.hadoop.hbase.io.hfile.HFilePrettyPrinter;
64 import org.apache.hadoop.hbase.io.hfile.NoOpDataBlockEncoder;
65 import org.apache.hadoop.hbase.util.Bytes;
66 import org.apache.hadoop.hbase.util.LoadTestTool;
67 import org.apache.hadoop.hbase.util.MD5Hash;
68 import org.apache.hadoop.util.StringUtils;
69
70
71
72
73 public class HFileReadWriteTest {
74
75 private static final String TABLE_NAME = "MyTable";
76
77 private static enum Workload {
78 MERGE("merge", "Merge the specified HFiles", 1, Integer.MAX_VALUE),
79 RANDOM_READS("read", "Perform a random read benchmark on the given HFile",
80 1, 1);
81
82 private String option;
83 private String description;
84
85 public final int minNumInputFiles;
86 public final int maxNumInputFiles;
87
88 Workload(String option, String description, int minNumInputFiles,
89 int maxNumInputFiles) {
90 this.option = option;
91 this.description = description;
92 this.minNumInputFiles = minNumInputFiles;
93 this.maxNumInputFiles = maxNumInputFiles;
94 }
95
96 static OptionGroup getOptionGroup() {
97 OptionGroup optionGroup = new OptionGroup();
98 for (Workload w : values())
99 optionGroup.addOption(new Option(w.option, w.description));
100 return optionGroup;
101 }
102
103 private static String getOptionListStr() {
104 StringBuilder sb = new StringBuilder();
105 for (Workload w : values()) {
106 if (sb.length() > 0)
107 sb.append(", ");
108 sb.append("-" + w.option);
109 }
110 return sb.toString();
111 }
112
113 static Workload fromCmdLine(CommandLine cmdLine) {
114 for (Workload w : values()) {
115 if (cmdLine.hasOption(w.option))
116 return w;
117 }
118 LOG.error("No workload specified. Specify one of the options: " +
119 getOptionListStr());
120 return null;
121 }
122
123 public String onlyUsedFor() {
124 return ". Only used for the " + this + " workload.";
125 }
126 }
127
128 private static final String OUTPUT_DIR_OPTION = "output_dir";
129 private static final String COMPRESSION_OPTION = "compression";
130 private static final String BLOOM_FILTER_OPTION = "bloom";
131 private static final String BLOCK_SIZE_OPTION = "block_size";
132 private static final String DURATION_OPTION = "duration";
133 private static final String NUM_THREADS_OPTION = "num_threads";
134
135 private static final Log LOG = LogFactory.getLog(HFileReadWriteTest.class);
136
137 private Workload workload;
138 private FileSystem fs;
139 private Configuration conf;
140 private CacheConfig cacheConf;
141 private List<String> inputFileNames;
142 private Path outputDir;
143 private int numReadThreads;
144 private int durationSec;
145 private DataBlockEncoding dataBlockEncoding;
146 private boolean encodeInCacheOnly;
147 private HFileDataBlockEncoder dataBlockEncoder =
148 NoOpDataBlockEncoder.INSTANCE;
149
150 private BloomType bloomType = BloomType.NONE;
151 private int blockSize;
152 private Compression.Algorithm compression = Compression.Algorithm.NONE;
153
154 private byte[] firstRow, lastRow;
155
156 private AtomicLong numSeeks = new AtomicLong();
157 private AtomicLong numKV = new AtomicLong();
158 private AtomicLong totalBytes = new AtomicLong();
159
160 private byte[] family;
161
162 private long endTime = Long.MAX_VALUE;
163
164 private SortedSet<String> keysRead = new ConcurrentSkipListSet<String>();
165 private List<StoreFile> inputStoreFiles;
166
167 public HFileReadWriteTest() {
168 conf = HBaseConfiguration.create();
169 cacheConf = new CacheConfig(conf);
170 }
171
172 @SuppressWarnings("unchecked")
173 public boolean parseOptions(String args[]) {
174
175 Options options = new Options();
176 options.addOption(OUTPUT_DIR_OPTION, true, "Output directory" +
177 Workload.MERGE.onlyUsedFor());
178 options.addOption(COMPRESSION_OPTION, true, " Compression type, one of "
179 + Arrays.toString(Compression.Algorithm.values()) +
180 Workload.MERGE.onlyUsedFor());
181 options.addOption(BLOOM_FILTER_OPTION, true, "Bloom filter type, one of "
182 + Arrays.toString(BloomType.values()) +
183 Workload.MERGE.onlyUsedFor());
184 options.addOption(BLOCK_SIZE_OPTION, true, "HFile block size" +
185 Workload.MERGE.onlyUsedFor());
186 options.addOption(DURATION_OPTION, true, "The amount of time to run the " +
187 "random read workload for" + Workload.RANDOM_READS.onlyUsedFor());
188 options.addOption(NUM_THREADS_OPTION, true, "The number of random " +
189 "reader threads" + Workload.RANDOM_READS.onlyUsedFor());
190 options.addOption(NUM_THREADS_OPTION, true, "The number of random " +
191 "reader threads" + Workload.RANDOM_READS.onlyUsedFor());
192 options.addOption(LoadTestTool.OPT_DATA_BLOCK_ENCODING, true,
193 LoadTestTool.OPT_DATA_BLOCK_ENCODING_USAGE);
194 options.addOption(LoadTestTool.OPT_ENCODE_IN_CACHE_ONLY, false,
195 LoadTestTool.OPT_ENCODE_IN_CACHE_ONLY_USAGE);
196 options.addOptionGroup(Workload.getOptionGroup());
197
198 if (args.length == 0) {
199 HelpFormatter formatter = new HelpFormatter();
200 formatter.printHelp(HFileReadWriteTest.class.getSimpleName(),
201 options, true);
202 return false;
203 }
204
205 CommandLineParser parser = new PosixParser();
206 CommandLine cmdLine;
207 try {
208 cmdLine = parser.parse(options, args);
209 } catch (ParseException ex) {
210 LOG.error(ex);
211 return false;
212 }
213
214 workload = Workload.fromCmdLine(cmdLine);
215 if (workload == null)
216 return false;
217
218 inputFileNames = (List<String>) cmdLine.getArgList();
219
220 if (inputFileNames.size() == 0) {
221 LOG.error("No input file names specified");
222 return false;
223 }
224
225 if (inputFileNames.size() < workload.minNumInputFiles) {
226 LOG.error("Too few input files: at least " + workload.minNumInputFiles +
227 " required");
228 return false;
229 }
230
231 if (inputFileNames.size() > workload.maxNumInputFiles) {
232 LOG.error("Too many input files: at most " + workload.minNumInputFiles +
233 " allowed");
234 return false;
235 }
236
237 if (cmdLine.hasOption(COMPRESSION_OPTION)) {
238 compression = Compression.Algorithm.valueOf(
239 cmdLine.getOptionValue(COMPRESSION_OPTION));
240 }
241
242 if (cmdLine.hasOption(BLOOM_FILTER_OPTION)) {
243 bloomType = BloomType.valueOf(cmdLine.getOptionValue(
244 BLOOM_FILTER_OPTION));
245 }
246
247 encodeInCacheOnly =
248 cmdLine.hasOption(LoadTestTool.OPT_ENCODE_IN_CACHE_ONLY);
249
250 if (cmdLine.hasOption(LoadTestTool.OPT_DATA_BLOCK_ENCODING)) {
251 dataBlockEncoding = DataBlockEncoding.valueOf(
252 cmdLine.getOptionValue(LoadTestTool.OPT_DATA_BLOCK_ENCODING));
253
254 dataBlockEncoder = new HFileDataBlockEncoderImpl(
255 encodeInCacheOnly ? DataBlockEncoding.NONE : dataBlockEncoding,
256 dataBlockEncoding);
257 } else {
258 if (encodeInCacheOnly) {
259 LOG.error("The -" + LoadTestTool.OPT_ENCODE_IN_CACHE_ONLY +
260 " option does not make sense without -" +
261 LoadTestTool.OPT_DATA_BLOCK_ENCODING);
262 return false;
263 }
264 }
265
266 blockSize = conf.getInt("hfile.min.blocksize.size", 65536);
267 if (cmdLine.hasOption(BLOCK_SIZE_OPTION))
268 blockSize = Integer.valueOf(cmdLine.getOptionValue(BLOCK_SIZE_OPTION));
269
270 if (workload == Workload.MERGE) {
271 String outputDirStr = cmdLine.getOptionValue(OUTPUT_DIR_OPTION);
272 if (outputDirStr == null) {
273 LOG.error("Output directory is not specified");
274 return false;
275 }
276 outputDir = new Path(outputDirStr);
277
278 }
279
280 if (workload == Workload.RANDOM_READS) {
281 if (!requireOptions(cmdLine, new String[] { DURATION_OPTION,
282 NUM_THREADS_OPTION })) {
283 return false;
284 }
285
286 durationSec = Integer.parseInt(cmdLine.getOptionValue(DURATION_OPTION));
287 numReadThreads = Integer.parseInt(
288 cmdLine.getOptionValue(NUM_THREADS_OPTION));
289 }
290
291 Collections.sort(inputFileNames);
292
293 return true;
294 }
295
296
297 private boolean requireOptions(CommandLine cmdLine,
298 String[] requiredOptions) {
299 for (String option : requiredOptions)
300 if (!cmdLine.hasOption(option)) {
301 LOG.error("Required option -" + option + " not specified");
302 return false;
303 }
304 return true;
305 }
306
307 public boolean validateConfiguration() throws IOException {
308 fs = FileSystem.get(conf);
309
310 for (String inputFileName : inputFileNames) {
311 Path path = new Path(inputFileName);
312 if (!fs.exists(path)) {
313 LOG.error("File " + inputFileName + " does not exist");
314 return false;
315 }
316
317 if (fs.getFileStatus(path).isDir()) {
318 LOG.error(inputFileName + " is a directory");
319 return false;
320 }
321 }
322
323 if (outputDir != null &&
324 (!fs.exists(outputDir) || !fs.getFileStatus(outputDir).isDir())) {
325 LOG.error(outputDir.toString() + " does not exist or is not a " +
326 "directory");
327 return false;
328 }
329
330 return true;
331 }
332
333 public void runMergeWorkload() throws IOException {
334 long maxKeyCount = prepareForMerge();
335
336 List<StoreFileScanner> scanners =
337 StoreFileScanner.getScannersForStoreFiles(inputStoreFiles, false,
338 false);
339
340 HColumnDescriptor columnDescriptor = new HColumnDescriptor(
341 HFileReadWriteTest.class.getSimpleName());
342 columnDescriptor.setBlocksize(blockSize);
343 columnDescriptor.setBloomFilterType(bloomType);
344 columnDescriptor.setCompressionType(compression);
345 columnDescriptor.setDataBlockEncoding(dataBlockEncoding);
346 HRegionInfo regionInfo = new HRegionInfo();
347 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
348 HRegion region = new HRegion(outputDir, null, fs, conf, regionInfo, htd, null);
349 HStore store = new HStore(region, columnDescriptor, conf);
350
351 StoreFile.Writer writer = store.createWriterInTmp(maxKeyCount, compression, false, true);
352
353 StatisticsPrinter statsPrinter = new StatisticsPrinter();
354 statsPrinter.startThread();
355
356 try {
357 performMerge(scanners, store, writer);
358 writer.close();
359 } finally {
360 statsPrinter.requestStop();
361 }
362
363 Path resultPath = writer.getPath();
364
365 resultPath = tryUsingSimpleOutputPath(resultPath);
366
367 long fileSize = fs.getFileStatus(resultPath).getLen();
368 LOG.info("Created " + resultPath + ", size " + fileSize);
369
370 System.out.println();
371 System.out.println("HFile information for " + resultPath);
372 System.out.println();
373
374 HFilePrettyPrinter hfpp = new HFilePrettyPrinter();
375 hfpp.run(new String[] { "-m", "-f", resultPath.toString() });
376 }
377
378 private Path tryUsingSimpleOutputPath(Path resultPath) throws IOException {
379 if (inputFileNames.size() == 1) {
380
381
382
383 Path inputPath = new Path(inputFileNames.get(0));
384 Path betterOutputPath = new Path(outputDir,
385 inputPath.getName());
386 if (!fs.exists(betterOutputPath)) {
387 fs.rename(resultPath, betterOutputPath);
388 resultPath = betterOutputPath;
389 }
390 }
391 return resultPath;
392 }
393
394 private void performMerge(List<StoreFileScanner> scanners, HStore store,
395 StoreFile.Writer writer) throws IOException {
396 InternalScanner scanner = null;
397 try {
398 Scan scan = new Scan();
399
400
401 scanner = new StoreScanner(store, store.getScanInfo(), scan, scanners,
402 ScanType.COMPACT_DROP_DELETES, Long.MIN_VALUE, Long.MIN_VALUE);
403
404 ArrayList<KeyValue> kvs = new ArrayList<KeyValue>();
405
406 while (scanner.next(kvs) || kvs.size() != 0) {
407 numKV.addAndGet(kvs.size());
408 for (KeyValue kv : kvs) {
409 totalBytes.addAndGet(kv.getLength());
410 writer.append(kv);
411 }
412 kvs.clear();
413 }
414 } finally {
415 if (scanner != null)
416 scanner.close();
417 }
418 }
419
420
421
422
423
424 private long prepareForMerge() throws IOException {
425 LOG.info("Merging " + inputFileNames);
426 LOG.info("Using block size: " + blockSize);
427 inputStoreFiles = new ArrayList<StoreFile>();
428
429 long maxKeyCount = 0;
430 for (String fileName : inputFileNames) {
431 Path filePath = new Path(fileName);
432
433
434 StoreFile sf = openStoreFile(filePath, false);
435 sf.createReader();
436 inputStoreFiles.add(sf);
437
438 StoreFile.Reader r = sf.getReader();
439 if (r != null) {
440 long keyCount = r.getFilterEntries();
441 maxKeyCount += keyCount;
442 LOG.info("Compacting: " + sf + "; keyCount = " + keyCount
443 + "; Bloom Type = " + r.getBloomFilterType().toString()
444 + "; Size = " + StringUtils.humanReadableInt(r.length()));
445 }
446 }
447 return maxKeyCount;
448 }
449
450 public HFile.Reader[] getHFileReaders() {
451 HFile.Reader readers[] = new HFile.Reader[inputStoreFiles.size()];
452 for (int i = 0; i < inputStoreFiles.size(); ++i)
453 readers[i] = inputStoreFiles.get(i).getReader().getHFileReader();
454 return readers;
455 }
456
457 private StoreFile openStoreFile(Path filePath, boolean blockCache)
458 throws IOException {
459
460
461 return new StoreFile(fs, filePath, conf, cacheConf,
462 BloomType.ROWCOL, dataBlockEncoder);
463 }
464
465 public static int charToHex(int c) {
466 if ('0' <= c && c <= '9')
467 return c - '0';
468 if ('a' <= c && c <= 'f')
469 return 10 + c - 'a';
470 return -1;
471 }
472
473 public static int hexToChar(int h) {
474 h &= 0xff;
475 if (0 <= h && h <= 9)
476 return '0' + h;
477 if (10 <= h && h <= 15)
478 return 'a' + h - 10;
479 return -1;
480 }
481
482 public static byte[] createRandomRow(Random rand, byte[] first, byte[] last)
483 {
484 int resultLen = Math.max(first.length, last.length);
485 int minLen = Math.min(first.length, last.length);
486 byte[] result = new byte[resultLen];
487 boolean greaterThanFirst = false;
488 boolean lessThanLast = false;
489
490 for (int i = 0; i < resultLen; ++i) {
491
492
493 boolean isHex = i < minLen && charToHex(first[i]) != -1
494 && charToHex(last[i]) != -1;
495
496
497
498 int low = greaterThanFirst || i >= first.length ? 0 : first[i] & 0xff;
499
500
501
502 int high = lessThanLast || i >= last.length ? 0xff : last[i] & 0xff;
503
504
505
506
507
508
509 int r;
510 if (isHex) {
511
512 if (low < '0')
513 low = '0';
514
515 if (high > 'f')
516 high = 'f';
517
518 int lowHex = charToHex(low);
519 int highHex = charToHex(high);
520 r = hexToChar(lowHex + rand.nextInt(highHex - lowHex + 1));
521 } else {
522 r = low + rand.nextInt(high - low + 1);
523 }
524
525 if (r > low)
526 greaterThanFirst = true;
527
528 if (r < high)
529 lessThanLast = true;
530
531 result[i] = (byte) r;
532 }
533
534 if (Bytes.compareTo(result, first) < 0) {
535 throw new IllegalStateException("Generated key " +
536 Bytes.toStringBinary(result) + " is less than the first key " +
537 Bytes.toStringBinary(first));
538 }
539
540 if (Bytes.compareTo(result, last) > 0) {
541 throw new IllegalStateException("Generated key " +
542 Bytes.toStringBinary(result) + " is greater than te last key " +
543 Bytes.toStringBinary(last));
544 }
545
546 return result;
547 }
548
549 private static byte[] createRandomQualifier(Random rand) {
550 byte[] q = new byte[10 + rand.nextInt(30)];
551 rand.nextBytes(q);
552 return q;
553 }
554
555 private class RandomReader implements Callable<Boolean> {
556
557 private int readerId;
558 private StoreFile.Reader reader;
559 private boolean pread;
560
561 public RandomReader(int readerId, StoreFile.Reader reader,
562 boolean pread)
563 {
564 this.readerId = readerId;
565 this.reader = reader;
566 this.pread = pread;
567 }
568
569 @Override
570 public Boolean call() throws Exception {
571 Thread.currentThread().setName("reader " + readerId);
572 Random rand = new Random();
573 StoreFileScanner scanner = reader.getStoreFileScanner(true, pread);
574
575 while (System.currentTimeMillis() < endTime) {
576 byte[] row = createRandomRow(rand, firstRow, lastRow);
577 KeyValue kvToSeek = new KeyValue(row, family,
578 createRandomQualifier(rand));
579 if (rand.nextDouble() < 0.0001) {
580 LOG.info("kvToSeek=" + kvToSeek);
581 }
582 boolean seekResult;
583 try {
584 seekResult = scanner.seek(kvToSeek);
585 } catch (IOException ex) {
586 throw new IOException("Seek failed for key " + kvToSeek + ", pread="
587 + pread, ex);
588 }
589 numSeeks.incrementAndGet();
590 if (!seekResult) {
591 error("Seek returned false for row " + Bytes.toStringBinary(row));
592 return false;
593 }
594 for (int i = 0; i < rand.nextInt(10) + 1; ++i) {
595 KeyValue kv = scanner.next();
596 numKV.incrementAndGet();
597 if (i == 0 && kv == null) {
598 error("scanner.next() returned null at the first iteration for " +
599 "row " + Bytes.toStringBinary(row));
600 return false;
601 }
602 if (kv == null)
603 break;
604
605 String keyHashStr = MD5Hash.getMD5AsHex(kv.getKey());
606 keysRead.add(keyHashStr);
607 totalBytes.addAndGet(kv.getLength());
608 }
609 }
610
611 return true;
612 }
613
614 private void error(String msg) {
615 LOG.error("error in reader " + readerId + " (pread=" + pread + "): "
616 + msg);
617 }
618
619 }
620
621 private class StatisticsPrinter implements Callable<Boolean> {
622
623 private volatile boolean stopRequested;
624 private volatile Thread thread;
625 private long totalSeekAndReads, totalPositionalReads;
626
627
628
629
630 public void startThread() {
631 new Thread() {
632 @Override
633 public void run() {
634 try {
635 call();
636 } catch (Exception e) {
637 LOG.error(e);
638 }
639 }
640 }.start();
641 }
642
643 @Override
644 public Boolean call() throws Exception {
645 LOG.info("Starting statistics printer");
646 thread = Thread.currentThread();
647 thread.setName(StatisticsPrinter.class.getSimpleName());
648 long startTime = System.currentTimeMillis();
649 long curTime;
650 while ((curTime = System.currentTimeMillis()) < endTime &&
651 !stopRequested) {
652 long elapsedTime = curTime - startTime;
653 printStats(elapsedTime);
654 try {
655 Thread.sleep(1000 - elapsedTime % 1000);
656 } catch (InterruptedException iex) {
657 Thread.currentThread().interrupt();
658 if (stopRequested)
659 break;
660 }
661 }
662 printStats(curTime - startTime);
663 LOG.info("Stopping statistics printer");
664 return true;
665 }
666
667 private void printStats(long elapsedTime) {
668 long numSeeksL = numSeeks.get();
669 double timeSec = elapsedTime / 1000.0;
670 double seekPerSec = numSeeksL / timeSec;
671 long kvCount = numKV.get();
672 double kvPerSec = kvCount / timeSec;
673 long bytes = totalBytes.get();
674 double bytesPerSec = bytes / timeSec;
675
676
677
678
679
680 totalSeekAndReads += HFile.getReadOps();
681 totalPositionalReads += HFile.getPreadOps();
682 long totalBlocksRead = totalSeekAndReads + totalPositionalReads;
683
684 double blkReadPerSec = totalBlocksRead / timeSec;
685
686 double seekReadPerSec = totalSeekAndReads / timeSec;
687 double preadPerSec = totalPositionalReads / timeSec;
688
689 boolean isRead = workload == Workload.RANDOM_READS;
690
691 StringBuilder sb = new StringBuilder();
692 sb.append("Time: " + (long) timeSec + " sec");
693 if (isRead)
694 sb.append(", seek/sec: " + (long) seekPerSec);
695 sb.append(", kv/sec: " + (long) kvPerSec);
696 sb.append(", bytes/sec: " + (long) bytesPerSec);
697 sb.append(", blk/sec: " + (long) blkReadPerSec);
698 sb.append(", total KV: " + numKV);
699 sb.append(", total bytes: " + totalBytes);
700 sb.append(", total blk: " + totalBlocksRead);
701
702 sb.append(", seekRead/sec: " + (long) seekReadPerSec);
703 sb.append(", pread/sec: " + (long) preadPerSec);
704
705 if (isRead)
706 sb.append(", unique keys: " + (long) keysRead.size());
707
708 LOG.info(sb.toString());
709 }
710
711 public void requestStop() {
712 stopRequested = true;
713 if (thread != null)
714 thread.interrupt();
715 }
716
717 }
718
719 public boolean runRandomReadWorkload() throws IOException {
720 if (inputFileNames.size() != 1) {
721 throw new IOException("Need exactly one input file for random reads: " +
722 inputFileNames);
723 }
724
725 Path inputPath = new Path(inputFileNames.get(0));
726
727
728 StoreFile storeFile = openStoreFile(inputPath, true);
729
730 StoreFile.Reader reader = storeFile.createReader();
731
732 LOG.info("First key: " + Bytes.toStringBinary(reader.getFirstKey()));
733 LOG.info("Last key: " + Bytes.toStringBinary(reader.getLastKey()));
734
735 KeyValue firstKV = KeyValue.createKeyValueFromKey(reader.getFirstKey());
736 firstRow = firstKV.getRow();
737
738 KeyValue lastKV = KeyValue.createKeyValueFromKey(reader.getLastKey());
739 lastRow = lastKV.getRow();
740
741 byte[] family = firstKV.getFamily();
742 if (!Bytes.equals(family, lastKV.getFamily())) {
743 LOG.error("First and last key have different families: "
744 + Bytes.toStringBinary(family) + " and "
745 + Bytes.toStringBinary(lastKV.getFamily()));
746 return false;
747 }
748
749 if (Bytes.equals(firstRow, lastRow)) {
750 LOG.error("First and last row are the same, cannot run read workload: " +
751 "firstRow=" + Bytes.toStringBinary(firstRow) + ", " +
752 "lastRow=" + Bytes.toStringBinary(lastRow));
753 return false;
754 }
755
756 ExecutorService exec = Executors.newFixedThreadPool(numReadThreads + 1);
757 int numCompleted = 0;
758 int numFailed = 0;
759 try {
760 ExecutorCompletionService<Boolean> ecs =
761 new ExecutorCompletionService<Boolean>(exec);
762 endTime = System.currentTimeMillis() + 1000 * durationSec;
763 boolean pread = true;
764 for (int i = 0; i < numReadThreads; ++i)
765 ecs.submit(new RandomReader(i, reader, pread));
766 ecs.submit(new StatisticsPrinter());
767 Future<Boolean> result;
768 while (true) {
769 try {
770 result = ecs.poll(endTime + 1000 - System.currentTimeMillis(),
771 TimeUnit.MILLISECONDS);
772 if (result == null)
773 break;
774 try {
775 if (result.get()) {
776 ++numCompleted;
777 } else {
778 ++numFailed;
779 }
780 } catch (ExecutionException e) {
781 LOG.error("Worker thread failure", e.getCause());
782 ++numFailed;
783 }
784 } catch (InterruptedException ex) {
785 LOG.error("Interrupted after " + numCompleted +
786 " workers completed");
787 Thread.currentThread().interrupt();
788 continue;
789 }
790
791 }
792 } finally {
793 storeFile.closeReader(true);
794 exec.shutdown();
795
796 BlockCache c = cacheConf.getBlockCache();
797 if (c != null) {
798 c.shutdown();
799 }
800 }
801 LOG.info("Worker threads completed: " + numCompleted);
802 LOG.info("Worker threads failed: " + numFailed);
803 return true;
804 }
805
806 public boolean run() throws IOException {
807 LOG.info("Workload: " + workload);
808 switch (workload) {
809 case MERGE:
810 runMergeWorkload();
811 break;
812 case RANDOM_READS:
813 return runRandomReadWorkload();
814 default:
815 LOG.error("Unknown workload: " + workload);
816 return false;
817 }
818
819 return true;
820 }
821
822 private static void failure() {
823 System.exit(1);
824 }
825
826 public static void main(String[] args) {
827 HFileReadWriteTest app = new HFileReadWriteTest();
828 if (!app.parseOptions(args))
829 failure();
830
831 try {
832 if (!app.validateConfiguration() ||
833 !app.run())
834 failure();
835 } catch (IOException ex) {
836 LOG.error(ex);
837 failure();
838 }
839 }
840
841 }