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