001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.hfile;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotEquals;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import com.codahale.metrics.MetricRegistry;
025import java.io.ByteArrayOutputStream;
026import java.io.PrintStream;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.fs.FileSystem;
029import org.apache.hadoop.fs.Path;
030import org.apache.hadoop.hbase.HBaseTestingUtil;
031import org.apache.hadoop.hbase.regionserver.TestHRegionServerBulkLoad;
032import org.apache.hadoop.hbase.testclassification.IOTests;
033import org.apache.hadoop.hbase.testclassification.SmallTests;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.apache.hadoop.hbase.util.CommonFSUtils;
036import org.junit.jupiter.api.AfterEach;
037import org.junit.jupiter.api.BeforeEach;
038import org.junit.jupiter.api.Tag;
039import org.junit.jupiter.api.Test;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043@Tag(IOTests.TAG)
044@Tag(SmallTests.TAG)
045public class TestHFilePrettyPrinter {
046  private static final Logger LOG = LoggerFactory.getLogger(TestHFilePrettyPrinter.class);
047
048  private final static HBaseTestingUtil UTIL = new HBaseTestingUtil();
049  private static FileSystem fs;
050  private static Configuration conf;
051  private static byte[] cf = Bytes.toBytes("cf");
052  private static byte[] fam = Bytes.toBytes("fam");
053  private static byte[] value = Bytes.toBytes("val");
054  private static PrintStream original;
055  private static PrintStream ps;
056  private static ByteArrayOutputStream stream;
057
058  @BeforeEach
059  public void setup() throws Exception {
060    conf = UTIL.getConfiguration();
061    // Runs on local filesystem. Test does not need sync. Turn off checks.
062    conf.setBoolean(CommonFSUtils.UNSAFE_STREAM_CAPABILITY_ENFORCE, false);
063    fs = UTIL.getTestFileSystem();
064    stream = new ByteArrayOutputStream();
065    ps = new PrintStream(stream);
066  }
067
068  @AfterEach
069  public void teardown() {
070    original = System.out;
071    System.setOut(original);
072  }
073
074  @Test
075  public void testHFilePrettyPrinterNonRootDir() throws Exception {
076    Path fileNotInRootDir = UTIL.getDataTestDir("hfile");
077    TestHRegionServerBulkLoad.createHFile(fs, fileNotInRootDir, cf, fam, value, 1000);
078    assertNotEquals(UTIL.getDefaultRootDirPath(), fileNotInRootDir,
079      "directory used is not an HBase root dir");
080
081    System.setOut(ps);
082    new HFilePrettyPrinter(conf).run(new String[] { "-v", String.valueOf(fileNotInRootDir) });
083    String result = new String(stream.toByteArray());
084    String expectedResult = "Scanning -> " + fileNotInRootDir + "\n" + "Scanned kv count -> 1000\n";
085    assertEquals(expectedResult, result);
086  }
087
088  @Test
089  public void testHFilePrettyPrinterRootDir() throws Exception {
090    Path rootPath = CommonFSUtils.getRootDir(conf);
091    String rootString = rootPath + rootPath.SEPARATOR;
092    Path fileInRootDir = new Path(rootString + "hfile");
093    TestHRegionServerBulkLoad.createHFile(fs, fileInRootDir, cf, fam, value, 1000);
094    assertTrue(fileInRootDir.toString().startsWith(rootString), "directory used is a root dir");
095
096    System.setOut(ps);
097    HFilePrettyPrinter printer = new HFilePrettyPrinter();
098    printer.setConf(conf);
099    printer.processFile(fileInRootDir, true);
100    printer.run(new String[] { "-v", String.valueOf(fileInRootDir) });
101    String result = new String(stream.toByteArray());
102    String expectedResult = "Scanning -> " + fileInRootDir + "\n" + "Scanned kv count -> 1000\n";
103    assertEquals(expectedResult, result);
104  }
105
106  @Test
107  public void testHFilePrettyPrinterSeekFirstRow() throws Exception {
108    Path fileNotInRootDir = UTIL.getDataTestDir("hfile");
109    TestHRegionServerBulkLoad.createHFile(fs, fileNotInRootDir, cf, fam, value, 1000);
110    assertNotEquals(UTIL.getDefaultRootDirPath(), fileNotInRootDir,
111      "directory used is not an HBase root dir");
112
113    HFile.Reader reader =
114      HFile.createReader(fs, fileNotInRootDir, CacheConfig.DISABLED, true, conf);
115    String firstRowKey = new String(reader.getFirstRowKey().get());
116
117    System.setOut(ps);
118    new HFilePrettyPrinter(conf)
119      .run(new String[] { "-v", "-w" + firstRowKey, String.valueOf(fileNotInRootDir) });
120    String result = new String(stream.toByteArray());
121    String expectedResult = "Scanning -> " + fileNotInRootDir + "\n" + "Scanned kv count -> 1\n";
122    assertEquals(expectedResult, result);
123  }
124
125  @Test
126  public void testHistograms() throws Exception {
127    Path fileNotInRootDir = UTIL.getDataTestDir("hfile");
128    TestHRegionServerBulkLoad.createHFile(fs, fileNotInRootDir, cf, fam, value, 1000);
129    assertNotEquals(UTIL.getDefaultRootDirPath(), fileNotInRootDir,
130      "directory used is not an HBase root dir");
131
132    System.setOut(ps);
133    new HFilePrettyPrinter(conf).run(new String[] { "-s", "-d", String.valueOf(fileNotInRootDir) });
134    String result = stream.toString();
135    LOG.info(result);
136
137    // split out the output into sections based on the headers
138    String[] headers =
139      new String[] { "Key length", "Val length", "Row size (bytes)", "Row size (columns)" };
140    // for each section, there is a corresponding expected (count, range) pairs
141    int[][] expectations = new int[][] { new int[] { 0, 10, 1000, 50 }, new int[] { 0, 1, 1000, 3 },
142      new int[] { 0, 10, 1000, 50 }, new int[] { 1000, 1 }, };
143
144    for (int i = 0; i < headers.length - 1; i++) {
145      int idx = result.indexOf(headers[i]);
146      int nextIdx = result.indexOf(headers[i + 1]);
147
148      assertContainsRanges(result.substring(idx, nextIdx), expectations[i]);
149    }
150  }
151
152  private void assertContainsRanges(String result, int... rangeCountPairs) {
153    for (int i = 0; i < rangeCountPairs.length - 1; i += 2) {
154      String expected = rangeCountPairs[i + 1] + " <= " + rangeCountPairs[i];
155      assertTrue(result.contains(expected),
156        "expected:\n" + result + "\nto contain: '" + expected + "'");
157    }
158  }
159
160  @Test
161  public void testKeyValueStats() {
162    HFilePrettyPrinter.KeyValueStats stats =
163      new HFilePrettyPrinter.KeyValueStats(new MetricRegistry(), "test");
164    long[] ranges = stats.getRanges();
165    for (long range : ranges) {
166      stats.update(range - 1, true);
167    }
168
169    assertEquals(ranges[ranges.length - 1] - 1, stats.getMax());
170    assertEquals(ranges[0] - 1, stats.getMin());
171
172    int total = 1;
173    for (long range : ranges) {
174      long val = stats.getCountAtOrBelow(range);
175      assertEquals(total++, val);
176    }
177
178  }
179}