View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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     * Just checks if heapsize grows when something is cached, and gets smaller
52     * when the same object is evicted
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      /*When we cache something HeapSize should always increase */
62      assertTrue(heapSize < ((HeapSize) toBeTested).heapSize());
63  
64      toBeTested.evictBlock(blocks[0].blockName);
65  
66      /*Post eviction, heapsize should be the same */
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              // if we run out of blocks to test, then we should stop the tests.
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     // Confirm empty
131     for (HFileBlockPair block : blocks) {
132       assertNull(toBeTested.getBlock(block.blockName, true, false, true));
133     }
134 
135     // Add blocks
136     for (HFileBlockPair block : blocks) {
137       toBeTested.cacheBlock(block.blockName, block.block);
138     }
139 
140     // Check if all blocks are properly cached and contain the right
141     // information, or the blocks are null.
142     // MapMaker makes no guarantees when it will evict, so neither can we.
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     // Re-add some duplicate blocks. Hope nothing breaks.
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             // BucketCache won't throw exception when caching already cached
160             // block
161             fail("Cache should not allow re-caching a block");
162           }
163         }
164       } catch (RuntimeException re) {
165         // expected
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     // add a thread to periodically evict and re-cache the block
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) {
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       /* No conflicting keys */
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 }