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 static com.codahale.metrics.MetricRegistry.name;
22
23  import java.io.ByteArrayOutputStream;
24  import java.io.DataInput;
25  import java.io.IOException;
26  import java.io.PrintStream;
27  import java.text.DateFormat;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.SortedMap;
37  import java.util.TimeZone;
38  import java.util.concurrent.TimeUnit;
39
40  import org.apache.commons.cli.CommandLine;
41  import org.apache.commons.cli.CommandLineParser;
42  import org.apache.commons.cli.HelpFormatter;
43  import org.apache.commons.cli.Option;
44  import org.apache.commons.cli.OptionGroup;
45  import org.apache.commons.cli.Options;
46  import org.apache.commons.cli.ParseException;
47  import org.apache.commons.cli.PosixParser;
48  import org.apache.commons.lang.StringUtils;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  import org.apache.hadoop.conf.Configuration;
52  import org.apache.hadoop.conf.Configured;
53  import org.apache.hadoop.fs.FileSystem;
54  import org.apache.hadoop.fs.Path;
55  import org.apache.hadoop.hbase.Cell;
56  import org.apache.hadoop.hbase.CellComparator;
57  import org.apache.hadoop.hbase.CellUtil;
58  import org.apache.hadoop.hbase.HBaseConfiguration;
59  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
60  import org.apache.hadoop.hbase.HConstants;
61  import org.apache.hadoop.hbase.HRegionInfo;
62  import org.apache.hadoop.hbase.KeyValueUtil;
63  import org.apache.hadoop.hbase.TableName;
64  import org.apache.hadoop.hbase.Tag;
65  import org.apache.hadoop.hbase.TagUtil;
66  import org.apache.hadoop.hbase.classification.InterfaceAudience;
67  import org.apache.hadoop.hbase.classification.InterfaceStability;
68  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
69  import org.apache.hadoop.hbase.io.hfile.HFile.FileInfo;
70  import org.apache.hadoop.hbase.mob.MobUtils;
71  import org.apache.hadoop.hbase.regionserver.TimeRangeTracker;
72  import org.apache.hadoop.hbase.util.BloomFilter;
73  import org.apache.hadoop.hbase.util.BloomFilterFactory;
74  import org.apache.hadoop.hbase.util.BloomFilterUtil;
75  import org.apache.hadoop.hbase.util.Bytes;
76  import org.apache.hadoop.hbase.util.FSUtils;
77  import org.apache.hadoop.hbase.util.HFileArchiveUtil;
78  import org.apache.hadoop.util.Tool;
79  import org.apache.hadoop.util.ToolRunner;
80
81  import com.codahale.metrics.ConsoleReporter;
82  import com.codahale.metrics.Counter;
83  import com.codahale.metrics.Gauge;
84  import com.codahale.metrics.Histogram;
85  import com.codahale.metrics.Meter;
86  import com.codahale.metrics.MetricFilter;
87  import com.codahale.metrics.MetricRegistry;
88  import com.codahale.metrics.ScheduledReporter;
89  import com.codahale.metrics.Snapshot;
90  import com.codahale.metrics.Timer;
91
92  /**
93   * Implements pretty-printing functionality for {@link HFile}s.
94   */
95  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
96  @InterfaceStability.Evolving
97  public class HFilePrettyPrinter extends Configured implements Tool {
98
99    private static final Log LOG = LogFactory.getLog(HFilePrettyPrinter.class);
100
101   private Options options = new Options();
102
103   private boolean verbose;
104   private boolean printValue;
105   private boolean printKey;
106   private boolean shouldPrintMeta;
107   private boolean printBlockIndex;
108   private boolean printBlockHeaders;
109   private boolean printStats;
110   private boolean checkRow;
111   private boolean checkFamily;
112   private boolean isSeekToRow = false;
113   private boolean checkMobIntegrity = false;
114   private Map<String, List<Path>> mobFileLocations;
115   private static final int FOUND_MOB_FILES_CACHE_CAPACITY = 50;
116   private static final int MISSING_MOB_FILES_CACHE_CAPACITY = 20;
117   private PrintStream out = System.out;
118   private PrintStream err = System.err;
119
120   /**
121    * The row which the user wants to specify and print all the KeyValues for.
122    */
123   private byte[] row = null;
124
125   private List<Path> files = new ArrayList<Path>();
126   private int count;
127
128   private static final String FOUR_SPACES = "    ";
129
130   public HFilePrettyPrinter() {
131     super();
132     init();
133   }
134
135   public HFilePrettyPrinter(Configuration conf) {
136     super(conf);
137     init();
138   }
139
140   private void init() {
141     options.addOption("v", "verbose", false,
142         "Verbose output; emits file and meta data delimiters");
143     options.addOption("p", "printkv", false, "Print key/value pairs");
144     options.addOption("e", "printkey", false, "Print keys");
145     options.addOption("m", "printmeta", false, "Print meta data of file");
146     options.addOption("b", "printblocks", false, "Print block index meta data");
147     options.addOption("h", "printblockheaders", false, "Print block headers for each block.");
148     options.addOption("k", "checkrow", false,
149         "Enable row order check; looks for out-of-order keys");
150     options.addOption("a", "checkfamily", false, "Enable family check");
151     options.addOption("w", "seekToRow", true,
152       "Seek to this row and print all the kvs for this row only");
153     options.addOption("s", "stats", false, "Print statistics");
154     options.addOption("i", "checkMobIntegrity", false,
155       "Print all cells whose mob files are missing");
156
157     OptionGroup files = new OptionGroup();
158     files.addOption(new Option("f", "file", true,
159       "File to scan. Pass full-path; e.g. hdfs://a:9000/hbase/hbase:meta/12/34"));
160     files.addOption(new Option("r", "region", true,
161       "Region to scan. Pass region name; e.g. 'hbase:meta,,1'"));
162     options.addOptionGroup(files);
163   }
164
165   public void setPrintStreams(PrintStream out, PrintStream err) {
166     this.out = out;
167     this.err = err;
168   }
169
170   public boolean parseOptions(String args[]) throws ParseException,
171       IOException {
172     if (args.length == 0) {
173       HelpFormatter formatter = new HelpFormatter();
174       formatter.printHelp("HFile", options, true);
175       return false;
176     }
177     CommandLineParser parser = new PosixParser();
178     CommandLine cmd = parser.parse(options, args);
179
180     verbose = cmd.hasOption("v");
181     printValue = cmd.hasOption("p");
182     printKey = cmd.hasOption("e") || printValue;
183     shouldPrintMeta = cmd.hasOption("m");
184     printBlockIndex = cmd.hasOption("b");
185     printBlockHeaders = cmd.hasOption("h");
186     printStats = cmd.hasOption("s");
187     checkRow = cmd.hasOption("k");
188     checkFamily = cmd.hasOption("a");
189     checkMobIntegrity = cmd.hasOption("i");
190
191     if (cmd.hasOption("f")) {
192       files.add(new Path(cmd.getOptionValue("f")));
193     }
194
195     if (cmd.hasOption("w")) {
196       String key = cmd.getOptionValue("w");
197       if (key != null && key.length() != 0) {
198         row = Bytes.toBytesBinary(key);
199         isSeekToRow = true;
200       } else {
201         err.println("Invalid row is specified.");
202         System.exit(-1);
203       }
204     }
205
206     if (cmd.hasOption("r")) {
207       String regionName = cmd.getOptionValue("r");
208       byte[] rn = Bytes.toBytes(regionName);
209       byte[][] hri = HRegionInfo.parseRegionName(rn);
210       Path rootDir = FSUtils.getRootDir(getConf());
211       Path tableDir = FSUtils.getTableDir(rootDir, TableName.valueOf(hri[0]));
212       String enc = HRegionInfo.encodeRegionName(rn);
213       Path regionDir = new Path(tableDir, enc);
214       if (verbose)
215         out.println("region dir -> " + regionDir);
216       List<Path> regionFiles = HFile.getStoreFiles(FileSystem.get(getConf()),
217           regionDir);
218       if (verbose)
219         out.println("Number of region files found -> "
220             + regionFiles.size());
221       if (verbose) {
222         int i = 1;
223         for (Path p : regionFiles) {
224           if (verbose)
225             out.println("Found file[" + i++ + "] -> " + p);
226         }
227       }
228       files.addAll(regionFiles);
229     }
230
231     if(checkMobIntegrity) {
232       if (verbose) {
233         System.out.println("checkMobIntegrity is enabled");
234       }
235       mobFileLocations = new HashMap<String, List<Path>>();
236     }
237     return true;
238   }
239
240   /**
241    * Runs the command-line pretty-printer, and returns the desired command
242    * exit code (zero for success, non-zero for failure).
243    */
244   @Override
245   public int run(String[] args) {
246     if (getConf() == null) {
247       throw new RuntimeException("A Configuration instance must be provided.");
248     }
249     try {
250       FSUtils.setFsDefault(getConf(), FSUtils.getRootDir(getConf()));
251       if (!parseOptions(args))
252         return 1;
253     } catch (IOException ex) {
254       LOG.error("Error parsing command-line options", ex);
255       return 1;
256     } catch (ParseException ex) {
257       LOG.error("Error parsing command-line options", ex);
258       return 1;
259     }
260
261     // iterate over all files found
262     for (Path fileName : files) {
263       try {
264         int exitCode = processFile(fileName);
265         if (exitCode != 0) {
266           return exitCode;
267         }
268       } catch (IOException ex) {
269         LOG.error("Error reading " + fileName, ex);
270         return -2;
271       }
272     }
273
274     if (verbose || printKey) {
275       out.println("Scanned kv count -> " + count);
276     }
277
278     return 0;
279   }
280 
281   public int processFile(Path file) throws IOException {
282     if (verbose)
283       out.println("Scanning -> " + file);
284 
285     Path rootPath = FSUtils.getRootDir(getConf());
286     String rootString = rootPath + rootPath.SEPARATOR;
287     if (!file.toString().startsWith(rootString)) {
288       // First we see if fully-qualified URI matches the root dir. It might
289       // also be an absolute path in the same filesystem, so we prepend the FS
290       // of the root dir and see if that fully-qualified URI matches.
291       FileSystem rootFS = rootPath.getFileSystem(getConf());
292       String qualifiedFile = rootFS.getUri().toString() + file.toString();
293       if (!qualifiedFile.startsWith(rootString)) {
294         err.println("ERROR, file (" + file +
295             ") is not in HBase's root directory (" + rootString + ")");
296         return -2;
297       }
298     }
299
300     FileSystem fs = file.getFileSystem(getConf());
301     if (!fs.exists(file)) {
302       err.println("ERROR, file doesnt exist: " + file);
303       return -2;
304     }
305
306     HFile.Reader reader = HFile.createReader(fs, file, new CacheConfig(getConf()), getConf());
307 
308     Map<byte[], byte[]> fileInfo = reader.loadFileInfo();
309
310     KeyValueStatsCollector fileStats = null;
311
312     if (verbose || printKey || checkRow || checkFamily || printStats || checkMobIntegrity) {
313       // scan over file and read key/value's and check if requested
314       HFileScanner scanner = reader.getScanner(false, false, false);
315       fileStats = new KeyValueStatsCollector();
316       boolean shouldScanKeysValues = false;
317       if (this.isSeekToRow) {
318         // seek to the first kv on this row
319         shouldScanKeysValues =
320           (scanner.seekTo(CellUtil.createFirstOnRow(this.row)) != -1);
321       } else {
322         shouldScanKeysValues = scanner.seekTo();
323       }
324       if (shouldScanKeysValues)
325         scanKeysValues(file, fileStats, scanner, row);
326     }
327
328     // print meta data
329     if (shouldPrintMeta) {
330       printMeta(reader, fileInfo);
331     }
332
333     if (printBlockIndex) {
334       out.println("Block Index:");
335       out.println(reader.getDataBlockIndexReader());
336     }
337
338     if (printBlockHeaders) {
339       out.println("Block Headers:");
340       /*
341        * TODO: this same/similar block iteration logic is used in HFileBlock#blockRange and
342        * TestLazyDataBlockDecompression. Refactor?
343        */
344       FSDataInputStreamWrapper fsdis = new FSDataInputStreamWrapper(fs, file);
345       long fileSize = fs.getFileStatus(file).getLen();
346       FixedFileTrailer trailer =
347         FixedFileTrailer.readFromStream(fsdis.getStream(false), fileSize);
348       long offset = trailer.getFirstDataBlockOffset(),
349         max = trailer.getLastDataBlockOffset();
350       HFileBlock block;
351       while (offset <= max) {
352         block = reader.readBlock(offset, -1, /* cacheBlock */ false, /* pread */ false,
353           /* isCompaction */ false, /* updateCacheMetrics */ false, null, null);
354         offset += block.getOnDiskSizeWithHeader();
355         out.println(block);
356       }
357     }
358
359     if (printStats) {
360       fileStats.finish();
361       out.println("Stats:\n" + fileStats);
362     }
363
364     reader.close();
365     return 0;
366   }
367
368   private void scanKeysValues(Path file, KeyValueStatsCollector fileStats,
369       HFileScanner scanner,  byte[] row) throws IOException {
370     Cell pCell = null;
371     FileSystem fs = FileSystem.get(getConf());
372     Set<String> foundMobFiles = new LinkedHashSet<String>(FOUND_MOB_FILES_CACHE_CAPACITY);
373     Set<String> missingMobFiles = new LinkedHashSet<String>(MISSING_MOB_FILES_CACHE_CAPACITY);
374     do {
375       Cell cell = scanner.getCell();
376       if (row != null && row.length != 0) {
377         int result = CellComparator.COMPARATOR.compareRows(cell, row, 0, row.length);
378         if (result > 0) {
379           break;
380         } else if (result < 0) {
381           continue;
382         }
383       }
384       // collect stats
385       if (printStats) {
386         fileStats.collect(cell);
387       }
388       // dump key value
389       if (printKey) {
390         out.print("K: " + cell);
391         if (printValue) {
392           out.print(" V: "
393               + Bytes.toStringBinary(cell.getValueArray(), cell.getValueOffset(),
394                   cell.getValueLength()));
395           int i = 0;
396           List<Tag> tags = TagUtil.asList(cell.getTagsArray(), cell.getTagsOffset(),
397               cell.getTagsLength());
398           for (Tag tag : tags) {
399             out.print(String.format(" T[%d]: %s", i++, tag.toString()));
400           }
401         }
402         out.println();
403       }
404       // check if rows are in order
405       if (checkRow && pCell != null) {
406         if (CellComparator.COMPARATOR.compareRows(pCell, cell) > 0) {
407           err.println("WARNING, previous row is greater then"
408               + " current row\n\tfilename -> " + file + "\n\tprevious -> "
409               + CellUtil.getCellKeyAsString(pCell) + "\n\tcurrent  -> "
410               + CellUtil.getCellKeyAsString(cell));
411         }
412       }
413       // check if families are consistent
414       if (checkFamily) {
415         String fam = Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(),
416             cell.getFamilyLength());
417         if (!file.toString().contains(fam)) {
418           err.println("WARNING, filename does not match kv family,"
419               + "\n\tfilename -> " + file + "\n\tkeyvalue -> "
420               + CellUtil.getCellKeyAsString(cell));
421         }
422         if (pCell != null && CellComparator.compareFamilies(pCell, cell) != 0) {
423           err.println("WARNING, previous kv has different family"
424               + " compared to current key\n\tfilename -> " + file
425               + "\n\tprevious -> " + CellUtil.getCellKeyAsString(pCell)
426               + "\n\tcurrent  -> " + CellUtil.getCellKeyAsString(cell));
427         }
428       }
429       // check if mob files are missing.
430       if (checkMobIntegrity && MobUtils.isMobReferenceCell(cell)) {
431         Tag tnTag = MobUtils.getTableNameTag(cell);
432         if (tnTag == null) {
433           System.err.println("ERROR, wrong tag format in mob reference cell "
434             + CellUtil.getCellKeyAsString(cell));
435         } else if (!MobUtils.hasValidMobRefCellValue(cell)) {
436           System.err.println("ERROR, wrong value format in mob reference cell "
437             + CellUtil.getCellKeyAsString(cell));
438         } else {
439           TableName tn = TableName.valueOf(TagUtil.cloneValue(tnTag));
440           String mobFileName = MobUtils.getMobFileName(cell);
441           boolean exist = mobFileExists(fs, tn, mobFileName,
442             Bytes.toString(CellUtil.cloneFamily(cell)), foundMobFiles, missingMobFiles);
443           if (!exist) {
444             // report error
445             System.err.println("ERROR, the mob file [" + mobFileName
446               + "] is missing referenced by cell " + CellUtil.getCellKeyAsString(cell));
447           }
448         }
449       }
450       pCell = cell;
451       ++count;
452     } while (scanner.next());
453   }
454
455   /**
456    * Checks whether the referenced mob file exists.
457    */
458   private boolean mobFileExists(FileSystem fs, TableName tn, String mobFileName, String family,
459     Set<String> foundMobFiles, Set<String> missingMobFiles) throws IOException {
460     if (foundMobFiles.contains(mobFileName)) {
461       return true;
462     }
463     if (missingMobFiles.contains(mobFileName)) {
464       return false;
465     }
466     String tableName = tn.getNameAsString();
467     List<Path> locations = mobFileLocations.get(tableName);
468     if (locations == null) {
469       locations = new ArrayList<Path>(2);
470       locations.add(MobUtils.getMobFamilyPath(getConf(), tn, family));
471       locations.add(HFileArchiveUtil.getStoreArchivePath(getConf(), tn,
472         MobUtils.getMobRegionInfo(tn).getEncodedName(), family));
473       mobFileLocations.put(tn.getNameAsString(), locations);
474     }
475     boolean exist = false;
476     for (Path location : locations) {
477       Path mobFilePath = new Path(location, mobFileName);
478       if (fs.exists(mobFilePath)) {
479         exist = true;
480         break;
481       }
482     }
483     if (exist) {
484       evictMobFilesIfNecessary(foundMobFiles, FOUND_MOB_FILES_CACHE_CAPACITY);
485       foundMobFiles.add(mobFileName);
486     } else {
487       evictMobFilesIfNecessary(missingMobFiles, MISSING_MOB_FILES_CACHE_CAPACITY);
488       missingMobFiles.add(mobFileName);
489     }
490     return exist;
491   }
492 
493   /**
494    * Evicts the cached mob files if the set is larger than the limit.
495    */
496   private void evictMobFilesIfNecessary(Set<String> mobFileNames, int limit) {
497     if (mobFileNames.size() < limit) {
498       return;
499     }
500     int index = 0;
501     int evict = limit / 2;
502     Iterator<String> fileNamesItr = mobFileNames.iterator();
503     while (index < evict && fileNamesItr.hasNext()) {
504       fileNamesItr.next();
505       fileNamesItr.remove();
506       index++;
507     }
508   }
509
510   /**
511    * Format a string of the form "k1=v1, k2=v2, ..." into separate lines
512    * with a four-space indentation.
513    */
514   private static String asSeparateLines(String keyValueStr) {
515     return keyValueStr.replaceAll(", ([a-zA-Z]+=)",
516                                   ",\n" + FOUR_SPACES + "$1");
517   }
518
519   private void printMeta(HFile.Reader reader, Map<byte[], byte[]> fileInfo)
520       throws IOException {
521     out.println("Block index size as per heapsize: "
522         + reader.indexSize());
523     out.println(asSeparateLines(reader.toString()));
524     out.println("Trailer:\n    "
525         + asSeparateLines(reader.getTrailer().toString()));
526     out.println("Fileinfo:");
527     for (Map.Entry<byte[], byte[]> e : fileInfo.entrySet()) {
528       out.print(FOUR_SPACES + Bytes.toString(e.getKey()) + " = ");
529       if (Bytes.compareTo(e.getKey(), Bytes.toBytes("MAX_SEQ_ID_KEY")) == 0) {
530         long seqid = Bytes.toLong(e.getValue());
531         out.println(seqid);
532       } else if (Bytes.compareTo(e.getKey(), Bytes.toBytes("TIMERANGE")) == 0) {
533         TimeRangeTracker timeRangeTracker = TimeRangeTracker.getTimeRangeTracker(e.getValue());
534         out.println(timeRangeTracker.getMin() + "...." + timeRangeTracker.getMax());
535       } else if (Bytes.compareTo(e.getKey(), FileInfo.AVG_KEY_LEN) == 0
536           || Bytes.compareTo(e.getKey(), FileInfo.AVG_VALUE_LEN) == 0) {
537         out.println(Bytes.toInt(e.getValue()));
538       } else {
539         out.println(Bytes.toStringBinary(e.getValue()));
540       }
541     }
542 
543     try {
544       out.println("Mid-key: " + (CellUtil.getCellKeyAsString(reader.midkey())));
545     } catch (Exception e) {
546       out.println ("Unable to retrieve the midkey");
547     }
548
549     // Printing general bloom information
550     DataInput bloomMeta = reader.getGeneralBloomFilterMetadata();
551     BloomFilter bloomFilter = null;
552     if (bloomMeta != null)
553       bloomFilter = BloomFilterFactory.createFromMeta(bloomMeta, reader);
554
555     out.println("Bloom filter:");
556     if (bloomFilter != null) {
557       out.println(FOUR_SPACES + bloomFilter.toString().replaceAll(
558           BloomFilterUtil.STATS_RECORD_SEP, "\n" + FOUR_SPACES));
559     } else {
560       out.println(FOUR_SPACES + "Not present");
561     }
562
563     // Printing delete bloom information
564     bloomMeta = reader.getDeleteBloomFilterMetadata();
565     bloomFilter = null;
566     if (bloomMeta != null)
567       bloomFilter = BloomFilterFactory.createFromMeta(bloomMeta, reader);
568 
569     out.println("Delete Family Bloom filter:");
570     if (bloomFilter != null) {
571       out.println(FOUR_SPACES
572           + bloomFilter.toString().replaceAll(BloomFilterUtil.STATS_RECORD_SEP,
573               "\n" + FOUR_SPACES));
574     } else {
575       out.println(FOUR_SPACES + "Not present");
576     }
577   }
578
579   private static class KeyValueStatsCollector {
580     private final MetricRegistry metricsRegistry = new MetricRegistry();
581     private final ByteArrayOutputStream metricsOutput = new ByteArrayOutputStream();
582     private final SimpleReporter simpleReporter = SimpleReporter.forRegistry(metricsRegistry).
583         outputTo(new PrintStream(metricsOutput)).filter(MetricFilter.ALL).build();
584
585     Histogram keyLen = metricsRegistry.histogram(name(HFilePrettyPrinter.class, "Key length"));
586     Histogram valLen = metricsRegistry.histogram(name(HFilePrettyPrinter.class, "Val length"));
587     Histogram rowSizeBytes = metricsRegistry.histogram(
588       name(HFilePrettyPrinter.class, "Row size (bytes)"));
589     Histogram rowSizeCols = metricsRegistry.histogram(
590       name(HFilePrettyPrinter.class, "Row size (columns)"));
591
592     long curRowBytes = 0;
593     long curRowCols = 0;
594
595     byte[] biggestRow = null;
596
597     private Cell prevCell = null;
598     private long maxRowBytes = 0;
599     private long curRowKeyLength;
600
601     public void collect(Cell cell) {
602       valLen.update(cell.getValueLength());
603       if (prevCell != null &&
604           CellComparator.COMPARATOR.compareRows(prevCell, cell) != 0) {
605         // new row
606         collectRow();
607       }
608       curRowBytes += KeyValueUtil.length(cell);
609       curRowKeyLength = KeyValueUtil.keyLength(cell);
610       curRowCols++;
611       prevCell = cell;
612     }
613
614     private void collectRow() {
615       rowSizeBytes.update(curRowBytes);
616       rowSizeCols.update(curRowCols);
617       keyLen.update(curRowKeyLength);
618
619       if (curRowBytes > maxRowBytes && prevCell != null) {
620         biggestRow = CellUtil.cloneRow(prevCell);
621         maxRowBytes = curRowBytes;
622       }
623
624       curRowBytes = 0;
625       curRowCols = 0;
626     }
627
628     public void finish() {
629       if (curRowCols > 0) {
630         collectRow();
631       }
632     }
633
634     @Override
635     public String toString() {
636       if (prevCell == null)
637         return "no data available for statistics";
638
639       // Dump the metrics to the output stream
640       simpleReporter.stop();
641       simpleReporter.report();
642
643       return
644               metricsOutput.toString() +
645                       "Key of biggest row: " + Bytes.toStringBinary(biggestRow);
646     }
647   }
648
649   /**
650    * Almost identical to ConsoleReporter, but extending ScheduledReporter,
651    * as extending ConsoleReporter in this version of dropwizard is now too much trouble.
652    */
653   private static class SimpleReporter extends ScheduledReporter {
654     /**
655      * Returns a new {@link Builder} for {@link ConsoleReporter}.
656      *
657      * @param registry the registry to report
658      * @return a {@link Builder} instance for a {@link ConsoleReporter}
659      */
660     public static Builder forRegistry(MetricRegistry registry) {
661       return new Builder(registry);
662     }
663
664     /**
665      * A builder for {@link SimpleReporter} instances. Defaults to using the default locale and
666      * time zone, writing to {@code System.out}, converting rates to events/second, converting
667      * durations to milliseconds, and not filtering metrics.
668      */
669     public static class Builder {
670       private final MetricRegistry registry;
671       private PrintStream output;
672       private Locale locale;
673       private TimeZone timeZone;
674       private TimeUnit rateUnit;
675       private TimeUnit durationUnit;
676       private MetricFilter filter;
677
678       private Builder(MetricRegistry registry) {
679         this.registry = registry;
680         this.output = System.out;
681         this.locale = Locale.getDefault();
682         this.timeZone = TimeZone.getDefault();
683         this.rateUnit = TimeUnit.SECONDS;
684         this.durationUnit = TimeUnit.MILLISECONDS;
685         this.filter = MetricFilter.ALL;
686       }
687
688       /**
689        * Write to the given {@link PrintStream}.
690        *
691        * @param output a {@link PrintStream} instance.
692        * @return {@code this}
693        */
694       public Builder outputTo(PrintStream output) {
695         this.output = output;
696         return this;
697       }
698
699       /**
700        * Only report metrics which match the given filter.
701        *
702        * @param filter a {@link MetricFilter}
703        * @return {@code this}
704        */
705       public Builder filter(MetricFilter filter) {
706         this.filter = filter;
707         return this;
708       }
709
710       /**
711        * Builds a {@link ConsoleReporter} with the given properties.
712        *
713        * @return a {@link ConsoleReporter}
714        */
715       public SimpleReporter build() {
716         return new SimpleReporter(registry,
717             output,
718             locale,
719             timeZone,
720             rateUnit,
721             durationUnit,
722             filter);
723       }
724     }
725
726     private final PrintStream output;
727     private final Locale locale;
728     private final DateFormat dateFormat;
729
730     private SimpleReporter(MetricRegistry registry,
731                             PrintStream output,
732                             Locale locale,
733                             TimeZone timeZone,
734                             TimeUnit rateUnit,
735                             TimeUnit durationUnit,
736                             MetricFilter filter) {
737       super(registry, "simple-reporter", filter, rateUnit, durationUnit);
738       this.output = output;
739       this.locale = locale;
740 
741       this.dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT,
742           DateFormat.MEDIUM,
743           locale);
744       dateFormat.setTimeZone(timeZone);
745     }
746
747     @Override
748     public void report(SortedMap<String, Gauge> gauges,
749                        SortedMap<String, Counter> counters,
750                        SortedMap<String, Histogram> histograms,
751                        SortedMap<String, Meter> meters,
752                        SortedMap<String, Timer> timers) {
753       // we know we only have histograms
754       if (!histograms.isEmpty()) {
755         for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
756           output.print("   " + StringUtils.substringAfterLast(entry.getKey(), "."));
757           output.println(':');
758           printHistogram(entry.getValue());
759         }
760         output.println();
761       }
762
763       output.println();
764       output.flush();
765     }
766
767     private void printHistogram(Histogram histogram) {
768       Snapshot snapshot = histogram.getSnapshot();
769       output.printf(locale, "               min = %d%n", snapshot.getMin());
770       output.printf(locale, "               max = %d%n", snapshot.getMax());
771       output.printf(locale, "              mean = %2.2f%n", snapshot.getMean());
772       output.printf(locale, "            stddev = %2.2f%n", snapshot.getStdDev());
773       output.printf(locale, "            median = %2.2f%n", snapshot.getMedian());
774       output.printf(locale, "              75%% <= %2.2f%n", snapshot.get75thPercentile());
775       output.printf(locale, "              95%% <= %2.2f%n", snapshot.get95thPercentile());
776       output.printf(locale, "              98%% <= %2.2f%n", snapshot.get98thPercentile());
777       output.printf(locale, "              99%% <= %2.2f%n", snapshot.get99thPercentile());
778       output.printf(locale, "            99.9%% <= %2.2f%n", snapshot.get999thPercentile());
779       output.printf(locale, "             count = %d%n", histogram.getCount());
780     }
781   }
782
783   public static void main(String[] args) throws Exception {
784     Configuration conf = HBaseConfiguration.create();
785     // no need for a block cache
786     conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0);
787     int ret = ToolRunner.run(conf, new HFilePrettyPrinter(), args);
788     System.exit(ret);
789   }
790 }