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