View Javadoc

1   
2   /*
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
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   * Implements pretty-printing functionality for {@link HFile}s.
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    * The row which the user wants to specify and print all the KeyValues for.
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    * Runs the command-line pretty-printer, and returns the desired command
227    * exit code (zero for success, non-zero for failure).
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     // iterate over all files found
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       // scan over file and read key/value's and check if requested
280       HFileScanner scanner = reader.getScanner(false, false, false);
281       fileStats = new KeyValueStatsCollector();
282       boolean shouldScanKeysValues = false;
283       if (this.isSeekToRow) {
284         // seek to the first kv on this row
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     // print meta data
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        * TODO: this same/similar block iteration logic is used in HFileBlock#blockRange and
308        * TestLazyDataBlockDecompression. Refactor?
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, /* cacheBlock */ false, /* pread */ false,
319           /* isCompaction */ false, /* updateCacheMetrics */ false, null, null);
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       // collect stats
351       if (printStats) {
352         fileStats.collect(cell);
353       }
354       // dump key value
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       // check if rows are in order
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       // check if families are consistent
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       // check if mob files are missing.
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             // report error
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    * Checks whether the referenced mob file exists.
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    * Evicts the cached mob files if the set is larger than the limit.
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    * Format a string of the form "k1=v1, k2=v2, ..." into separate lines
479    * with a four-space indentation.
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     // Printing general bloom information
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     // Printing delete bloom information
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         // new row
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       // Dump the metrics to the output stream
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     // no need for a block cache
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 }