1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with this 4 * work for additional information regarding copyright ownership. The ASF 5 * licenses this file to you under the Apache License, Version 2.0 (the 6 * "License"); you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 * License for the specific language governing permissions and limitations 15 * under the License. 16 */ 17 package org.apache.hadoop.hbase.util.test; 18 19 import java.util.Random; 20 21 import org.apache.hadoop.hbase.classification.InterfaceAudience; 22 import org.apache.hadoop.hbase.util.Bytes; 23 import org.apache.hadoop.hbase.util.MD5Hash; 24 25 /** 26 * A generator of random keys and values for load testing. Keys are generated 27 * by converting numeric indexes to strings and prefixing them with an MD5 28 * hash. Values are generated by selecting value size in the configured range 29 * and generating a pseudo-random sequence of bytes seeded by key, column 30 * qualifier, and value size. 31 */ 32 @InterfaceAudience.Private 33 public class LoadTestKVGenerator { 34 35 /** A random number generator for determining value size */ 36 private Random randomForValueSize = new Random(); 37 38 private final int minValueSize; 39 private final int maxValueSize; 40 41 public LoadTestKVGenerator(int minValueSize, int maxValueSize) { 42 if (minValueSize <= 0 || maxValueSize <= 0) { 43 throw new IllegalArgumentException("Invalid min/max value sizes: " + 44 minValueSize + ", " + maxValueSize); 45 } 46 this.minValueSize = minValueSize; 47 this.maxValueSize = maxValueSize; 48 } 49 50 /** 51 * Verifies that the given byte array is the same as what would be generated 52 * for the given seed strings (row/cf/column/...). We are assuming that the 53 * value size is correct, and only verify the actual bytes. However, if the 54 * min/max value sizes are set sufficiently high, an accidental match should be 55 * extremely improbable. 56 */ 57 public static boolean verify(byte[] value, byte[]... seedStrings) { 58 byte[] expectedData = getValueForRowColumn(value.length, seedStrings); 59 return Bytes.equals(expectedData, value); 60 } 61 62 /** 63 * Converts the given key to string, and prefixes it with the MD5 hash of 64 * the index's string representation. 65 */ 66 public static String md5PrefixedKey(long key) { 67 String stringKey = Long.toString(key); 68 String md5hash = MD5Hash.getMD5AsHex(Bytes.toBytes(stringKey)); 69 70 // flip the key to randomize 71 return md5hash + "-" + stringKey; 72 } 73 74 /** 75 * Generates a value for the given key index and column qualifier. Size is 76 * selected randomly in the configured range. The generated value depends 77 * only on the combination of the strings passed (key/cf/column/...) and the selected 78 * value size. This allows to verify the actual value bytes when reading, as done 79 * in {#verify(byte[], byte[]...)} 80 * This method is as thread-safe as Random class. It appears that the worst bug ever 81 * found with the latter is that multiple threads will get some duplicate values, which 82 * we don't care about. 83 */ 84 public byte[] generateRandomSizeValue(byte[]... seedStrings) { 85 int dataSize = minValueSize; 86 if(minValueSize != maxValueSize) { 87 dataSize = minValueSize + randomForValueSize.nextInt(Math.abs(maxValueSize - minValueSize)); 88 } 89 return getValueForRowColumn(dataSize, seedStrings); 90 } 91 92 /** 93 * Generates random bytes of the given size for the given row and column 94 * qualifier. The random seed is fully determined by these parameters. 95 */ 96 private static byte[] getValueForRowColumn(int dataSize, byte[]... seedStrings) { 97 long seed = dataSize; 98 for (byte[] str : seedStrings) { 99 seed += Bytes.toString(str).hashCode(); 100 } 101 Random seededRandom = new Random(seed); 102 byte[] randomBytes = new byte[dataSize]; 103 seededRandom.nextBytes(randomBytes); 104 return randomBytes; 105 } 106 107 }