View Javadoc

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