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