1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.io.hfile;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.nio.ByteBuffer;
26  import java.util.Collection;
27  import java.util.Map;
28  import java.util.Random;
29  
30  import org.apache.hadoop.hbase.HBaseTestingUtility;
31  import org.apache.hadoop.hbase.MediumTests;
32  import org.apache.hadoop.hbase.io.HeapSize;
33  import org.apache.hadoop.hbase.io.hfile.LruBlockCache.EvictionThread;
34  import org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics;
35  import org.apache.hadoop.hbase.regionserver.metrics.TestSchemaMetrics;
36  import org.apache.hadoop.hbase.util.ClassSize;
37  import org.junit.After;
38  import org.junit.Before;
39  import org.junit.Test;
40  import org.junit.experimental.categories.Category;
41  import org.junit.runner.RunWith;
42  import org.junit.runners.Parameterized;
43  import org.junit.runners.Parameterized.Parameters;
44  
45  /**
46   * Tests the concurrent LruBlockCache.<p>
47   *
48   * Tests will ensure it grows and shrinks in size properly,
49   * evictions run when they're supposed to and do what they should,
50   * and that cached blocks are accessible when expected to be.
51   */
52  @RunWith(Parameterized.class)
53  @Category(MediumTests.class)
54  public class TestLruBlockCache {
55  
56    private Map<String, Long> startingMetrics;
57    private final HBaseTestingUtility TEST_UTIL =
58        new HBaseTestingUtility();
59  
60    public TestLruBlockCache(boolean useTableName) {
61      SchemaMetrics.setUseTableNameInTest(useTableName);
62    }
63  
64    @Parameters
65    public static Collection<Object[]> parameters() {
66      return TestSchemaMetrics.parameters();
67    }
68  
69    @Before
70    public void setUp() throws Exception {
71      startingMetrics = SchemaMetrics.getMetricsSnapshot();
72    }
73  
74    @After
75    public void tearDown() throws Exception {
76      SchemaMetrics.validateMetricChanges(startingMetrics);
77    }
78  
79    @Test
80    public void testBackgroundEvictionThread() throws Exception {
81      long maxSize = 100000;
82      long blockSize = calculateBlockSizeDefault(maxSize, 9); // room for 9, will evict
83  
84      LruBlockCache cache = new LruBlockCache(maxSize, blockSize, TEST_UTIL.getConfiguration());
85  
86      CachedItem [] blocks = generateFixedBlocks(10, blockSize, "block");
87  
88      EvictionThread evictionThread = cache.getEvictionThread();
89      assertTrue(evictionThread != null);
90  
91      // Make sure eviction thread has entered run method
92      while (!evictionThread.isEnteringRun()) {
93        Thread.sleep(1);
94      }
95  
96      // Add all the blocks
97      for (CachedItem block : blocks) {
98        cache.cacheBlock(block.cacheKey, block);
99      }
100 
101     // Let the eviction run
102     int n = 0;
103     while(cache.getEvictionCount() == 0) {
104       Thread.sleep(200);
105       assertTrue(n++ < 20);
106     }
107     System.out.println("Background Evictions run: " + cache.getEvictionCount());
108 
109     // A single eviction run should have occurred
110     assertEquals(cache.getEvictionCount(), 1);
111   }
112 
113   @Test
114   public void testCacheSimple() throws Exception {
115 
116     long maxSize = 1000000;
117     long blockSize = calculateBlockSizeDefault(maxSize, 101);
118 
119     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, TEST_UTIL.getConfiguration());
120 
121     CachedItem [] blocks = generateRandomBlocks(100, blockSize);
122 
123     long expectedCacheSize = cache.heapSize();
124 
125     // Confirm empty
126     for (CachedItem block : blocks) {
127       assertTrue(cache.getBlock(block.cacheKey, true, false) == null);
128     }
129 
130     // Add blocks
131     for (CachedItem block : blocks) {
132       cache.cacheBlock(block.cacheKey, block);
133       expectedCacheSize += block.cacheBlockHeapSize();
134     }
135 
136     // Verify correctly calculated cache heap size
137     assertEquals(expectedCacheSize, cache.heapSize());
138 
139     // Check if all blocks are properly cached and retrieved
140     for (CachedItem block : blocks) {
141       HeapSize buf = cache.getBlock(block.cacheKey, true, false);
142       assertTrue(buf != null);
143       assertEquals(buf.heapSize(), block.heapSize());
144     }
145 
146     // Verify correctly calculated cache heap size
147     assertEquals(expectedCacheSize, cache.heapSize());
148 
149     // Check if all blocks are properly cached and retrieved
150     for (CachedItem block : blocks) {
151       HeapSize buf = cache.getBlock(block.cacheKey, true, false);
152       assertTrue(buf != null);
153       assertEquals(buf.heapSize(), block.heapSize());
154     }
155 
156     // Expect no evictions
157     assertEquals(0, cache.getEvictionCount());
158     Thread t = new LruBlockCache.StatisticsThread(cache);
159     t.start();
160     t.join();
161   }
162 
163   @Test
164   public void testCacheEvictionSimple() throws Exception {
165 
166     long maxSize = 100000;
167     long blockSize = calculateBlockSizeDefault(maxSize, 10);
168 
169     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false, TEST_UTIL.getConfiguration());
170 
171     CachedItem [] blocks = generateFixedBlocks(10, blockSize, "block");
172 
173     long expectedCacheSize = cache.heapSize();
174 
175     // Add all the blocks
176     for (CachedItem block : blocks) {
177       cache.cacheBlock(block.cacheKey, block);
178       expectedCacheSize += block.cacheBlockHeapSize();
179     }
180 
181     // A single eviction run should have occurred
182     assertEquals(1, cache.getEvictionCount());
183 
184     // Our expected size overruns acceptable limit
185     assertTrue(expectedCacheSize >
186       (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
187 
188     // But the cache did not grow beyond max
189     assertTrue(cache.heapSize() < maxSize);
190 
191     // And is still below the acceptable limit
192     assertTrue(cache.heapSize() <
193         (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
194 
195     // All blocks except block 0 and 1 should be in the cache
196     assertTrue(cache.getBlock(blocks[0].cacheKey, true, false) == null);
197     assertTrue(cache.getBlock(blocks[1].cacheKey, true, false) == null);
198     for(int i=2;i<blocks.length;i++) {
199       assertEquals(cache.getBlock(blocks[i].cacheKey, true, false),
200           blocks[i]);
201     }
202   }
203 
204   @Test
205   public void testCacheEvictionTwoPriorities() throws Exception {
206 
207     long maxSize = 100000;
208     long blockSize = calculateBlockSizeDefault(maxSize, 10);
209 
210     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false, TEST_UTIL.getConfiguration());
211 
212     CachedItem [] singleBlocks = generateFixedBlocks(5, 10000, "single");
213     CachedItem [] multiBlocks = generateFixedBlocks(5, 10000, "multi");
214 
215     long expectedCacheSize = cache.heapSize();
216 
217     // Add and get the multi blocks
218     for (CachedItem block : multiBlocks) {
219       cache.cacheBlock(block.cacheKey, block);
220       expectedCacheSize += block.cacheBlockHeapSize();
221       assertEquals(cache.getBlock(block.cacheKey, true, false), block);
222     }
223 
224     // Add the single blocks (no get)
225     for (CachedItem block : singleBlocks) {
226       cache.cacheBlock(block.cacheKey, block);
227       expectedCacheSize += block.heapSize();
228     }
229 
230     // A single eviction run should have occurred
231     assertEquals(cache.getEvictionCount(), 1);
232 
233     // We expect two entries evicted
234     assertEquals(cache.getEvictedCount(), 2);
235 
236     // Our expected size overruns acceptable limit
237     assertTrue(expectedCacheSize >
238       (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
239 
240     // But the cache did not grow beyond max
241     assertTrue(cache.heapSize() <= maxSize);
242 
243     // And is now below the acceptable limit
244     assertTrue(cache.heapSize() <=
245         (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
246 
247     // We expect fairness across the two priorities.
248     // This test makes multi go barely over its limit, in-memory
249     // empty, and the rest in single.  Two single evictions and
250     // one multi eviction expected.
251     assertTrue(cache.getBlock(singleBlocks[0].cacheKey, true, false) == null);
252     assertTrue(cache.getBlock(multiBlocks[0].cacheKey, true, false) == null);
253 
254     // And all others to be cached
255     for(int i=1;i<4;i++) {
256       assertEquals(cache.getBlock(singleBlocks[i].cacheKey, true, false),
257           singleBlocks[i]);
258       assertEquals(cache.getBlock(multiBlocks[i].cacheKey, true, false),
259           multiBlocks[i]);
260     }
261   }
262 
263   @Test
264   public void testCacheEvictionThreePriorities() throws Exception {
265 
266     long maxSize = 100000;
267     long blockSize = calculateBlockSize(maxSize, 10);
268 
269     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
270         (int)Math.ceil(1.2*maxSize/blockSize),
271         LruBlockCache.DEFAULT_LOAD_FACTOR,
272         LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
273         0.98f, // min
274         0.99f, // acceptable
275         0.33f, // single
276         0.33f, // multi
277         0.34f);// memory
278 
279 
280     CachedItem [] singleBlocks = generateFixedBlocks(5, blockSize, "single");
281     CachedItem [] multiBlocks = generateFixedBlocks(5, blockSize, "multi");
282     CachedItem [] memoryBlocks = generateFixedBlocks(5, blockSize, "memory");
283 
284     long expectedCacheSize = cache.heapSize();
285 
286     // Add 3 blocks from each priority
287     for(int i=0;i<3;i++) {
288 
289       // Just add single blocks
290       cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
291       expectedCacheSize += singleBlocks[i].cacheBlockHeapSize();
292 
293       // Add and get multi blocks
294       cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]);
295       expectedCacheSize += multiBlocks[i].cacheBlockHeapSize();
296       cache.getBlock(multiBlocks[i].cacheKey, true, false);
297 
298       // Add memory blocks as such
299       cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true);
300       expectedCacheSize += memoryBlocks[i].cacheBlockHeapSize();
301 
302     }
303 
304     // Do not expect any evictions yet
305     assertEquals(0, cache.getEvictionCount());
306 
307     // Verify cache size
308     assertEquals(expectedCacheSize, cache.heapSize());
309 
310     // Insert a single block, oldest single should be evicted
311     cache.cacheBlock(singleBlocks[3].cacheKey, singleBlocks[3]);
312 
313     // Single eviction, one thing evicted
314     assertEquals(1, cache.getEvictionCount());
315     assertEquals(1, cache.getEvictedCount());
316 
317     // Verify oldest single block is the one evicted
318     assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false));
319 
320     // Change the oldest remaining single block to a multi
321     cache.getBlock(singleBlocks[1].cacheKey, true, false);
322 
323     // Insert another single block
324     cache.cacheBlock(singleBlocks[4].cacheKey, singleBlocks[4]);
325 
326     // Two evictions, two evicted.
327     assertEquals(2, cache.getEvictionCount());
328     assertEquals(2, cache.getEvictedCount());
329 
330     // Oldest multi block should be evicted now
331     assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false));
332 
333     // Insert another memory block
334     cache.cacheBlock(memoryBlocks[3].cacheKey, memoryBlocks[3], true);
335 
336     // Three evictions, three evicted.
337     assertEquals(3, cache.getEvictionCount());
338     assertEquals(3, cache.getEvictedCount());
339 
340     // Oldest memory block should be evicted now
341     assertEquals(null, cache.getBlock(memoryBlocks[0].cacheKey, true, false));
342 
343     // Add a block that is twice as big (should force two evictions)
344     CachedItem [] bigBlocks = generateFixedBlocks(3, blockSize*3, "big");
345     cache.cacheBlock(bigBlocks[0].cacheKey, bigBlocks[0]);
346 
347     // Four evictions, six evicted (inserted block 3X size, expect +3 evicted)
348     assertEquals(4, cache.getEvictionCount());
349     assertEquals(6, cache.getEvictedCount());
350 
351     // Expect three remaining singles to be evicted
352     assertEquals(null, cache.getBlock(singleBlocks[2].cacheKey, true, false));
353     assertEquals(null, cache.getBlock(singleBlocks[3].cacheKey, true, false));
354     assertEquals(null, cache.getBlock(singleBlocks[4].cacheKey, true, false));
355 
356     // Make the big block a multi block
357     cache.getBlock(bigBlocks[0].cacheKey, true, false);
358 
359     // Cache another single big block
360     cache.cacheBlock(bigBlocks[1].cacheKey, bigBlocks[1]);
361 
362     // Five evictions, nine evicted (3 new)
363     assertEquals(5, cache.getEvictionCount());
364     assertEquals(9, cache.getEvictedCount());
365 
366     // Expect three remaining multis to be evicted
367     assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false));
368     assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false));
369     assertEquals(null, cache.getBlock(multiBlocks[2].cacheKey, true, false));
370 
371     // Cache a big memory block
372     cache.cacheBlock(bigBlocks[2].cacheKey, bigBlocks[2], true);
373 
374     // Six evictions, twelve evicted (3 new)
375     assertEquals(6, cache.getEvictionCount());
376     assertEquals(12, cache.getEvictedCount());
377 
378     // Expect three remaining in-memory to be evicted
379     assertEquals(null, cache.getBlock(memoryBlocks[1].cacheKey, true, false));
380     assertEquals(null, cache.getBlock(memoryBlocks[2].cacheKey, true, false));
381     assertEquals(null, cache.getBlock(memoryBlocks[3].cacheKey, true, false));
382 
383 
384   }
385 
386   // test scan resistance
387   @Test
388   public void testScanResistance() throws Exception {
389 
390     long maxSize = 100000;
391     long blockSize = calculateBlockSize(maxSize, 10);
392 
393     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
394         (int)Math.ceil(1.2*maxSize/blockSize),
395         LruBlockCache.DEFAULT_LOAD_FACTOR,
396         LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
397         0.66f, // min
398         0.99f, // acceptable
399         0.33f, // single
400         0.33f, // multi
401         0.34f);// memory
402 
403     CachedItem [] singleBlocks = generateFixedBlocks(20, blockSize, "single");
404     CachedItem [] multiBlocks = generateFixedBlocks(5, blockSize, "multi");
405 
406     // Add 5 multi blocks
407     for (CachedItem block : multiBlocks) {
408       cache.cacheBlock(block.cacheKey, block);
409       cache.getBlock(block.cacheKey, true, false);
410     }
411 
412     // Add 5 single blocks
413     for(int i=0;i<5;i++) {
414       cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
415     }
416 
417     // An eviction ran
418     assertEquals(1, cache.getEvictionCount());
419 
420     // To drop down to 2/3 capacity, we'll need to evict 4 blocks
421     assertEquals(4, cache.getEvictedCount());
422 
423     // Should have been taken off equally from single and multi
424     assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false));
425     assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false));
426     assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false));
427     assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false));
428 
429     // Let's keep "scanning" by adding single blocks.  From here on we only
430     // expect evictions from the single bucket.
431 
432     // Every time we reach 10 total blocks (every 4 inserts) we get 4 single
433     // blocks evicted.  Inserting 13 blocks should yield 3 more evictions and
434     // 12 more evicted.
435 
436     for(int i=5;i<18;i++) {
437       cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
438     }
439 
440     // 4 total evictions, 16 total evicted
441     assertEquals(4, cache.getEvictionCount());
442     assertEquals(16, cache.getEvictedCount());
443 
444     // Should now have 7 total blocks
445     assertEquals(7, cache.size());
446 
447   }
448 
449   // test setMaxSize
450   @Test
451   public void testResizeBlockCache() throws Exception {
452 
453     long maxSize = 300000;
454     long blockSize = calculateBlockSize(maxSize, 31);
455 
456     LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
457         (int)Math.ceil(1.2*maxSize/blockSize),
458         LruBlockCache.DEFAULT_LOAD_FACTOR,
459         LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
460         0.98f, // min
461         0.99f, // acceptable
462         0.33f, // single
463         0.33f, // multi
464         0.34f);// memory
465 
466     CachedItem [] singleBlocks = generateFixedBlocks(10, blockSize, "single");
467     CachedItem [] multiBlocks = generateFixedBlocks(10, blockSize, "multi");
468     CachedItem [] memoryBlocks = generateFixedBlocks(10, blockSize, "memory");
469 
470     // Add all blocks from all priorities
471     for(int i=0;i<10;i++) {
472 
473       // Just add single blocks
474       cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
475 
476       // Add and get multi blocks
477       cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]);
478       cache.getBlock(multiBlocks[i].cacheKey, true, false);
479 
480       // Add memory blocks as such
481       cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true);
482     }
483 
484     // Do not expect any evictions yet
485     assertEquals(0, cache.getEvictionCount());
486 
487     // Resize to half capacity plus an extra block (otherwise we evict an extra)
488     cache.setMaxSize((long)(maxSize * 0.5f));
489 
490     // Should have run a single eviction
491     assertEquals(1, cache.getEvictionCount());
492 
493     // And we expect 1/2 of the blocks to be evicted
494     assertEquals(15, cache.getEvictedCount());
495 
496     // And the oldest 5 blocks from each category should be gone
497     for(int i=0;i<5;i++) {
498       assertEquals(null, cache.getBlock(singleBlocks[i].cacheKey, true, false));
499       assertEquals(null, cache.getBlock(multiBlocks[i].cacheKey, true, false));
500       assertEquals(null, cache.getBlock(memoryBlocks[i].cacheKey, true, false));
501     }
502 
503     // And the newest 5 blocks should still be accessible
504     for(int i=5;i<10;i++) {
505       assertEquals(singleBlocks[i], cache.getBlock(singleBlocks[i].cacheKey, true, false));
506       assertEquals(multiBlocks[i], cache.getBlock(multiBlocks[i].cacheKey, true, false));
507       assertEquals(memoryBlocks[i], cache.getBlock(memoryBlocks[i].cacheKey, true, false));
508     }
509   }
510 
511   // test metricsPastNPeriods
512   @Test
513   public void testPastNPeriodsMetrics() throws Exception {
514    double delta = 0.01;
515 
516     // 3 total periods
517     CacheStats stats = new CacheStats(3);
518 
519     // No accesses, should be 0
520     stats.rollMetricsPeriod();
521     assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta);
522     assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
523 
524     // period 1, 1 hit caching, 1 hit non-caching, 2 miss non-caching
525     // should be (2/4)=0.5 and (1/1)=1
526     stats.hit(false);
527     stats.hit(true);
528     stats.miss(false);
529     stats.miss(false);
530     stats.rollMetricsPeriod();
531     assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
532     assertEquals(1.0, stats.getHitCachingRatioPastNPeriods(), delta);
533 
534     // period 2, 1 miss caching, 3 miss non-caching
535     // should be (2/8)=0.25 and (1/2)=0.5
536     stats.miss(true);
537     stats.miss(false);
538     stats.miss(false);
539     stats.miss(false);
540     stats.rollMetricsPeriod();
541     assertEquals(0.25, stats.getHitRatioPastNPeriods(), delta);
542     assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta);
543 
544     // period 3, 2 hits of each type
545     // should be (6/12)=0.5 and (3/4)=0.75
546     stats.hit(false);
547     stats.hit(true);
548     stats.hit(false);
549     stats.hit(true);
550     stats.rollMetricsPeriod();
551     assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
552     assertEquals(0.75, stats.getHitCachingRatioPastNPeriods(), delta);
553 
554     // period 4, evict period 1, two caching misses
555     // should be (4/10)=0.4 and (2/5)=0.4
556     stats.miss(true);
557     stats.miss(true);
558     stats.rollMetricsPeriod();
559     assertEquals(0.4, stats.getHitRatioPastNPeriods(), delta);
560     assertEquals(0.4, stats.getHitCachingRatioPastNPeriods(), delta);
561 
562     // period 5, evict period 2, 2 caching misses, 2 non-caching hit
563     // should be (6/10)=0.6 and (2/6)=1/3
564     stats.miss(true);
565     stats.miss(true);
566     stats.hit(false);
567     stats.hit(false);
568     stats.rollMetricsPeriod();
569     assertEquals(0.6, stats.getHitRatioPastNPeriods(), delta);
570     assertEquals((double)1/3, stats.getHitCachingRatioPastNPeriods(), delta);
571 
572     // period 6, evict period 3
573     // should be (2/6)=1/3 and (0/4)=0
574     stats.rollMetricsPeriod();
575     assertEquals((double)1/3, stats.getHitRatioPastNPeriods(), delta);
576     assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
577 
578     // period 7, evict period 4
579     // should be (2/4)=0.5 and (0/2)=0
580     stats.rollMetricsPeriod();
581     assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
582     assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
583 
584     // period 8, evict period 5
585     // should be 0 and 0
586     stats.rollMetricsPeriod();
587     assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta);
588     assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
589 
590     // period 9, one of each
591     // should be (2/4)=0.5 and (1/2)=0.5
592     stats.miss(true);
593     stats.miss(false);
594     stats.hit(true);
595     stats.hit(false);
596     stats.rollMetricsPeriod();
597     assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
598     assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta);
599   }
600 
601   private CachedItem [] generateFixedBlocks(int numBlocks, int size, String pfx) {
602     CachedItem [] blocks = new CachedItem[numBlocks];
603     for(int i=0;i<numBlocks;i++) {
604       blocks[i] = new CachedItem(pfx + i, size);
605     }
606     return blocks;
607   }
608 
609   private CachedItem [] generateFixedBlocks(int numBlocks, long size, String pfx) {
610     return generateFixedBlocks(numBlocks, (int)size, pfx);
611   }
612 
613   private CachedItem [] generateRandomBlocks(int numBlocks, long maxSize) {
614     CachedItem [] blocks = new CachedItem[numBlocks];
615     Random r = new Random();
616     for(int i=0;i<numBlocks;i++) {
617       blocks[i] = new CachedItem("block" + i, r.nextInt((int)maxSize)+1);
618     }
619     return blocks;
620   }
621 
622   private long calculateBlockSize(long maxSize, int numBlocks) {
623     long roughBlockSize = maxSize / numBlocks;
624     int numEntries = (int)Math.ceil((1.2)*maxSize/roughBlockSize);
625     long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD +
626         ClassSize.CONCURRENT_HASHMAP +
627         (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
628         (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
629     long negateBlockSize = (long)(totalOverhead/numEntries);
630     negateBlockSize += CachedBlock.PER_BLOCK_OVERHEAD;
631     return ClassSize.align((long)Math.floor((roughBlockSize - negateBlockSize)*0.99f));
632   }
633 
634   private long calculateBlockSizeDefault(long maxSize, int numBlocks) {
635     long roughBlockSize = maxSize / numBlocks;
636     int numEntries = (int)Math.ceil((1.2)*maxSize/roughBlockSize);
637     long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD +
638         ClassSize.CONCURRENT_HASHMAP +
639         (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
640         (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
641     long negateBlockSize = totalOverhead / numEntries;
642     negateBlockSize += CachedBlock.PER_BLOCK_OVERHEAD;
643     return ClassSize.align((long)Math.floor((roughBlockSize - negateBlockSize)*
644         LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
645   }
646 
647   private static class CachedItem implements Cacheable {
648     BlockCacheKey cacheKey;
649     int size;
650 
651     CachedItem(String blockName, int size) {
652       this.cacheKey = new BlockCacheKey(blockName, 0);
653       this.size = size;
654     }
655 
656     /** The size of this item reported to the block cache layer */
657     @Override
658     public long heapSize() {
659       return ClassSize.align(size);
660     }
661 
662     /** Size of the cache block holding this item. Used for verification. */
663     public long cacheBlockHeapSize() {
664       return CachedBlock.PER_BLOCK_OVERHEAD
665           + ClassSize.align(cacheKey.heapSize())
666           + ClassSize.align(size);
667     }
668 
669     @Override
670     public BlockType getBlockType() {
671       return BlockType.DATA;
672     }
673 
674     @Override
675     public SchemaMetrics getSchemaMetrics() {
676       return SchemaMetrics.getUnknownInstanceForTest();
677     }
678 
679     @Override
680     public int getSerializedLength() {
681       return 0;
682     }
683 
684     @Override
685     public CacheableDeserializer<Cacheable> getDeserializer() {
686       return null;
687     }
688 
689     @Override
690     public void serialize(ByteBuffer destination) {
691     }
692 
693   }
694 
695   @org.junit.Rule
696   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
697     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
698 }
699