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.regionserver;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Random;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.fs.Path;
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.HBaseTestingUtility;
028import org.apache.hadoop.hbase.HConstants;
029import org.apache.hadoop.hbase.KeyValue;
030import org.apache.hadoop.hbase.KeyValueUtil;
031import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
032import org.apache.hadoop.hbase.io.hfile.CacheConfig;
033import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
034
035/**
036 * Test seek performance for encoded data blocks. Read an HFile and do several random seeks.
037 */
038public class EncodedSeekPerformanceTest {
039  private static final double NANOSEC_IN_SEC = 1000.0 * 1000.0 * 1000.0;
040  private static final double BYTES_IN_MEGABYTES = 1024.0 * 1024.0;
041  /** Default number of seeks which will be used in benchmark. */
042  public static int DEFAULT_NUMBER_OF_SEEKS = 10000;
043
044  private final HBaseTestingUtility testingUtility = new HBaseTestingUtility();
045  private Configuration configuration = testingUtility.getConfiguration();
046  private CacheConfig cacheConf = new CacheConfig(configuration);
047  private Random randomizer;
048  private int numberOfSeeks;
049
050  /** Use this benchmark with default options */
051  public EncodedSeekPerformanceTest() {
052    configuration.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.5f);
053    randomizer = new Random(42L);
054    numberOfSeeks = DEFAULT_NUMBER_OF_SEEKS;
055  }
056
057  private List<Cell> prepareListOfTestSeeks(Path path) throws IOException {
058    List<Cell> allKeyValues = new ArrayList<>();
059
060    // read all of the key values
061    HStoreFile storeFile = new HStoreFile(testingUtility.getTestFileSystem(), path, configuration,
062      cacheConf, BloomType.NONE, true);
063    storeFile.initReader();
064    StoreFileReader reader = storeFile.getReader();
065    StoreFileScanner scanner = reader.getStoreFileScanner(true, false, false, 0, 0, false);
066    Cell current;
067
068    scanner.seek(KeyValue.LOWESTKEY);
069    while (null != (current = scanner.next())) {
070      allKeyValues.add(current);
071    }
072
073    storeFile.closeStoreFile(cacheConf.shouldEvictOnClose());
074
075    // pick seeks by random
076    List<Cell> seeks = new ArrayList<>();
077    for (int i = 0; i < numberOfSeeks; ++i) {
078      Cell keyValue = allKeyValues.get(randomizer.nextInt(allKeyValues.size()));
079      seeks.add(keyValue);
080    }
081
082    clearBlockCache();
083
084    return seeks;
085  }
086
087  private void runTest(Path path, DataBlockEncoding blockEncoding, List<Cell> seeks)
088    throws IOException {
089    // read all of the key values
090    HStoreFile storeFile = new HStoreFile(testingUtility.getTestFileSystem(), path, configuration,
091      cacheConf, BloomType.NONE, true);
092    storeFile.initReader();
093    long totalSize = 0;
094
095    StoreFileReader reader = storeFile.getReader();
096    StoreFileScanner scanner = reader.getStoreFileScanner(true, false, false, 0, 0, false);
097
098    long startReadingTime = System.nanoTime();
099    Cell current;
100    scanner.seek(KeyValue.LOWESTKEY);
101    while (null != (current = scanner.next())) { // just iterate it!
102      if (KeyValueUtil.ensureKeyValue(current).getLength() < 0) {
103        throw new IOException("Negative KV size: " + current);
104      }
105      totalSize += KeyValueUtil.ensureKeyValue(current).getLength();
106    }
107    long finishReadingTime = System.nanoTime();
108
109    // do seeks
110    long startSeeksTime = System.nanoTime();
111    for (Cell keyValue : seeks) {
112      scanner.seek(keyValue);
113      Cell toVerify = scanner.next();
114      if (!keyValue.equals(toVerify)) {
115        System.out
116          .println(String.format("KeyValue doesn't match:\n" + "Orig key: %s\n" + "Ret key:  %s",
117            KeyValueUtil.ensureKeyValue(keyValue).getKeyString(),
118            KeyValueUtil.ensureKeyValue(toVerify).getKeyString()));
119        break;
120      }
121    }
122    long finishSeeksTime = System.nanoTime();
123    if (finishSeeksTime < startSeeksTime) {
124      throw new AssertionError(
125        "Finish time " + finishSeeksTime + " is earlier than start time " + startSeeksTime);
126    }
127
128    // write some stats
129    double readInMbPerSec =
130      (totalSize * NANOSEC_IN_SEC) / (BYTES_IN_MEGABYTES * (finishReadingTime - startReadingTime));
131    double seeksPerSec = (seeks.size() * NANOSEC_IN_SEC) / (finishSeeksTime - startSeeksTime);
132
133    storeFile.closeStoreFile(cacheConf.shouldEvictOnClose());
134    clearBlockCache();
135
136    System.out.println(blockEncoding);
137    System.out.printf("  Read speed:       %8.2f (MB/s)\n", readInMbPerSec);
138    System.out.printf("  Seeks per second: %8.2f (#/s)\n", seeksPerSec);
139    System.out.printf("  Total KV size:    %d\n", totalSize);
140  }
141
142  /**
143   * @param path      Path to the HFile which will be used.
144   * @param encodings the data block encoding algorithms to use
145   * @throws IOException if there is a bug while reading from disk
146   */
147  public void runTests(Path path, DataBlockEncoding[] encodings) throws IOException {
148    List<Cell> seeks = prepareListOfTestSeeks(path);
149
150    for (DataBlockEncoding blockEncoding : encodings) {
151      runTest(path, blockEncoding, seeks);
152    }
153  }
154
155  /**
156   * Command line interface:
157   * @param args Takes one argument - file size.
158   * @throws IOException if there is a bug while reading from disk
159   */
160  public static void main(final String[] args) throws IOException {
161    if (args.length < 1) {
162      printUsage();
163      System.exit(-1);
164    }
165
166    Path path = new Path(args[0]);
167
168    // TODO, this test doesn't work as expected any more. Need to fix.
169    EncodedSeekPerformanceTest utility = new EncodedSeekPerformanceTest();
170    utility.runTests(path, DataBlockEncoding.values());
171
172    System.exit(0);
173  }
174
175  private static void printUsage() {
176    System.out.println("Usage: one argument, name of the HFile");
177  }
178
179  private void clearBlockCache() {
180    ((LruBlockCache) cacheConf.getBlockCache().get()).clearCache();
181  }
182}