1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.io.hfile;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.DataInput;
24 import java.io.IOException;
25 import java.io.PrintStream;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.SortedMap;
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.hbase.classification.InterfaceAudience;
47 import org.apache.hadoop.hbase.classification.InterfaceStability;
48 import org.apache.hadoop.conf.Configuration;
49 import org.apache.hadoop.conf.Configured;
50 import org.apache.hadoop.fs.FileSystem;
51 import org.apache.hadoop.fs.Path;
52 import org.apache.hadoop.hbase.Cell;
53 import org.apache.hadoop.hbase.CellComparator;
54 import org.apache.hadoop.hbase.CellUtil;
55 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
56 import org.apache.hadoop.hbase.HConstants;
57 import org.apache.hadoop.hbase.TableName;
58 import org.apache.hadoop.hbase.HBaseConfiguration;
59 import org.apache.hadoop.hbase.HRegionInfo;
60 import org.apache.hadoop.hbase.KeyValue;
61 import org.apache.hadoop.hbase.KeyValueUtil;
62 import org.apache.hadoop.hbase.Tag;
63 import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
64 import org.apache.hadoop.hbase.io.hfile.HFile.FileInfo;
65 import org.apache.hadoop.hbase.mob.MobUtils;
66 import org.apache.hadoop.hbase.regionserver.TimeRangeTracker;
67 import org.apache.hadoop.hbase.util.BloomFilter;
68 import org.apache.hadoop.hbase.util.BloomFilterFactory;
69 import org.apache.hadoop.hbase.util.ByteBloomFilter;
70 import org.apache.hadoop.hbase.util.Bytes;
71 import org.apache.hadoop.hbase.util.FSUtils;
72 import org.apache.hadoop.hbase.util.HFileArchiveUtil;
73 import org.apache.hadoop.hbase.util.Writables;
74 import org.apache.hadoop.util.Tool;
75 import org.apache.hadoop.util.ToolRunner;
76
77 import com.yammer.metrics.core.Histogram;
78 import com.yammer.metrics.core.Metric;
79 import com.yammer.metrics.core.MetricName;
80 import com.yammer.metrics.core.MetricPredicate;
81 import com.yammer.metrics.core.MetricsRegistry;
82 import com.yammer.metrics.reporting.ConsoleReporter;
83
84
85
86
87 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
88 @InterfaceStability.Evolving
89 public class HFilePrettyPrinter extends Configured implements Tool {
90
91 private static final Log LOG = LogFactory.getLog(HFilePrettyPrinter.class);
92
93 private Options options = new Options();
94
95 private boolean verbose;
96 private boolean printValue;
97 private boolean printKey;
98 private boolean shouldPrintMeta;
99 private boolean printBlockIndex;
100 private boolean printBlockHeaders;
101 private boolean printStats;
102 private boolean checkRow;
103 private boolean checkFamily;
104 private boolean isSeekToRow = false;
105 private boolean checkMobIntegrity = false;
106 private Map<String, List<Path>> mobFileLocations;
107 private static final int FOUND_MOB_FILES_CACHE_CAPACITY = 50;
108 private static final int MISSING_MOB_FILES_CACHE_CAPACITY = 20;
109
110
111
112
113 private byte[] row = null;
114
115 private List<Path> files = new ArrayList<Path>();
116 private int count;
117
118 private static final String FOUR_SPACES = " ";
119
120 public HFilePrettyPrinter() {
121 super();
122 init();
123 }
124
125 public HFilePrettyPrinter(Configuration conf) {
126 super(conf);
127 init();
128 }
129
130 private void init() {
131 options.addOption("v", "verbose", false,
132 "Verbose output; emits file and meta data delimiters");
133 options.addOption("p", "printkv", false, "Print key/value pairs");
134 options.addOption("e", "printkey", false, "Print keys");
135 options.addOption("m", "printmeta", false, "Print meta data of file");
136 options.addOption("b", "printblocks", false, "Print block index meta data");
137 options.addOption("h", "printblockheaders", false, "Print block headers for each block.");
138 options.addOption("k", "checkrow", false,
139 "Enable row order check; looks for out-of-order keys");
140 options.addOption("a", "checkfamily", false, "Enable family check");
141 options.addOption("w", "seekToRow", true,
142 "Seek to this row and print all the kvs for this row only");
143 options.addOption("s", "stats", false, "Print statistics");
144 options.addOption("i", "checkMobIntegrity", false,
145 "Print all cells whose mob files are missing");
146
147 OptionGroup files = new OptionGroup();
148 files.addOption(new Option("f", "file", true,
149 "File to scan. Pass full-path; e.g. hdfs://a:9000/hbase/hbase:meta/12/34"));
150 files.addOption(new Option("r", "region", true,
151 "Region to scan. Pass region name; e.g. 'hbase:meta,,1'"));
152 options.addOptionGroup(files);
153 }
154
155 public boolean parseOptions(String args[]) throws ParseException,
156 IOException {
157 if (args.length == 0) {
158 HelpFormatter formatter = new HelpFormatter();
159 formatter.printHelp("HFile", options, true);
160 return false;
161 }
162 CommandLineParser parser = new PosixParser();
163 CommandLine cmd = parser.parse(options, args);
164
165 verbose = cmd.hasOption("v");
166 printValue = cmd.hasOption("p");
167 printKey = cmd.hasOption("e") || printValue;
168 shouldPrintMeta = cmd.hasOption("m");
169 printBlockIndex = cmd.hasOption("b");
170 printBlockHeaders = cmd.hasOption("h");
171 printStats = cmd.hasOption("s");
172 checkRow = cmd.hasOption("k");
173 checkFamily = cmd.hasOption("a");
174 checkMobIntegrity = cmd.hasOption("i");
175
176 if (cmd.hasOption("f")) {
177 files.add(new Path(cmd.getOptionValue("f")));
178 }
179
180 if (cmd.hasOption("w")) {
181 String key = cmd.getOptionValue("w");
182 if (key != null && key.length() != 0) {
183 row = Bytes.toBytesBinary(key);
184 isSeekToRow = true;
185 } else {
186 System.err.println("Invalid row is specified.");
187 System.exit(-1);
188 }
189 }
190
191 if (cmd.hasOption("r")) {
192 String regionName = cmd.getOptionValue("r");
193 byte[] rn = Bytes.toBytes(regionName);
194 byte[][] hri = HRegionInfo.parseRegionName(rn);
195 Path rootDir = FSUtils.getRootDir(getConf());
196 Path tableDir = FSUtils.getTableDir(rootDir, TableName.valueOf(hri[0]));
197 String enc = HRegionInfo.encodeRegionName(rn);
198 Path regionDir = new Path(tableDir, enc);
199 if (verbose)
200 System.out.println("region dir -> " + regionDir);
201 List<Path> regionFiles = HFile.getStoreFiles(FileSystem.get(getConf()),
202 regionDir);
203 if (verbose)
204 System.out.println("Number of region files found -> "
205 + regionFiles.size());
206 if (verbose) {
207 int i = 1;
208 for (Path p : regionFiles) {
209 if (verbose)
210 System.out.println("Found file[" + i++ + "] -> " + p);
211 }
212 }
213 files.addAll(regionFiles);
214 }
215
216 if(checkMobIntegrity) {
217 if (verbose) {
218 System.out.println("checkMobIntegrity is enabled");
219 }
220 mobFileLocations = new HashMap<String, List<Path>>();
221 }
222 return true;
223 }
224
225
226
227
228
229 @Override
230 public int run(String[] args) {
231 if (getConf() == null) {
232 throw new RuntimeException("A Configuration instance must be provided.");
233 }
234 try {
235 FSUtils.setFsDefault(getConf(), FSUtils.getRootDir(getConf()));
236 if (!parseOptions(args))
237 return 1;
238 } catch (IOException ex) {
239 LOG.error("Error parsing command-line options", ex);
240 return 1;
241 } catch (ParseException ex) {
242 LOG.error("Error parsing command-line options", ex);
243 return 1;
244 }
245
246
247 for (Path fileName : files) {
248 try {
249 processFile(fileName);
250 } catch (IOException ex) {
251 LOG.error("Error reading " + fileName, ex);
252 System.exit(-2);
253 }
254 }
255
256 if (verbose || printKey) {
257 System.out.println("Scanned kv count -> " + count);
258 }
259
260 return 0;
261 }
262
263 private void processFile(Path file) throws IOException {
264 if (verbose)
265 System.out.println("Scanning -> " + file);
266 FileSystem fs = file.getFileSystem(getConf());
267 if (!fs.exists(file)) {
268 System.err.println("ERROR, file doesnt exist: " + file);
269 System.exit(-2);
270 }
271
272 HFile.Reader reader = HFile.createReader(fs, file, new CacheConfig(getConf()), getConf());
273
274 Map<byte[], byte[]> fileInfo = reader.loadFileInfo();
275
276 KeyValueStatsCollector fileStats = null;
277
278 if (verbose || printKey || checkRow || checkFamily || printStats || checkMobIntegrity) {
279
280 HFileScanner scanner = reader.getScanner(false, false, false);
281 fileStats = new KeyValueStatsCollector();
282 boolean shouldScanKeysValues = false;
283 if (this.isSeekToRow) {
284
285 shouldScanKeysValues =
286 (scanner.seekTo(KeyValueUtil.createFirstOnRow(this.row).getKey()) != -1);
287 } else {
288 shouldScanKeysValues = scanner.seekTo();
289 }
290 if (shouldScanKeysValues)
291 scanKeysValues(file, fileStats, scanner, row);
292 }
293
294
295 if (shouldPrintMeta) {
296 printMeta(reader, fileInfo);
297 }
298
299 if (printBlockIndex) {
300 System.out.println("Block Index:");
301 System.out.println(reader.getDataBlockIndexReader());
302 }
303
304 if (printBlockHeaders) {
305 System.out.println("Block Headers:");
306
307
308
309
310 FSDataInputStreamWrapper fsdis = new FSDataInputStreamWrapper(fs, file);
311 long fileSize = fs.getFileStatus(file).getLen();
312 FixedFileTrailer trailer =
313 FixedFileTrailer.readFromStream(fsdis.getStream(false), fileSize);
314 long offset = trailer.getFirstDataBlockOffset(),
315 max = trailer.getLastDataBlockOffset();
316 HFileBlock block;
317 while (offset <= max) {
318 block = reader.readBlock(offset, -1,
319
320 offset += block.getOnDiskSizeWithHeader();
321 System.out.println(block);
322 }
323 }
324
325 if (printStats) {
326 fileStats.finish();
327 System.out.println("Stats:\n" + fileStats);
328 }
329
330 reader.close();
331 }
332
333 private void scanKeysValues(Path file, KeyValueStatsCollector fileStats,
334 HFileScanner scanner, byte[] row) throws IOException {
335 Cell pCell = null;
336 FileSystem fs = FileSystem.get(getConf());
337 Set<String> foundMobFiles = new LinkedHashSet<String>(FOUND_MOB_FILES_CACHE_CAPACITY);
338 Set<String> missingMobFiles = new LinkedHashSet<String>(MISSING_MOB_FILES_CACHE_CAPACITY);
339 do {
340 Cell cell = scanner.getKeyValue();
341 if (row != null && row.length != 0) {
342 int result = CellComparator.compareRows(cell.getRowArray(), cell.getRowOffset(),
343 cell.getRowLength(), row, 0, row.length);
344 if (result > 0) {
345 break;
346 } else if (result < 0) {
347 continue;
348 }
349 }
350
351 if (printStats) {
352 fileStats.collect(cell);
353 }
354
355 if (printKey) {
356 System.out.print("K: " + cell);
357 if (printValue) {
358 System.out.print(" V: "
359 + Bytes.toStringBinary(cell.getValueArray(), cell.getValueOffset(),
360 cell.getValueLength()));
361 int i = 0;
362 List<Tag> tags = Tag.asList(cell.getTagsArray(), cell.getTagsOffset(),
363 cell.getTagsLength());
364 for (Tag tag : tags) {
365 System.out.print(String.format(" T[%d]: %s", i++,
366 Bytes.toStringBinary(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength())));
367 }
368 }
369 System.out.println();
370 }
371
372 if (checkRow && pCell != null) {
373 if (CellComparator.compareRows(pCell, cell) > 0) {
374 System.err.println("WARNING, previous row is greater then"
375 + " current row\n\tfilename -> " + file + "\n\tprevious -> "
376 + CellUtil.getCellKeyAsString(pCell) + "\n\tcurrent -> "
377 + CellUtil.getCellKeyAsString(cell));
378 }
379 }
380
381 if (checkFamily) {
382 String fam = Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(),
383 cell.getFamilyLength());
384 if (!file.toString().contains(fam)) {
385 System.err.println("WARNING, filename does not match kv family,"
386 + "\n\tfilename -> " + file + "\n\tkeyvalue -> "
387 + CellUtil.getCellKeyAsString(cell));
388 }
389 if (pCell != null && CellComparator.compareFamilies(pCell, cell) != 0) {
390 System.err.println("WARNING, previous kv has different family"
391 + " compared to current key\n\tfilename -> " + file
392 + "\n\tprevious -> " + CellUtil.getCellKeyAsString(pCell)
393 + "\n\tcurrent -> " + CellUtil.getCellKeyAsString(cell));
394 }
395 }
396
397 if (checkMobIntegrity && MobUtils.isMobReferenceCell(cell)) {
398 Tag tnTag = MobUtils.getTableNameTag(cell);
399 if (tnTag == null) {
400 System.err.println("ERROR, wrong tag format in mob reference cell "
401 + CellUtil.getCellKeyAsString(cell));
402 } else if (!MobUtils.hasValidMobRefCellValue(cell)) {
403 System.err.println("ERROR, wrong value format in mob reference cell "
404 + CellUtil.getCellKeyAsString(cell));
405 } else {
406 TableName tn = TableName.valueOf(tnTag.getValue());
407 String mobFileName = MobUtils.getMobFileName(cell);
408 boolean exist = mobFileExists(fs, tn, mobFileName,
409 Bytes.toString(CellUtil.cloneFamily(cell)), foundMobFiles, missingMobFiles);
410 if (!exist) {
411
412 System.err.println("ERROR, the mob file [" + mobFileName
413 + "] is missing referenced by cell " + CellUtil.getCellKeyAsString(cell));
414 }
415 }
416 }
417 pCell = cell;
418 ++count;
419 } while (scanner.next());
420 }
421
422
423
424
425 private boolean mobFileExists(FileSystem fs, TableName tn, String mobFileName, String family,
426 Set<String> foundMobFiles, Set<String> missingMobFiles) throws IOException {
427 if (foundMobFiles.contains(mobFileName)) {
428 return true;
429 }
430 if (missingMobFiles.contains(mobFileName)) {
431 return false;
432 }
433 String tableName = tn.getNameAsString();
434 List<Path> locations = mobFileLocations.get(tableName);
435 if (locations == null) {
436 locations = new ArrayList<Path>(2);
437 locations.add(MobUtils.getMobFamilyPath(getConf(), tn, family));
438 locations.add(HFileArchiveUtil.getStoreArchivePath(getConf(), tn,
439 MobUtils.getMobRegionInfo(tn).getEncodedName(), family));
440 mobFileLocations.put(tn.getNameAsString(), locations);
441 }
442 boolean exist = false;
443 for (Path location : locations) {
444 Path mobFilePath = new Path(location, mobFileName);
445 if (fs.exists(mobFilePath)) {
446 exist = true;
447 break;
448 }
449 }
450 if (exist) {
451 evictMobFilesIfNecessary(foundMobFiles, FOUND_MOB_FILES_CACHE_CAPACITY);
452 foundMobFiles.add(mobFileName);
453 } else {
454 evictMobFilesIfNecessary(missingMobFiles, MISSING_MOB_FILES_CACHE_CAPACITY);
455 missingMobFiles.add(mobFileName);
456 }
457 return exist;
458 }
459
460
461
462
463 private void evictMobFilesIfNecessary(Set<String> mobFileNames, int limit) {
464 if (mobFileNames.size() < limit) {
465 return;
466 }
467 int index = 0;
468 int evict = limit / 2;
469 Iterator<String> fileNamesItr = mobFileNames.iterator();
470 while (index < evict && fileNamesItr.hasNext()) {
471 fileNamesItr.next();
472 fileNamesItr.remove();
473 index++;
474 }
475 }
476
477
478
479
480
481 private static String asSeparateLines(String keyValueStr) {
482 return keyValueStr.replaceAll(", ([a-zA-Z]+=)",
483 ",\n" + FOUR_SPACES + "$1");
484 }
485
486 private void printMeta(HFile.Reader reader, Map<byte[], byte[]> fileInfo)
487 throws IOException {
488 System.out.println("Block index size as per heapsize: "
489 + reader.indexSize());
490 System.out.println(asSeparateLines(reader.toString()));
491 System.out.println("Trailer:\n "
492 + asSeparateLines(reader.getTrailer().toString()));
493 System.out.println("Fileinfo:");
494 for (Map.Entry<byte[], byte[]> e : fileInfo.entrySet()) {
495 System.out.print(FOUR_SPACES + Bytes.toString(e.getKey()) + " = ");
496 if (Bytes.compareTo(e.getKey(), Bytes.toBytes("MAX_SEQ_ID_KEY")) == 0) {
497 long seqid = Bytes.toLong(e.getValue());
498 System.out.println(seqid);
499 } else if (Bytes.compareTo(e.getKey(), Bytes.toBytes("TIMERANGE")) == 0) {
500 TimeRangeTracker timeRangeTracker = new TimeRangeTracker();
501 Writables.copyWritable(e.getValue(), timeRangeTracker);
502 System.out.println(timeRangeTracker.getMinimumTimestamp() + "...."
503 + timeRangeTracker.getMaximumTimestamp());
504 } else if (Bytes.compareTo(e.getKey(), FileInfo.AVG_KEY_LEN) == 0
505 || Bytes.compareTo(e.getKey(), FileInfo.AVG_VALUE_LEN) == 0) {
506 System.out.println(Bytes.toInt(e.getValue()));
507 } else {
508 System.out.println(Bytes.toStringBinary(e.getValue()));
509 }
510 }
511
512 try {
513 System.out.println("Mid-key: " + Bytes.toStringBinary(reader.midkey()));
514 } catch (Exception e) {
515 System.out.println ("Unable to retrieve the midkey");
516 }
517
518
519 DataInput bloomMeta = reader.getGeneralBloomFilterMetadata();
520 BloomFilter bloomFilter = null;
521 if (bloomMeta != null)
522 bloomFilter = BloomFilterFactory.createFromMeta(bloomMeta, reader);
523
524 System.out.println("Bloom filter:");
525 if (bloomFilter != null) {
526 System.out.println(FOUR_SPACES + bloomFilter.toString().replaceAll(
527 ByteBloomFilter.STATS_RECORD_SEP, "\n" + FOUR_SPACES));
528 } else {
529 System.out.println(FOUR_SPACES + "Not present");
530 }
531
532
533 bloomMeta = reader.getDeleteBloomFilterMetadata();
534 bloomFilter = null;
535 if (bloomMeta != null)
536 bloomFilter = BloomFilterFactory.createFromMeta(bloomMeta, reader);
537
538 System.out.println("Delete Family Bloom filter:");
539 if (bloomFilter != null) {
540 System.out.println(FOUR_SPACES
541 + bloomFilter.toString().replaceAll(ByteBloomFilter.STATS_RECORD_SEP,
542 "\n" + FOUR_SPACES));
543 } else {
544 System.out.println(FOUR_SPACES + "Not present");
545 }
546 }
547
548 private static class KeyValueStatsCollector {
549 private final MetricsRegistry metricsRegistry = new MetricsRegistry();
550 private final ByteArrayOutputStream metricsOutput = new ByteArrayOutputStream();
551 private final SimpleReporter simpleReporter = new SimpleReporter(metricsRegistry, new PrintStream(metricsOutput));
552 Histogram keyLen = metricsRegistry.newHistogram(HFilePrettyPrinter.class, "Key length");
553 Histogram valLen = metricsRegistry.newHistogram(HFilePrettyPrinter.class, "Val length");
554 Histogram rowSizeBytes = metricsRegistry.newHistogram(HFilePrettyPrinter.class, "Row size (bytes)");
555 Histogram rowSizeCols = metricsRegistry.newHistogram(HFilePrettyPrinter.class, "Row size (columns)");
556
557 long curRowBytes = 0;
558 long curRowCols = 0;
559
560 byte[] biggestRow = null;
561
562 private Cell prevCell = null;
563 private long maxRowBytes = 0;
564 private long curRowKeyLength;
565
566 public void collect(Cell cell) {
567 valLen.update(cell.getValueLength());
568 if (prevCell != null &&
569 KeyValue.COMPARATOR.compareRows(prevCell, cell) != 0) {
570
571 collectRow();
572 }
573 curRowBytes += KeyValueUtil.length(cell);
574 curRowKeyLength = KeyValueUtil.keyLength(cell);
575 curRowCols++;
576 prevCell = cell;
577 }
578
579 private void collectRow() {
580 rowSizeBytes.update(curRowBytes);
581 rowSizeCols.update(curRowCols);
582 keyLen.update(curRowKeyLength);
583
584 if (curRowBytes > maxRowBytes && prevCell != null) {
585 biggestRow = prevCell.getRow();
586 maxRowBytes = curRowBytes;
587 }
588
589 curRowBytes = 0;
590 curRowCols = 0;
591 }
592
593 public void finish() {
594 if (curRowCols > 0) {
595 collectRow();
596 }
597 }
598
599 @Override
600 public String toString() {
601 if (prevCell == null)
602 return "no data available for statistics";
603
604
605 simpleReporter.shutdown();
606 simpleReporter.run();
607 metricsRegistry.shutdown();
608
609 return
610 metricsOutput.toString() +
611 "Key of biggest row: " + Bytes.toStringBinary(biggestRow);
612 }
613 }
614
615 private static class SimpleReporter extends ConsoleReporter {
616 private final PrintStream out;
617
618 public SimpleReporter(MetricsRegistry metricsRegistry, PrintStream out) {
619 super(metricsRegistry, out, MetricPredicate.ALL);
620 this.out = out;
621 }
622
623 @Override
624 public void run() {
625 for (Map.Entry<String, SortedMap<MetricName, Metric>> entry : getMetricsRegistry().groupedMetrics(
626 MetricPredicate.ALL).entrySet()) {
627 try {
628 for (Map.Entry<MetricName, Metric> subEntry : entry.getValue().entrySet()) {
629 out.print(" " + subEntry.getKey().getName());
630 out.println(':');
631
632 subEntry.getValue().processWith(this, subEntry.getKey(), out);
633 }
634 } catch (Exception e) {
635 e.printStackTrace(out);
636 }
637 }
638 }
639
640 @Override
641 public void processHistogram(MetricName name, Histogram histogram, PrintStream stream) {
642 super.processHistogram(name, histogram, stream);
643 stream.printf(Locale.getDefault(), " count = %d%n", histogram.count());
644 }
645 }
646
647 public static void main(String[] args) throws Exception {
648 Configuration conf = HBaseConfiguration.create();
649
650 conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0);
651 int ret = ToolRunner.run(conf, new HFilePrettyPrinter(), args);
652 System.exit(ret);
653 }
654 }