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  
20  package org.apache.hadoop.hbase.io;
21  
22  import java.io.IOException;
23  import java.lang.management.ManagementFactory;
24  import java.lang.management.RuntimeMXBean;
25  import java.nio.ByteBuffer;
26  import java.util.ArrayList;
27  import java.util.Map;
28  import java.util.TreeMap;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.ConcurrentSkipListMap;
31  import java.util.concurrent.CopyOnWriteArrayList;
32  import java.util.concurrent.CopyOnWriteArraySet;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  import java.util.concurrent.atomic.AtomicInteger;
35  import java.util.concurrent.atomic.AtomicLong;
36  import java.util.concurrent.locks.ReentrantReadWriteLock;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.hbase.KeyValue;
41  import org.apache.hadoop.hbase.testclassification.SmallTests;
42  import org.apache.hadoop.hbase.client.Delete;
43  import org.apache.hadoop.hbase.client.Put;
44  import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
45  import org.apache.hadoop.hbase.io.hfile.LruCachedBlock;
46  import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
47  import org.apache.hadoop.hbase.regionserver.CellSkipListSet;
48  import org.apache.hadoop.hbase.regionserver.DefaultMemStore;
49  import org.apache.hadoop.hbase.regionserver.HRegion;
50  import org.apache.hadoop.hbase.regionserver.HStore;
51  import org.apache.hadoop.hbase.regionserver.TimeRangeTracker;
52  import org.apache.hadoop.hbase.util.ClassSize;
53  import org.junit.BeforeClass;
54  import org.junit.Test;
55  import org.junit.experimental.categories.Category;
56  
57  import static org.junit.Assert.assertEquals;
58  import static org.junit.Assert.assertTrue;
59  
60  /**
61   * Testing the sizing that HeapSize offers and compares to the size given by
62   * ClassSize.
63   */
64  @Category(SmallTests.class)
65  public class TestHeapSize  {
66    private static final Log LOG = LogFactory.getLog(TestHeapSize.class);
67    // List of classes implementing HeapSize
68    // BatchOperation, BatchUpdate, BlockIndex, Entry, Entry<K,V>, HStoreKey
69    // KeyValue, LruBlockCache, LruHashMap<K,V>, Put, WALKey
70  
71    @BeforeClass
72    public static void beforeClass() throws Exception {
73      // Print detail on jvm so we know what is different should below test fail.
74      RuntimeMXBean b = ManagementFactory.getRuntimeMXBean();
75      LOG.info("name=" + b.getName());
76      LOG.info("specname=" + b.getSpecName());
77      LOG.info("specvendor=" + b.getSpecVendor());
78      LOG.info("vmname=" + b.getVmName());
79      LOG.info("vmversion=" + b.getVmVersion());
80      LOG.info("vmvendor=" + b.getVmVendor());
81      Map<String, String> p = b.getSystemProperties();
82      LOG.info("properties=" + p);
83    }
84  
85    /**
86     * Test our hard-coded sizing of native java objects
87     */
88    @Test
89    public void testNativeSizes() throws IOException {
90      Class<?> cl;
91      long expected;
92      long actual;
93  
94      // ArrayList
95      cl = ArrayList.class;
96      expected = ClassSize.estimateBase(cl, false);
97      actual = ClassSize.ARRAYLIST;
98      if(expected != actual) {
99        ClassSize.estimateBase(cl, true);
100       assertEquals(expected, actual);
101     }
102 
103     // ByteBuffer
104     cl = ByteBuffer.class;
105     expected = ClassSize.estimateBase(cl, false);
106     actual = ClassSize.BYTE_BUFFER;
107     if(expected != actual) {
108       ClassSize.estimateBase(cl, true);
109       assertEquals(expected, actual);
110     }
111 
112     // Integer
113     cl = Integer.class;
114     expected = ClassSize.estimateBase(cl, false);
115     actual = ClassSize.INTEGER;
116     if(expected != actual) {
117       ClassSize.estimateBase(cl, true);
118       assertEquals(expected, actual);
119     }
120 
121     // Map.Entry
122     // Interface is public, all others are not.  Hard to size via ClassSize
123 //    cl = Map.Entry.class;
124 //    expected = ClassSize.estimateBase(cl, false);
125 //    actual = ClassSize.MAP_ENTRY;
126 //    if(expected != actual) {
127 //      ClassSize.estimateBase(cl, true);
128 //      assertEquals(expected, actual);
129 //    }
130 
131     // Object
132     cl = Object.class;
133     expected = ClassSize.estimateBase(cl, false);
134     actual = ClassSize.align(ClassSize.OBJECT);
135     if(expected != actual) {
136       ClassSize.estimateBase(cl, true);
137       assertEquals(expected, actual);
138     }
139 
140     // TreeMap
141     cl = TreeMap.class;
142     expected = ClassSize.estimateBase(cl, false);
143     actual = ClassSize.TREEMAP;
144     if(expected != actual) {
145       ClassSize.estimateBase(cl, true);
146       assertEquals(expected, actual);
147     }
148 
149     // String
150     cl = String.class;
151     expected = ClassSize.estimateBase(cl, false);
152     actual = ClassSize.STRING;
153     if(expected != actual) {
154       ClassSize.estimateBase(cl, true);
155       assertEquals(expected, actual);
156     }
157 
158     // ConcurrentHashMap
159     cl = ConcurrentHashMap.class;
160     expected = ClassSize.estimateBase(cl, false);
161     actual = ClassSize.CONCURRENT_HASHMAP;
162     if(expected != actual) {
163       ClassSize.estimateBase(cl, true);
164       assertEquals(expected, actual);
165     }
166 
167     // ConcurrentSkipListMap
168     cl = ConcurrentSkipListMap.class;
169     expected = ClassSize.estimateBase(cl, false);
170     actual = ClassSize.CONCURRENT_SKIPLISTMAP;
171     if(expected != actual) {
172       ClassSize.estimateBase(cl, true);
173       assertEquals(expected, actual);
174     }
175 
176     // ReentrantReadWriteLock
177     cl = ReentrantReadWriteLock.class;
178     expected = ClassSize.estimateBase(cl, false);
179     actual = ClassSize.REENTRANT_LOCK;
180     if(expected != actual) {
181       ClassSize.estimateBase(cl, true);
182       assertEquals(expected, actual);
183     }
184 
185     // AtomicLong
186     cl = AtomicLong.class;
187     expected = ClassSize.estimateBase(cl, false);
188     actual = ClassSize.ATOMIC_LONG;
189     if(expected != actual) {
190       ClassSize.estimateBase(cl, true);
191       assertEquals(expected, actual);
192     }
193 
194     // AtomicInteger
195     cl = AtomicInteger.class;
196     expected = ClassSize.estimateBase(cl, false);
197     actual = ClassSize.ATOMIC_INTEGER;
198     if(expected != actual) {
199       ClassSize.estimateBase(cl, true);
200       assertEquals(expected, actual);
201     }
202 
203     // AtomicBoolean
204     cl = AtomicBoolean.class;
205     expected = ClassSize.estimateBase(cl, false);
206     actual = ClassSize.ATOMIC_BOOLEAN;
207     if(expected != actual) {
208       ClassSize.estimateBase(cl, true);
209       assertEquals(expected, actual);
210     }
211 
212     // CopyOnWriteArraySet
213     cl = CopyOnWriteArraySet.class;
214     expected = ClassSize.estimateBase(cl, false);
215     actual = ClassSize.COPYONWRITE_ARRAYSET;
216     if(expected != actual) {
217       ClassSize.estimateBase(cl, true);
218       assertEquals(expected, actual);
219     }
220 
221     // CopyOnWriteArrayList
222     cl = CopyOnWriteArrayList.class;
223     expected = ClassSize.estimateBase(cl, false);
224     actual = ClassSize.COPYONWRITE_ARRAYLIST;
225     if(expected != actual) {
226       ClassSize.estimateBase(cl, true);
227       assertEquals(expected, actual);
228     }
229 
230     // TimeRangeTracker
231     cl = TimeRangeTracker.class;
232     expected = ClassSize.estimateBase(cl, false);
233     actual = ClassSize.TIMERANGE_TRACKER;
234     if (expected != actual) {
235       ClassSize.estimateBase(cl, true);
236       assertEquals(expected, actual);
237     }
238 
239     // CellSkipListSet
240     cl = CellSkipListSet.class;
241     expected = ClassSize.estimateBase(cl, false);
242     actual = ClassSize.CELL_SKIPLIST_SET;
243     if (expected != actual) {
244       ClassSize.estimateBase(cl, true);
245       assertEquals(expected, actual);
246     }
247   }
248 
249   /**
250    * Testing the classes that implements HeapSize and are a part of 0.20.
251    * Some are not tested here for example BlockIndex which is tested in
252    * TestHFile since it is a non public class
253    * @throws IOException
254    */
255   @Test
256   public void testSizes() throws IOException {
257     Class<?> cl;
258     long expected;
259     long actual;
260 
261     //KeyValue
262     cl = KeyValue.class;
263     expected = ClassSize.estimateBase(cl, false);
264     KeyValue kv = new KeyValue();
265     actual = kv.heapSize();
266     if(expected != actual) {
267       ClassSize.estimateBase(cl, true);
268       assertEquals(expected, actual);
269     }
270 
271     //LruBlockCache Overhead
272     cl = LruBlockCache.class;
273     actual = LruBlockCache.CACHE_FIXED_OVERHEAD;
274     expected = ClassSize.estimateBase(cl, false);
275     if(expected != actual) {
276       ClassSize.estimateBase(cl, true);
277       assertEquals(expected, actual);
278     }
279 
280     // CachedBlock Fixed Overhead
281     // We really need "deep" sizing but ClassSize does not do this.
282     // Perhaps we should do all these more in this style....
283     cl = LruCachedBlock.class;
284     actual = LruCachedBlock.PER_BLOCK_OVERHEAD;
285     expected = ClassSize.estimateBase(cl, false);
286     expected += ClassSize.estimateBase(String.class, false);
287     expected += ClassSize.estimateBase(ByteBuffer.class, false);
288     if(expected != actual) {
289       ClassSize.estimateBase(cl, true);
290       ClassSize.estimateBase(String.class, true);
291       ClassSize.estimateBase(ByteBuffer.class, true);
292       assertEquals(expected, actual);
293     }
294 
295     // DefaultMemStore Overhead
296     cl = DefaultMemStore.class;
297     actual = DefaultMemStore.FIXED_OVERHEAD;
298     expected = ClassSize.estimateBase(cl, false);
299     if(expected != actual) {
300       ClassSize.estimateBase(cl, true);
301       assertEquals(expected, actual);
302     }
303 
304     // DefaultMemStore Deep Overhead
305     actual = DefaultMemStore.DEEP_OVERHEAD;
306     expected = ClassSize.estimateBase(cl, false);
307     expected += (2 * ClassSize.estimateBase(AtomicLong.class, false));
308     expected += ClassSize.estimateBase(AtomicInteger.class, false);
309     expected += (2 * ClassSize.estimateBase(CellSkipListSet.class, false));
310     expected += (2 * ClassSize.estimateBase(ConcurrentSkipListMap.class, false));
311     expected += (2 * ClassSize.estimateBase(TimeRangeTracker.class, false));
312     if (expected != actual) {
313       ClassSize.estimateBase(cl, true);
314       ClassSize.estimateBase(AtomicLong.class, true);
315       ClassSize.estimateBase(AtomicLong.class, true);
316       ClassSize.estimateBase(AtomicInteger.class, true);
317       ClassSize.estimateBase(CellSkipListSet.class, true);
318       ClassSize.estimateBase(CellSkipListSet.class, true);
319       ClassSize.estimateBase(ConcurrentSkipListMap.class, true);
320       ClassSize.estimateBase(ConcurrentSkipListMap.class, true);
321       ClassSize.estimateBase(TimeRangeTracker.class, true);
322       ClassSize.estimateBase(TimeRangeTracker.class, true);
323       assertEquals(expected, actual);
324     }
325 
326     // Store Overhead
327     cl = HStore.class;
328     actual = HStore.FIXED_OVERHEAD;
329     expected = ClassSize.estimateBase(cl, false);
330     if(expected != actual) {
331       ClassSize.estimateBase(cl, true);
332       assertEquals(expected, actual);
333     }
334 
335     // Region Overhead
336     cl = HRegion.class;
337     actual = HRegion.FIXED_OVERHEAD;
338     expected = ClassSize.estimateBase(cl, false);
339     if (expected != actual) {
340       ClassSize.estimateBase(cl, true);
341       assertEquals(expected, actual);
342     }
343 
344     // Block cache key overhead. Only tests fixed overhead as estimating heap
345     // size of strings is hard.
346     cl = BlockCacheKey.class;
347     actual = BlockCacheKey.FIXED_OVERHEAD;
348     expected  = ClassSize.estimateBase(cl, false);
349     if (expected != actual) {
350       ClassSize.estimateBase(cl, true);
351       assertEquals(expected, actual);
352     }
353 
354     // Currently NOT testing Deep Overheads of many of these classes.
355     // Deep overheads cover a vast majority of stuff, but will not be 100%
356     // accurate because it's unclear when we're referencing stuff that's already
357     // accounted for.  But we have satisfied our two core requirements.
358     // Sizing is quite accurate now, and our tests will throw errors if
359     // any of these classes are modified without updating overhead sizes.
360   }
361 
362   @Test
363   public void testMutations(){
364     Class<?> cl;
365     long expected;
366     long actual;
367 
368     cl = TimeRange.class;
369     actual = ClassSize.TIMERANGE;
370     expected  = ClassSize.estimateBase(cl, false);
371     if (expected != actual) {
372       ClassSize.estimateBase(cl, true);
373       assertEquals(expected, actual);
374     }
375 
376     byte[] row = new byte[] { 0 };
377     cl = Put.class;
378     actual = new Put(row).MUTATION_OVERHEAD + ClassSize.align(ClassSize.ARRAY);
379     expected = ClassSize.estimateBase(cl, false);
380     //The actual TreeMap is not included in the above calculation
381     expected += ClassSize.align(ClassSize.TREEMAP);
382     expected += ClassSize.align(ClassSize.INTEGER); // priority
383     if (expected != actual) {
384       ClassSize.estimateBase(cl, true);
385       assertEquals(expected, actual);
386     }
387 
388     cl = Delete.class;
389     actual = new Delete(row).MUTATION_OVERHEAD + ClassSize.align(ClassSize.ARRAY);
390     expected  = ClassSize.estimateBase(cl, false);
391     //The actual TreeMap is not included in the above calculation
392     expected += ClassSize.align(ClassSize.TREEMAP);
393     expected += ClassSize.align(ClassSize.INTEGER); // priority
394     if (expected != actual) {
395       ClassSize.estimateBase(cl, true);
396       assertEquals(expected, actual);
397     }
398   }
399 
400   @Test
401   public void testReferenceSize() {
402     LOG.info("ClassSize.REFERENCE is " + ClassSize.REFERENCE);
403     // oop should be either 4 or 8
404     assertTrue(ClassSize.REFERENCE == 4 || ClassSize.REFERENCE == 8);
405   }
406 
407   @Test
408   public void testObjectSize() throws IOException {
409     LOG.info("header:" + ClassSize.OBJECT);
410     LOG.info("array header:" + ClassSize.ARRAY);
411 
412     if (ClassSize.is32BitJVM()) {
413       assertEquals(ClassSize.OBJECT, 8);
414     } else {
415       assertTrue(ClassSize.OBJECT == 12 || ClassSize.OBJECT == 16); // depending on CompressedOops
416     }
417     assertEquals(ClassSize.OBJECT + 4, ClassSize.ARRAY);
418   }
419 
420 }
421