1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.io.hfile;
20
21 import static org.junit.Assert.assertArrayEquals;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26
27 import java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.util.Arrays;
30 import java.util.HashSet;
31 import java.util.Random;
32 import java.util.concurrent.ConcurrentLinkedQueue;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import org.apache.hadoop.conf.Configuration;
36 import org.apache.hadoop.hbase.HConstants;
37 import org.apache.hadoop.hbase.MultithreadedTestUtil;
38 import org.apache.hadoop.hbase.MultithreadedTestUtil.TestThread;
39 import org.apache.hadoop.hbase.io.HeapSize;
40 import org.apache.hadoop.hbase.io.compress.Compression;
41 import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
42 import org.apache.hadoop.hbase.util.ChecksumType;
43
44 import com.google.common.annotations.VisibleForTesting;
45
46 public class CacheTestUtils {
47
48 private static final boolean includesMemstoreTS = true;
49
50
51
52
53
54
55 public static void testHeapSizeChanges(final BlockCache toBeTested,
56 final int blockSize) {
57 HFileBlockPair[] blocks = generateHFileBlocks(blockSize, 1);
58 long heapSize = ((HeapSize) toBeTested).heapSize();
59 toBeTested.cacheBlock(blocks[0].blockName, blocks[0].block);
60
61
62 assertTrue(heapSize < ((HeapSize) toBeTested).heapSize());
63
64 toBeTested.evictBlock(blocks[0].blockName);
65
66
67 assertEquals(heapSize, ((HeapSize) toBeTested).heapSize());
68 }
69
70 public static void testCacheMultiThreaded(final BlockCache toBeTested,
71 final int blockSize, final int numThreads, final int numQueries,
72 final double passingScore) throws Exception {
73
74 Configuration conf = new Configuration();
75 MultithreadedTestUtil.TestContext ctx = new MultithreadedTestUtil.TestContext(
76 conf);
77
78 final AtomicInteger totalQueries = new AtomicInteger();
79 final ConcurrentLinkedQueue<HFileBlockPair> blocksToTest = new ConcurrentLinkedQueue<HFileBlockPair>();
80 final AtomicInteger hits = new AtomicInteger();
81 final AtomicInteger miss = new AtomicInteger();
82
83 HFileBlockPair[] blocks = generateHFileBlocks(numQueries, blockSize);
84 blocksToTest.addAll(Arrays.asList(blocks));
85
86 for (int i = 0; i < numThreads; i++) {
87 TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) {
88 @Override
89 public void doAnAction() throws Exception {
90 if (!blocksToTest.isEmpty()) {
91 HFileBlockPair ourBlock = blocksToTest.poll();
92
93 if (ourBlock == null) {
94 ctx.setStopFlag(true);
95 return;
96 }
97 toBeTested.cacheBlock(ourBlock.blockName, ourBlock.block);
98 Cacheable retrievedBlock = toBeTested.getBlock(ourBlock.blockName,
99 false, false, true);
100 if (retrievedBlock != null) {
101 assertEquals(ourBlock.block, retrievedBlock);
102 toBeTested.evictBlock(ourBlock.blockName);
103 hits.incrementAndGet();
104 assertNull(toBeTested.getBlock(ourBlock.blockName, false, false, true));
105 } else {
106 miss.incrementAndGet();
107 }
108 totalQueries.incrementAndGet();
109 }
110 }
111 };
112 t.setDaemon(true);
113 ctx.addThread(t);
114 }
115 ctx.startThreads();
116 while (!blocksToTest.isEmpty() && ctx.shouldRun()) {
117 Thread.sleep(10);
118 }
119 ctx.stop();
120 if (hits.get() / ((double) hits.get() + (double) miss.get()) < passingScore) {
121 fail("Too many nulls returned. Hits: " + hits.get() + " Misses: "
122 + miss.get());
123 }
124 }
125
126 public static void testCacheSimple(BlockCache toBeTested, int blockSize,
127 int numBlocks) throws Exception {
128
129 HFileBlockPair[] blocks = generateHFileBlocks(numBlocks, blockSize);
130
131 for (HFileBlockPair block : blocks) {
132 assertNull(toBeTested.getBlock(block.blockName, true, false, true));
133 }
134
135
136 for (HFileBlockPair block : blocks) {
137 toBeTested.cacheBlock(block.blockName, block.block);
138 }
139
140
141
142
143
144 for (HFileBlockPair block : blocks) {
145 HFileBlock buf = (HFileBlock) toBeTested.getBlock(block.blockName, true, false, true);
146 if (buf != null) {
147 assertEquals(block.block, buf);
148 }
149
150 }
151
152
153
154 for (HFileBlockPair block : blocks) {
155 try {
156 if (toBeTested.getBlock(block.blockName, true, false, true) != null) {
157 toBeTested.cacheBlock(block.blockName, block.block);
158 if (!(toBeTested instanceof BucketCache)) {
159
160
161 fail("Cache should not allow re-caching a block");
162 }
163 }
164 } catch (RuntimeException re) {
165
166 }
167 }
168
169 }
170
171 public static void hammerSingleKey(final BlockCache toBeTested,
172 int BlockSize, int numThreads, int numQueries) throws Exception {
173 final BlockCacheKey key = new BlockCacheKey("key", 0);
174 final byte[] buf = new byte[5 * 1024];
175 Arrays.fill(buf, (byte) 5);
176
177 final ByteArrayCacheable bac = new ByteArrayCacheable(buf);
178 Configuration conf = new Configuration();
179 MultithreadedTestUtil.TestContext ctx = new MultithreadedTestUtil.TestContext(
180 conf);
181
182 final AtomicInteger totalQueries = new AtomicInteger();
183 toBeTested.cacheBlock(key, bac);
184
185 for (int i = 0; i < numThreads; i++) {
186 TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) {
187 @Override
188 public void doAnAction() throws Exception {
189 ByteArrayCacheable returned = (ByteArrayCacheable) toBeTested
190 .getBlock(key, false, false, true);
191 if (returned != null) {
192 assertArrayEquals(buf, returned.buf);
193 } else {
194 Thread.sleep(10);
195 }
196 totalQueries.incrementAndGet();
197 }
198 };
199
200 t.setDaemon(true);
201 ctx.addThread(t);
202 }
203
204
205 final long blockEvictPeriod = 50;
206 TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) {
207 @Override
208 public void doAnAction() throws Exception {
209 toBeTested.evictBlock(key);
210 toBeTested.cacheBlock(key, bac);
211 Thread.sleep(blockEvictPeriod);
212 }
213 };
214 t.setDaemon(true);
215 ctx.addThread(t);
216
217 ctx.startThreads();
218 while (totalQueries.get() < numQueries && ctx.shouldRun()) {
219 Thread.sleep(10);
220 }
221 ctx.stop();
222 }
223
224 public static void hammerEviction(final BlockCache toBeTested, int BlockSize,
225 int numThreads, int numQueries) throws Exception {
226
227 Configuration conf = new Configuration();
228 MultithreadedTestUtil.TestContext ctx = new MultithreadedTestUtil.TestContext(
229 conf);
230
231 final AtomicInteger totalQueries = new AtomicInteger();
232
233 for (int i = 0; i < numThreads; i++) {
234 final int finalI = i;
235
236 final byte[] buf = new byte[5 * 1024];
237 TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) {
238 @Override
239 public void doAnAction() throws Exception {
240 for (int j = 0; j < 100; j++) {
241 BlockCacheKey key = new BlockCacheKey("key_" + finalI + "_" + j, 0);
242 Arrays.fill(buf, (byte) (finalI * j));
243 final ByteArrayCacheable bac = new ByteArrayCacheable(buf);
244
245 ByteArrayCacheable gotBack = (ByteArrayCacheable) toBeTested
246 .getBlock(key, true, false, true);
247 if (gotBack != null) {
248 assertArrayEquals(gotBack.buf, bac.buf);
249 } else {
250 toBeTested.cacheBlock(key, bac);
251 }
252 }
253 totalQueries.incrementAndGet();
254 }
255 };
256
257 t.setDaemon(true);
258 ctx.addThread(t);
259 }
260
261 ctx.startThreads();
262 while (totalQueries.get() < numQueries && ctx.shouldRun()) {
263 Thread.sleep(10);
264 }
265 ctx.stop();
266
267 assertTrue(toBeTested.getStats().getEvictedCount() > 0);
268 }
269
270 public static class ByteArrayCacheable implements Cacheable {
271
272 static final CacheableDeserializer<Cacheable> blockDeserializer =
273 new CacheableDeserializer<Cacheable>() {
274
275 @Override
276 public Cacheable deserialize(ByteBuffer b) throws IOException {
277 int len = b.getInt();
278 Thread.yield();
279 byte buf[] = new byte[len];
280 b.get(buf);
281 return new ByteArrayCacheable(buf);
282 }
283
284 @Override
285 public int getDeserialiserIdentifier() {
286 return deserializerIdentifier;
287 }
288
289 @Override
290 public Cacheable deserialize(ByteBuffer b, boolean reuse)
291 throws IOException {
292 return deserialize(b);
293 }
294 };
295
296 final byte[] buf;
297
298 public ByteArrayCacheable(byte[] buf) {
299 this.buf = buf;
300 }
301
302 @Override
303 public long heapSize() {
304 return 4 + buf.length;
305 }
306
307 @Override
308 public int getSerializedLength() {
309 return 4 + buf.length;
310 }
311
312 @Override
313 public void serialize(ByteBuffer destination, boolean includeNextBlockOnDiskSize) {
314 destination.putInt(buf.length);
315 Thread.yield();
316 destination.put(buf);
317 destination.rewind();
318 }
319
320 @Override
321 public CacheableDeserializer<Cacheable> getDeserializer() {
322 return blockDeserializer;
323 }
324
325 private static final int deserializerIdentifier;
326 static {
327 deserializerIdentifier = CacheableDeserializerIdManager
328 .registerDeserializer(blockDeserializer);
329 }
330
331 @Override
332 public BlockType getBlockType() {
333 return BlockType.DATA;
334 }
335 }
336
337
338 public static HFileBlockPair[] generateHFileBlocks(int blockSize, int numBlocks) {
339 HFileBlockPair[] returnedBlocks = new HFileBlockPair[numBlocks];
340 Random rand = new Random();
341 HashSet<String> usedStrings = new HashSet<String>();
342 for (int i = 0; i < numBlocks; i++) {
343 ByteBuffer cachedBuffer = ByteBuffer.allocate(blockSize);
344 rand.nextBytes(cachedBuffer.array());
345 cachedBuffer.rewind();
346 int onDiskSizeWithoutHeader = blockSize;
347 int uncompressedSizeWithoutHeader = blockSize;
348 long prevBlockOffset = rand.nextLong();
349 BlockType.DATA.write(cachedBuffer);
350 cachedBuffer.putInt(onDiskSizeWithoutHeader);
351 cachedBuffer.putInt(uncompressedSizeWithoutHeader);
352 cachedBuffer.putLong(prevBlockOffset);
353 cachedBuffer.rewind();
354 HFileContext meta = new HFileContextBuilder()
355 .withHBaseCheckSum(false)
356 .withIncludesMvcc(includesMemstoreTS)
357 .withIncludesTags(false)
358 .withCompression(Compression.Algorithm.NONE)
359 .withBytesPerCheckSum(0)
360 .withChecksumType(ChecksumType.NULL)
361 .build();
362 HFileBlock generated = new HFileBlock(BlockType.DATA,
363 onDiskSizeWithoutHeader, uncompressedSizeWithoutHeader,
364 prevBlockOffset, cachedBuffer, HFileBlock.DONT_FILL_HEADER,
365 blockSize,
366 onDiskSizeWithoutHeader + HConstants.HFILEBLOCK_HEADER_SIZE, -1, meta);
367
368 String strKey;
369
370 for (strKey = new Long(rand.nextLong()).toString(); !usedStrings
371 .add(strKey); strKey = new Long(rand.nextLong()).toString())
372 ;
373
374 returnedBlocks[i] = new HFileBlockPair();
375 returnedBlocks[i].blockName = new BlockCacheKey(strKey, 0);
376 returnedBlocks[i].block = generated;
377 }
378 return returnedBlocks;
379 }
380
381 @VisibleForTesting
382 public static class HFileBlockPair {
383 BlockCacheKey blockName;
384 HFileBlock block;
385
386 public BlockCacheKey getBlockName() {
387 return this.blockName;
388 }
389
390 public HFileBlock getBlock() {
391 return this.block;
392 }
393 }
394
395 public static void getBlockAndAssertEquals(BlockCache cache, BlockCacheKey key,
396 Cacheable blockToCache, ByteBuffer destBuffer,
397 ByteBuffer expectedBuffer) {
398 destBuffer.clear();
399 cache.cacheBlock(key, blockToCache);
400 Cacheable actualBlock = cache.getBlock(key, false, false, false);
401 actualBlock.serialize(destBuffer, true);
402 assertEquals(expectedBuffer, destBuffer);
403 }
404 }