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}