001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024
025import java.lang.management.ManagementFactory;
026import java.nio.ByteBuffer;
027import java.util.Iterator;
028import java.util.NavigableMap;
029import java.util.NavigableSet;
030import java.util.SortedSet;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.Cell;
033import org.apache.hadoop.hbase.CellComparator;
034import org.apache.hadoop.hbase.CellUtil;
035import org.apache.hadoop.hbase.HBaseClassTestRule;
036import org.apache.hadoop.hbase.KeyValue;
037import org.apache.hadoop.hbase.KeyValueUtil;
038import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
039import org.apache.hadoop.hbase.testclassification.RegionServerTests;
040import org.apache.hadoop.hbase.testclassification.SmallTests;
041import org.apache.hadoop.hbase.util.ByteBufferUtils;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.apache.hadoop.hbase.util.ClassSize;
044import org.junit.Before;
045import org.junit.ClassRule;
046import org.junit.Test;
047import org.junit.experimental.categories.Category;
048import org.junit.runner.RunWith;
049import org.junit.runners.Parameterized;
050
051@Category({RegionServerTests.class, SmallTests.class})
052@RunWith(Parameterized.class)
053public class TestCellFlatSet {
054
055  @ClassRule
056  public static final HBaseClassTestRule CLASS_RULE =
057      HBaseClassTestRule.forClass(TestCellFlatSet.class);
058
059  @Parameterized.Parameters
060  public static Object[] data() {
061    return new Object[] { "SMALL_CHUNKS", "NORMAL_CHUNKS" }; // test with different chunk sizes
062  }
063  private static final int NUM_OF_CELLS = 4;
064  private static final int SMALL_CHUNK_SIZE = 64;
065  private Cell ascCells[];
066  private CellArrayMap ascCbOnHeap;
067  private Cell descCells[];
068  private CellArrayMap descCbOnHeap;
069  private final static Configuration CONF = new Configuration();
070  private KeyValue lowerOuterCell;
071  private KeyValue upperOuterCell;
072
073
074  private CellChunkMap ascCCM;   // for testing ascending CellChunkMap with one chunk in array
075  private CellChunkMap descCCM;  // for testing descending CellChunkMap with one chunk in array
076  private final boolean smallChunks;
077  private static ChunkCreator chunkCreator;
078
079
080  public TestCellFlatSet(String chunkType){
081    long globalMemStoreLimit = (long) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()
082        .getMax() * MemorySizeUtil.getGlobalMemStoreHeapPercent(CONF, false));
083    if (chunkType.equals("NORMAL_CHUNKS")) {
084      chunkCreator = ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false,
085        globalMemStoreLimit, 0.2f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT,
086        null, MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
087      assertNotNull(chunkCreator);
088      smallChunks = false;
089    } else {
090      // chunkCreator with smaller chunk size, so only 3 cell-representations can accommodate a chunk
091      chunkCreator = ChunkCreator.initialize(SMALL_CHUNK_SIZE, false,
092        globalMemStoreLimit, 0.2f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT,
093        null, MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
094      assertNotNull(chunkCreator);
095      smallChunks = true;
096    }
097  }
098
099  @Before
100  public void setUp() throws Exception {
101    // create array of Cells to bass to the CellFlatMap under CellSet
102    final byte[] one = Bytes.toBytes(15);
103    final byte[] two = Bytes.toBytes(25);
104    final byte[] three = Bytes.toBytes(35);
105    final byte[] four = Bytes.toBytes(45);
106
107    final byte[] f = Bytes.toBytes("f");
108    final byte[] q = Bytes.toBytes("q");
109    final byte[] v = Bytes.toBytes(4);
110
111    final KeyValue kv1 = new KeyValue(one, f, q, 10, v);
112    final KeyValue kv2 = new KeyValue(two, f, q, 20, v);
113    final KeyValue kv3 = new KeyValue(three, f, q, 30, v);
114    final KeyValue kv4 = new KeyValue(four, f, q, 40, v);
115    lowerOuterCell = new KeyValue(Bytes.toBytes(10), f, q, 10, v);
116    upperOuterCell = new KeyValue(Bytes.toBytes(50), f, q, 10, v);
117    ascCells = new Cell[] {kv1,kv2,kv3,kv4};
118    ascCbOnHeap = new CellArrayMap(CellComparator.getInstance(), ascCells,0, NUM_OF_CELLS,false);
119    descCells = new Cell[] {kv4,kv3,kv2,kv1};
120    descCbOnHeap = new CellArrayMap(CellComparator.getInstance(), descCells,0, NUM_OF_CELLS,true);
121
122    CONF.setBoolean(MemStoreLAB.USEMSLAB_KEY, true);
123    CONF.setFloat(MemStoreLAB.CHUNK_POOL_MAXSIZE_KEY, 0.2f);
124    ChunkCreator.chunkPoolDisabled = false;
125
126    // create ascending and descending CellChunkMaps
127    // according to parameter, once built with normal chunks and at second with small chunks
128    ascCCM = setUpCellChunkMap(true);
129    descCCM = setUpCellChunkMap(false);
130
131    if (smallChunks) {    // check jumbo chunks as well
132      ascCCM = setUpJumboCellChunkMap(true);
133    }
134  }
135
136  /* Create and test ascending CellSet based on CellArrayMap */
137  @Test
138  public void testCellArrayMapAsc() throws Exception {
139    CellSet cs = new CellSet(ascCbOnHeap);
140    testCellBlocks(cs);
141    testIterators(cs);
142  }
143
144  /* Create and test ascending and descending CellSet based on CellChunkMap */
145  @Test
146  public void testCellChunkMap() throws Exception {
147    CellSet cs = new CellSet(ascCCM);
148    testCellBlocks(cs);
149    testIterators(cs);
150    testSubSet(cs);
151    cs = new CellSet(descCCM);
152    testSubSet(cs);
153//    cs = new CellSet(ascMultCCM);
154//    testCellBlocks(cs);
155//    testSubSet(cs);
156//    cs = new CellSet(descMultCCM);
157//    testSubSet(cs);
158  }
159
160  @Test
161  public void testAsc() throws Exception {
162    CellSet ascCs = new CellSet(ascCbOnHeap);
163    assertEquals(NUM_OF_CELLS, ascCs.size());
164    testSubSet(ascCs);
165  }
166  @Test
167  public void testDesc() throws Exception {
168    CellSet descCs = new CellSet(descCbOnHeap);
169    assertEquals(NUM_OF_CELLS, descCs.size());
170    testSubSet(descCs);
171  }
172
173  private void testSubSet(CellSet cs) throws Exception {
174    for (int i = 0; i != ascCells.length; ++i) {
175      NavigableSet<Cell> excludeTail = cs.tailSet(ascCells[i], false);
176      NavigableSet<Cell> includeTail = cs.tailSet(ascCells[i], true);
177      assertEquals(ascCells.length - 1 - i, excludeTail.size());
178      assertEquals(ascCells.length - i, includeTail.size());
179      Iterator<Cell> excludeIter = excludeTail.iterator();
180      Iterator<Cell> includeIter = includeTail.iterator();
181      for (int j = 1 + i; j != ascCells.length; ++j) {
182        assertEquals(true, CellUtil.equals(excludeIter.next(), ascCells[j]));
183      }
184      for (int j = i; j != ascCells.length; ++j) {
185        assertEquals(true, CellUtil.equals(includeIter.next(), ascCells[j]));
186      }
187    }
188    assertEquals(NUM_OF_CELLS, cs.tailSet(lowerOuterCell, false).size());
189    assertEquals(0, cs.tailSet(upperOuterCell, false).size());
190    for (int i = 0; i != ascCells.length; ++i) {
191      NavigableSet<Cell> excludeHead = cs.headSet(ascCells[i], false);
192      NavigableSet<Cell> includeHead = cs.headSet(ascCells[i], true);
193      assertEquals(i, excludeHead.size());
194      assertEquals(i + 1, includeHead.size());
195      Iterator<Cell> excludeIter = excludeHead.iterator();
196      Iterator<Cell> includeIter = includeHead.iterator();
197      for (int j = 0; j != i; ++j) {
198        assertEquals(true, CellUtil.equals(excludeIter.next(), ascCells[j]));
199      }
200      for (int j = 0; j != i + 1; ++j) {
201        assertEquals(true, CellUtil.equals(includeIter.next(), ascCells[j]));
202      }
203    }
204    assertEquals(0, cs.headSet(lowerOuterCell, false).size());
205    assertEquals(NUM_OF_CELLS, cs.headSet(upperOuterCell, false).size());
206
207    NavigableMap<Cell, Cell> sub = cs.getDelegatee().subMap(lowerOuterCell, true, upperOuterCell, true);
208    assertEquals(NUM_OF_CELLS, sub.size());
209    Iterator<Cell> iter = sub.values().iterator();
210    for (int i = 0; i != ascCells.length; ++i) {
211      assertEquals(true, CellUtil.equals(iter.next(), ascCells[i]));
212    }
213  }
214
215  /* Generic basic test for immutable CellSet */
216  private void testCellBlocks(CellSet cs) throws Exception {
217    final byte[] oneAndHalf = Bytes.toBytes(20);
218    final byte[] f = Bytes.toBytes("f");
219    final byte[] q = Bytes.toBytes("q");
220    final byte[] v = Bytes.toBytes(4);
221    final KeyValue outerCell = new KeyValue(oneAndHalf, f, q, 10, v);
222
223    assertEquals(NUM_OF_CELLS, cs.size());          // check size
224    assertFalse(cs.contains(outerCell));            // check outer cell
225
226    assertTrue(cs.contains(ascCells[0]));           // check existence of the first
227    Cell first = cs.first();
228    assertTrue(ascCells[0].equals(first));
229
230    assertTrue(cs.contains(ascCells[NUM_OF_CELLS - 1]));  // check last
231    Cell last = cs.last();
232    assertTrue(ascCells[NUM_OF_CELLS - 1].equals(last));
233
234    SortedSet<Cell> tail = cs.tailSet(ascCells[1]);    // check tail abd head sizes
235    assertEquals(NUM_OF_CELLS - 1, tail.size());
236    SortedSet<Cell> head = cs.headSet(ascCells[1]);
237    assertEquals(1, head.size());
238
239    SortedSet<Cell> tailOuter = cs.tailSet(outerCell);  // check tail starting from outer cell
240    assertEquals(NUM_OF_CELLS - 1, tailOuter.size());
241
242    Cell tailFirst = tail.first();
243    assertTrue(ascCells[1].equals(tailFirst));
244    Cell tailLast = tail.last();
245    assertTrue(ascCells[NUM_OF_CELLS - 1].equals(tailLast));
246
247    Cell headFirst = head.first();
248    assertTrue(ascCells[0].equals(headFirst));
249    Cell headLast = head.last();
250    assertTrue(ascCells[0].equals(headLast));
251  }
252
253  /* Generic iterators test for immutable CellSet */
254  private void testIterators(CellSet cs) throws Exception {
255
256    // Assert that we have NUM_OF_CELLS values and that they are in order
257    int count = 0;
258    for (Cell kv: cs) {
259      assertEquals("\n\n-------------------------------------------------------------------\n"
260              + "Comparing iteration number " + (count + 1) + " the returned cell: " + kv
261              + ", the first Cell in the CellBlocksMap: " + ascCells[count]
262              + ", and the same transformed to String: " + ascCells[count].toString()
263              + "\n-------------------------------------------------------------------\n",
264              ascCells[count], kv);
265      count++;
266    }
267    assertEquals(NUM_OF_CELLS, count);
268
269    // Test descending iterator
270    count = 0;
271    for (Iterator<Cell> i = cs.descendingIterator(); i.hasNext();) {
272      Cell kv = i.next();
273      assertEquals(ascCells[NUM_OF_CELLS - (count + 1)], kv);
274      count++;
275    }
276    assertEquals(NUM_OF_CELLS, count);
277  }
278
279  /* Create CellChunkMap with four cells inside the index chunk */
280  private CellChunkMap setUpCellChunkMap(boolean asc) {
281
282    // allocate new chunks and use the data chunk to hold the full data of the cells
283    // and the index chunk to hold the cell-representations
284    Chunk dataChunk = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
285    Chunk idxChunk  = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
286    // the array of index chunks to be used as a basis for CellChunkMap
287    Chunk chunkArray[] = new Chunk[8];  // according to test currently written 8 is way enough
288    int chunkArrayIdx = 0;
289    chunkArray[chunkArrayIdx++] = idxChunk;
290
291    ByteBuffer idxBuffer = idxChunk.getData();  // the buffers of the chunks
292    ByteBuffer dataBuffer = dataChunk.getData();
293    int dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;        // offset inside data buffer
294    int idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;         // skip the space for chunk ID
295
296    Cell[] cellArray = asc ? ascCells : descCells;
297
298    for (Cell kv: cellArray) {
299      // do we have enough space to write the cell data on the data chunk?
300      if (dataOffset + kv.getSerializedSize() > chunkCreator.getChunkSize()) {
301        // allocate more data chunks if needed
302        dataChunk = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
303        dataBuffer = dataChunk.getData();
304        dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;
305      }
306      int dataStartOfset = dataOffset;
307      dataOffset = KeyValueUtil.appendTo(kv, dataBuffer, dataOffset, false); // write deep cell data
308
309      // do we have enough space to write the cell-representation on the index chunk?
310      if (idxOffset + ClassSize.CELL_CHUNK_MAP_ENTRY > chunkCreator.getChunkSize()) {
311        // allocate more index chunks if needed
312        idxChunk = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
313        idxBuffer = idxChunk.getData();
314        idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;
315        chunkArray[chunkArrayIdx++] = idxChunk;
316      }
317      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataChunk.getId()); // write data chunk id
318      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataStartOfset);          // offset
319      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, kv.getSerializedSize()); // length
320      idxOffset = ByteBufferUtils.putLong(idxBuffer, idxOffset, kv.getSequenceId());     // seqId
321    }
322
323    return new CellChunkMap(CellComparator.getInstance(),chunkArray,0,NUM_OF_CELLS,!asc);
324  }
325
326  /* Create CellChunkMap with four cells inside the data jumbo chunk. This test is working only
327  ** with small chunks sized SMALL_CHUNK_SIZE (64) bytes */
328  private CellChunkMap setUpJumboCellChunkMap(boolean asc) {
329    int smallChunkSize = SMALL_CHUNK_SIZE+8;
330    // allocate new chunks and use the data JUMBO chunk to hold the full data of the cells
331    // and the normal index chunk to hold the cell-representations
332    Chunk dataJumboChunk =
333        chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP, smallChunkSize);
334    Chunk idxChunk  = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
335    // the array of index chunks to be used as a basis for CellChunkMap
336    Chunk[] chunkArray = new Chunk[8];  // according to test currently written 8 is way enough
337    int chunkArrayIdx = 0;
338    chunkArray[chunkArrayIdx++] = idxChunk;
339
340    ByteBuffer idxBuffer = idxChunk.getData();  // the buffers of the chunks
341    ByteBuffer dataBuffer = dataJumboChunk.getData();
342    int dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;          // offset inside data buffer
343    int idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;           // skip the space for chunk ID
344
345    Cell[] cellArray = asc ? ascCells : descCells;
346
347    for (Cell kv: cellArray) {
348      int dataStartOfset = dataOffset;
349      dataOffset = KeyValueUtil.appendTo(kv, dataBuffer, dataOffset, false); // write deep cell data
350
351      // do we have enough space to write the cell-representation on the index chunk?
352      if (idxOffset + ClassSize.CELL_CHUNK_MAP_ENTRY > chunkCreator.getChunkSize()) {
353        // allocate more index chunks if needed
354        idxChunk = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP);
355        idxBuffer = idxChunk.getData();
356        idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;
357        chunkArray[chunkArrayIdx++] = idxChunk;
358      }
359      // write data chunk id
360      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataJumboChunk.getId());
361      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataStartOfset);          // offset
362      idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, kv.getSerializedSize()); // length
363      idxOffset = ByteBufferUtils.putLong(idxBuffer, idxOffset, kv.getSequenceId());     // seqId
364
365      // Jumbo chunks are working only with one cell per chunk, thus always allocate a new jumbo
366      // data chunk for next cell
367      dataJumboChunk = chunkCreator.getChunk(CompactingMemStore.IndexType.CHUNK_MAP,smallChunkSize);
368      dataBuffer = dataJumboChunk.getData();
369      dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER;
370    }
371
372    return new CellChunkMap(CellComparator.getInstance(),chunkArray,0,NUM_OF_CELLS,!asc);
373  }
374}