1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.io.hfile;
20
21 import static org.apache.hadoop.hbase.io.compress.Compression.Algorithm.GZ;
22 import static org.apache.hadoop.hbase.io.compress.Compression.Algorithm.NONE;
23 import static org.junit.Assert.*;
24
25 import java.io.ByteArrayOutputStream;
26 import java.io.DataOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.nio.ByteBuffer;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Random;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.ExecutionException;
39 import java.util.concurrent.Executor;
40 import java.util.concurrent.ExecutorCompletionService;
41 import java.util.concurrent.Executors;
42 import java.util.concurrent.Future;
43
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.apache.hadoop.fs.FSDataInputStream;
47 import org.apache.hadoop.fs.FSDataOutputStream;
48 import org.apache.hadoop.fs.FileSystem;
49 import org.apache.hadoop.fs.Path;
50 import org.apache.hadoop.hbase.HBaseTestingUtility;
51 import org.apache.hadoop.hbase.HConstants;
52 import org.apache.hadoop.hbase.KeyValue;
53 import org.apache.hadoop.hbase.testclassification.MediumTests;
54 import org.apache.hadoop.hbase.Tag;
55 import org.apache.hadoop.hbase.fs.HFileSystem;
56 import org.apache.hadoop.hbase.io.compress.Compression;
57 import org.apache.hadoop.hbase.io.compress.Compression.Algorithm;
58 import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
59 import org.apache.hadoop.hbase.util.Bytes;
60 import org.apache.hadoop.hbase.util.ChecksumType;
61 import org.apache.hadoop.hbase.util.ClassSize;
62 import org.apache.hadoop.io.WritableUtils;
63 import org.apache.hadoop.io.compress.Compressor;
64 import org.junit.Before;
65 import org.junit.Test;
66 import org.junit.experimental.categories.Category;
67 import org.junit.runner.RunWith;
68 import org.junit.runners.Parameterized;
69 import org.junit.runners.Parameterized.Parameters;
70 import org.mockito.Mockito;
71
72 @Category(MediumTests.class)
73 @RunWith(Parameterized.class)
74 public class TestHFileBlock {
75
76 private static final boolean detailedLogging = false;
77 private static final boolean[] BOOLEAN_VALUES = new boolean[] { false, true };
78
79 private static final Log LOG = LogFactory.getLog(TestHFileBlock.class);
80
81 static final Compression.Algorithm[] COMPRESSION_ALGORITHMS = { NONE, GZ };
82
83 private static final int NUM_TEST_BLOCKS = 1000;
84 private static final int NUM_READER_THREADS = 26;
85
86
87 private static int NUM_KEYVALUES = 50;
88 private static int FIELD_LENGTH = 10;
89 private static float CHANCE_TO_REPEAT = 0.6f;
90
91 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
92 private FileSystem fs;
93
94 private final boolean includesMemstoreTS;
95 private final boolean includesTag;
96 public TestHFileBlock(boolean includesMemstoreTS, boolean includesTag) {
97 this.includesMemstoreTS = includesMemstoreTS;
98 this.includesTag = includesTag;
99 }
100
101 @Parameters
102 public static Collection<Object[]> parameters() {
103 return HBaseTestingUtility.MEMSTORETS_TAGS_PARAMETRIZED;
104 }
105
106 @Before
107 public void setUp() throws IOException {
108 fs = HFileSystem.get(TEST_UTIL.getConfiguration());
109 }
110
111 static void writeTestBlockContents(DataOutputStream dos) throws IOException {
112
113 for (int i = 0; i < 1000; ++i)
114 dos.writeInt(i / 100);
115 }
116
117 static int writeTestKeyValues(HFileBlock.Writer hbw, int seed, boolean includesMemstoreTS,
118 boolean useTag) throws IOException {
119 List<KeyValue> keyValues = new ArrayList<KeyValue>();
120 Random randomizer = new Random(42l + seed);
121
122
123 for (int i = 0; i < NUM_KEYVALUES; ++i) {
124 byte[] row;
125 long timestamp;
126 byte[] family;
127 byte[] qualifier;
128 byte[] value;
129
130
131 if (0 < i && randomizer.nextFloat() < CHANCE_TO_REPEAT) {
132 row = keyValues.get(randomizer.nextInt(keyValues.size())).getRow();
133 } else {
134 row = new byte[FIELD_LENGTH];
135 randomizer.nextBytes(row);
136 }
137 if (0 == i) {
138 family = new byte[FIELD_LENGTH];
139 randomizer.nextBytes(family);
140 } else {
141 family = keyValues.get(0).getFamily();
142 }
143 if (0 < i && randomizer.nextFloat() < CHANCE_TO_REPEAT) {
144 qualifier = keyValues.get(
145 randomizer.nextInt(keyValues.size())).getQualifier();
146 } else {
147 qualifier = new byte[FIELD_LENGTH];
148 randomizer.nextBytes(qualifier);
149 }
150 if (0 < i && randomizer.nextFloat() < CHANCE_TO_REPEAT) {
151 value = keyValues.get(randomizer.nextInt(keyValues.size())).getValue();
152 } else {
153 value = new byte[FIELD_LENGTH];
154 randomizer.nextBytes(value);
155 }
156 if (0 < i && randomizer.nextFloat() < CHANCE_TO_REPEAT) {
157 timestamp = keyValues.get(
158 randomizer.nextInt(keyValues.size())).getTimestamp();
159 } else {
160 timestamp = randomizer.nextLong();
161 }
162 if (!useTag) {
163 keyValues.add(new KeyValue(row, family, qualifier, timestamp, value));
164 } else {
165 keyValues.add(new KeyValue(row, family, qualifier, timestamp, value, new Tag[] { new Tag(
166 (byte) 1, Bytes.toBytes("myTagVal")) }));
167 }
168 }
169
170
171 int totalSize = 0;
172 Collections.sort(keyValues, KeyValue.COMPARATOR);
173
174 for (KeyValue kv : keyValues) {
175 totalSize += kv.getLength();
176 if (includesMemstoreTS) {
177 long memstoreTS = randomizer.nextLong();
178 kv.setSequenceId(memstoreTS);
179 totalSize += WritableUtils.getVIntSize(memstoreTS);
180 }
181 hbw.write(kv);
182 }
183 return totalSize;
184 }
185
186 public byte[] createTestV1Block(Compression.Algorithm algo)
187 throws IOException {
188 Compressor compressor = algo.getCompressor();
189 ByteArrayOutputStream baos = new ByteArrayOutputStream();
190 OutputStream os = algo.createCompressionStream(baos, compressor, 0);
191 DataOutputStream dos = new DataOutputStream(os);
192 BlockType.META.write(dos);
193 writeTestBlockContents(dos);
194 dos.flush();
195 algo.returnCompressor(compressor);
196 return baos.toByteArray();
197 }
198
199 static HFileBlock.Writer createTestV2Block(Compression.Algorithm algo,
200 boolean includesMemstoreTS, boolean includesTag) throws IOException {
201 final BlockType blockType = BlockType.DATA;
202 HFileContext meta = new HFileContextBuilder()
203 .withCompression(algo)
204 .withIncludesMvcc(includesMemstoreTS)
205 .withIncludesTags(includesTag)
206 .withBytesPerCheckSum(HFile.DEFAULT_BYTES_PER_CHECKSUM)
207 .build();
208 HFileBlock.Writer hbw = new HFileBlock.Writer(null, meta);
209 DataOutputStream dos = hbw.startWriting(blockType);
210 writeTestBlockContents(dos);
211 dos.flush();
212 hbw.ensureBlockReady();
213 assertEquals(1000 * 4, hbw.getUncompressedSizeWithoutHeader());
214 hbw.release();
215 return hbw;
216 }
217
218 public String createTestBlockStr(Compression.Algorithm algo,
219 int correctLength, boolean useTag) throws IOException {
220 HFileBlock.Writer hbw = createTestV2Block(algo, includesMemstoreTS, useTag);
221 byte[] testV2Block = hbw.getHeaderAndDataForTest();
222 int osOffset = HConstants.HFILEBLOCK_HEADER_SIZE + 9;
223 if (testV2Block.length == correctLength) {
224
225
226
227
228
229 testV2Block[osOffset] = 3;
230 }
231 return Bytes.toStringBinary(testV2Block);
232 }
233
234 @Test
235 public void testNoCompression() throws IOException {
236 CacheConfig cacheConf = Mockito.mock(CacheConfig.class);
237 Mockito.when(cacheConf.isBlockCacheEnabled()).thenReturn(false);
238
239 HFileBlock block =
240 createTestV2Block(NONE, includesMemstoreTS, false).getBlockForCaching(cacheConf);
241 assertEquals(4000, block.getUncompressedSizeWithoutHeader());
242 assertEquals(4004, block.getOnDiskSizeWithoutHeader());
243 assertTrue(block.isUnpacked());
244 }
245
246 @Test
247 public void testGzipCompression() throws IOException {
248 final String correctTestBlockStr =
249 "DATABLK*\\x00\\x00\\x00>\\x00\\x00\\x0F\\xA0\\xFF\\xFF\\xFF\\xFF"
250 + "\\xFF\\xFF\\xFF\\xFF"
251 + "\\x0" + ChecksumType.getDefaultChecksumType().getCode()
252 + "\\x00\\x00@\\x00\\x00\\x00\\x00["
253
254 + "\\x1F\\x8B"
255 + "\\x08"
256 + "\\x00"
257 + "\\x00\\x00\\x00\\x00"
258 + "\\x00"
259
260
261
262
263 + "\\x03"
264 + "\\xED\\xC3\\xC1\\x11\\x00 \\x08\\xC00DD\\xDD\\x7Fa"
265 + "\\xD6\\xE8\\xA3\\xB9K\\x84`\\x96Q\\xD3\\xA8\\xDB\\xA8e\\xD4c"
266 + "\\xD46\\xEA5\\xEA3\\xEA7\\xE7\\x00LI\\x5Cs\\xA0\\x0F\\x00\\x00"
267 + "\\x00\\x00\\x00\\x00";
268 final int correctGzipBlockLength = 95;
269 final String testBlockStr = createTestBlockStr(GZ, correctGzipBlockLength, false);
270
271
272 assertEquals(correctTestBlockStr.substring(0, correctGzipBlockLength - 4),
273 testBlockStr.substring(0, correctGzipBlockLength - 4));
274 }
275
276 @Test
277 public void testReaderV2() throws IOException {
278 testReaderV2Internals();
279 }
280
281 protected void testReaderV2Internals() throws IOException {
282 if(includesTag) {
283 TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3);
284 }
285 for (Compression.Algorithm algo : COMPRESSION_ALGORITHMS) {
286 for (boolean pread : new boolean[] { false, true }) {
287 LOG.info("testReaderV2: Compression algorithm: " + algo +
288 ", pread=" + pread);
289 Path path = new Path(TEST_UTIL.getDataTestDir(), "blocks_v2_"
290 + algo);
291 FSDataOutputStream os = fs.create(path);
292 HFileContext meta = new HFileContextBuilder()
293 .withCompression(algo)
294 .withIncludesMvcc(includesMemstoreTS)
295 .withIncludesTags(includesTag)
296 .withBytesPerCheckSum(HFile.DEFAULT_BYTES_PER_CHECKSUM)
297 .build();
298 HFileBlock.Writer hbw = new HFileBlock.Writer(null,
299 meta);
300 long totalSize = 0;
301 for (int blockId = 0; blockId < 2; ++blockId) {
302 DataOutputStream dos = hbw.startWriting(BlockType.DATA);
303 for (int i = 0; i < 1234; ++i)
304 dos.writeInt(i);
305 hbw.writeHeaderAndData(os);
306 totalSize += hbw.getOnDiskSizeWithHeader();
307 }
308 os.close();
309
310 FSDataInputStream is = fs.open(path);
311 meta = new HFileContextBuilder()
312 .withHBaseCheckSum(true)
313 .withIncludesMvcc(includesMemstoreTS)
314 .withIncludesTags(includesTag)
315 .withCompression(algo).build();
316 HFileBlock.FSReader hbr = new HFileBlock.FSReaderImpl(is, totalSize, meta);
317 HFileBlock b = hbr.readBlockData(0, -1, pread, false);
318 is.close();
319 assertEquals(0, HFile.getAndResetChecksumFailuresCount());
320
321 b.sanityCheck();
322 assertEquals(4936, b.getUncompressedSizeWithoutHeader());
323 assertEquals(algo == GZ ? 2173 : 4936,
324 b.getOnDiskSizeWithoutHeader() - b.totalChecksumBytes());
325 HFileBlock expected = b;
326
327 if (algo == GZ) {
328 is = fs.open(path);
329 hbr = new HFileBlock.FSReaderImpl(is, totalSize, meta);
330 b = hbr.readBlockData(0, 2173 + HConstants.HFILEBLOCK_HEADER_SIZE +
331 b.totalChecksumBytes(), pread, false);
332 assertEquals(expected, b);
333 int wrongCompressedSize = 2172;
334 try {
335 b = hbr.readBlockData(0, wrongCompressedSize
336 + HConstants.HFILEBLOCK_HEADER_SIZE, pread, false);
337 fail("Exception expected");
338 } catch (IOException ex) {
339 String expectedPrefix = "Passed in onDiskSizeWithHeader=";
340 assertTrue("Invalid exception message: '" + ex.getMessage()
341 + "'.\nMessage is expected to start with: '" + expectedPrefix
342 + "'", ex.getMessage().startsWith(expectedPrefix));
343 }
344 is.close();
345 }
346 }
347 }
348 }
349
350
351
352
353
354 @Test
355 public void testDataBlockEncoding() throws IOException {
356 testInternals();
357 }
358
359 private void testInternals() throws IOException {
360 final int numBlocks = 5;
361 if(includesTag) {
362 TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3);
363 }
364 for (Compression.Algorithm algo : COMPRESSION_ALGORITHMS) {
365 for (boolean pread : new boolean[] { false, true }) {
366 for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
367 LOG.info("testDataBlockEncoding: Compression algorithm=" + algo + ", pread=" + pread +
368 ", dataBlockEncoder=" + encoding);
369 Path path = new Path(TEST_UTIL.getDataTestDir(), "blocks_v2_"
370 + algo + "_" + encoding.toString());
371 FSDataOutputStream os = fs.create(path);
372 HFileDataBlockEncoder dataBlockEncoder = (encoding != DataBlockEncoding.NONE) ?
373 new HFileDataBlockEncoderImpl(encoding) : NoOpDataBlockEncoder.INSTANCE;
374 HFileContext meta = new HFileContextBuilder()
375 .withCompression(algo)
376 .withIncludesMvcc(includesMemstoreTS)
377 .withIncludesTags(includesTag)
378 .withBytesPerCheckSum(HFile.DEFAULT_BYTES_PER_CHECKSUM)
379 .build();
380 HFileBlock.Writer hbw = new HFileBlock.Writer(dataBlockEncoder, meta);
381 long totalSize = 0;
382 final List<Integer> encodedSizes = new ArrayList<Integer>();
383 final List<ByteBuffer> encodedBlocks = new ArrayList<ByteBuffer>();
384 for (int blockId = 0; blockId < numBlocks; ++blockId) {
385 hbw.startWriting(BlockType.DATA);
386 writeTestKeyValues(hbw, blockId, includesMemstoreTS, includesTag);
387 hbw.writeHeaderAndData(os);
388 int headerLen = HConstants.HFILEBLOCK_HEADER_SIZE;
389 byte[] encodedResultWithHeader = hbw.cloneUncompressedBufferWithHeader().array();
390 final int encodedSize = encodedResultWithHeader.length - headerLen;
391 if (encoding != DataBlockEncoding.NONE) {
392
393
394 headerLen += DataBlockEncoding.ID_SIZE;
395 }
396 byte[] encodedDataSection =
397 new byte[encodedResultWithHeader.length - headerLen];
398 System.arraycopy(encodedResultWithHeader, headerLen,
399 encodedDataSection, 0, encodedDataSection.length);
400 final ByteBuffer encodedBuf =
401 ByteBuffer.wrap(encodedDataSection);
402 encodedSizes.add(encodedSize);
403 encodedBlocks.add(encodedBuf);
404 totalSize += hbw.getOnDiskSizeWithHeader();
405 }
406 os.close();
407
408 FSDataInputStream is = fs.open(path);
409 meta = new HFileContextBuilder()
410 .withHBaseCheckSum(true)
411 .withCompression(algo)
412 .withIncludesMvcc(includesMemstoreTS)
413 .withIncludesTags(includesTag)
414 .build();
415 HFileBlock.FSReaderImpl hbr = new HFileBlock.FSReaderImpl(is, totalSize, meta);
416 hbr.setDataBlockEncoder(dataBlockEncoder);
417 hbr.setIncludesMemstoreTS(includesMemstoreTS);
418 HFileBlock blockFromHFile, blockUnpacked;
419 int pos = 0;
420 for (int blockId = 0; blockId < numBlocks; ++blockId) {
421 blockFromHFile = hbr.readBlockData(pos, -1, pread, false);
422 assertEquals(0, HFile.getAndResetChecksumFailuresCount());
423 blockFromHFile.sanityCheck();
424 pos += blockFromHFile.getOnDiskSizeWithHeader();
425 assertEquals((int) encodedSizes.get(blockId),
426 blockFromHFile.getUncompressedSizeWithoutHeader());
427 assertEquals(meta.isCompressedOrEncrypted(), !blockFromHFile.isUnpacked());
428 long packedHeapsize = blockFromHFile.heapSize();
429 blockUnpacked = blockFromHFile.unpack(meta, hbr);
430 assertTrue(blockUnpacked.isUnpacked());
431 if (meta.isCompressedOrEncrypted()) {
432 LOG.info("packedHeapsize=" + packedHeapsize + ", unpackedHeadsize=" + blockUnpacked
433 .heapSize());
434 assertFalse(packedHeapsize == blockUnpacked.heapSize());
435 assertTrue("Packed heapSize should be < unpacked heapSize",
436 packedHeapsize < blockUnpacked.heapSize());
437 }
438 ByteBuffer actualBuffer = blockUnpacked.getBufferWithoutHeader();
439 if (encoding != DataBlockEncoding.NONE) {
440
441 assertEquals(
442 "Unexpected first byte with " + buildMessageDetails(algo, encoding, pread),
443 Long.toHexString(0), Long.toHexString(actualBuffer.get(0)));
444 assertEquals(
445 "Unexpected second byte with " + buildMessageDetails(algo, encoding, pread),
446 Long.toHexString(encoding.getId()), Long.toHexString(actualBuffer.get(1)));
447 actualBuffer.position(2);
448 actualBuffer = actualBuffer.slice();
449 }
450
451 ByteBuffer expectedBuffer = encodedBlocks.get(blockId);
452 expectedBuffer.rewind();
453
454
455 assertBuffersEqual(expectedBuffer, actualBuffer, algo, encoding, pread);
456
457
458 for (boolean reuseBuffer : new boolean[] { false, true }) {
459 ByteBuffer serialized = ByteBuffer.allocate(blockFromHFile.getSerializedLength());
460 blockFromHFile.serialize(serialized, true);
461 HFileBlock deserialized =
462 (HFileBlock) blockFromHFile.getDeserializer().deserialize(serialized, reuseBuffer);
463 assertEquals(
464 "Serialization did not preserve block state. reuseBuffer=" + reuseBuffer,
465 blockFromHFile, deserialized);
466
467 if (blockFromHFile != blockUnpacked) {
468 assertEquals("Deserializaed block cannot be unpacked correctly.",
469 blockUnpacked, deserialized.unpack(meta, hbr));
470 }
471 }
472 }
473 is.close();
474 }
475 }
476 }
477 }
478
479 static String buildMessageDetails(Algorithm compression, DataBlockEncoding encoding,
480 boolean pread) {
481 return String.format("compression %s, encoding %s, pread %s", compression, encoding, pread);
482 }
483
484 static void assertBuffersEqual(ByteBuffer expectedBuffer,
485 ByteBuffer actualBuffer, Compression.Algorithm compression,
486 DataBlockEncoding encoding, boolean pread) {
487 if (!actualBuffer.equals(expectedBuffer)) {
488 int prefix = 0;
489 int minLimit = Math.min(expectedBuffer.limit(), actualBuffer.limit());
490 while (prefix < minLimit &&
491 expectedBuffer.get(prefix) == actualBuffer.get(prefix)) {
492 prefix++;
493 }
494
495 fail(String.format(
496 "Content mismatch for %s, commonPrefix %d, expected %s, got %s",
497 buildMessageDetails(compression, encoding, pread), prefix,
498 nextBytesToStr(expectedBuffer, prefix),
499 nextBytesToStr(actualBuffer, prefix)));
500 }
501 }
502
503
504
505
506
507 private static String nextBytesToStr(ByteBuffer buf, int pos) {
508 int maxBytes = buf.limit() - pos;
509 int numBytes = Math.min(16, maxBytes);
510 return Bytes.toStringBinary(buf.array(), buf.arrayOffset() + pos,
511 numBytes) + (numBytes < maxBytes ? "..." : "");
512 }
513
514 @Test
515 public void testPreviousOffset() throws IOException {
516 testPreviousOffsetInternals();
517 }
518
519 protected void testPreviousOffsetInternals() throws IOException {
520
521 for (Compression.Algorithm algo : COMPRESSION_ALGORITHMS) {
522 for (boolean pread : BOOLEAN_VALUES) {
523 for (boolean cacheOnWrite : BOOLEAN_VALUES) {
524 Random rand = defaultRandom();
525 LOG.info("testPreviousOffset: Compression algorithm=" + algo + ", pread=" + pread +
526 ", cacheOnWrite=" + cacheOnWrite);
527 Path path = new Path(TEST_UTIL.getDataTestDir(), "prev_offset");
528 List<Long> expectedOffsets = new ArrayList<Long>();
529 List<Long> expectedPrevOffsets = new ArrayList<Long>();
530 List<BlockType> expectedTypes = new ArrayList<BlockType>();
531 List<ByteBuffer> expectedContents = cacheOnWrite
532 ? new ArrayList<ByteBuffer>() : null;
533 long totalSize = writeBlocks(rand, algo, path, expectedOffsets,
534 expectedPrevOffsets, expectedTypes, expectedContents);
535
536 FSDataInputStream is = fs.open(path);
537 HFileContext meta = new HFileContextBuilder()
538 .withHBaseCheckSum(true)
539 .withIncludesMvcc(includesMemstoreTS)
540 .withIncludesTags(includesTag)
541 .withCompression(algo).build();
542 HFileBlock.FSReader hbr = new HFileBlock.FSReaderImpl(is, totalSize, meta);
543 long curOffset = 0;
544 for (int i = 0; i < NUM_TEST_BLOCKS; ++i) {
545 if (!pread) {
546 assertEquals(is.getPos(), curOffset + (i == 0 ? 0 :
547 HConstants.HFILEBLOCK_HEADER_SIZE));
548 }
549
550 assertEquals(expectedOffsets.get(i).longValue(), curOffset);
551 if (detailedLogging) {
552 LOG.info("Reading block #" + i + " at offset " + curOffset);
553 }
554 HFileBlock b = hbr.readBlockData(curOffset, -1, pread, false);
555 if (detailedLogging) {
556 LOG.info("Block #" + i + ": " + b);
557 }
558 assertEquals("Invalid block #" + i + "'s type:",
559 expectedTypes.get(i), b.getBlockType());
560 assertEquals("Invalid previous block offset for block " + i
561 + " of " + "type " + b.getBlockType() + ":",
562 (long) expectedPrevOffsets.get(i), b.getPrevBlockOffset());
563 b.sanityCheck();
564 assertEquals(curOffset, b.getOffset());
565
566
567
568 HFileBlock b2 = hbr.readBlockData(curOffset, b.getOnDiskSizeWithHeader(), pread, false);
569 b2.sanityCheck();
570
571 assertEquals(b.getBlockType(), b2.getBlockType());
572 assertEquals(b.getOnDiskSizeWithoutHeader(),
573 b2.getOnDiskSizeWithoutHeader());
574 assertEquals(b.getOnDiskSizeWithHeader(),
575 b2.getOnDiskSizeWithHeader());
576 assertEquals(b.getUncompressedSizeWithoutHeader(),
577 b2.getUncompressedSizeWithoutHeader());
578 assertEquals(b.getPrevBlockOffset(), b2.getPrevBlockOffset());
579 assertEquals(curOffset, b2.getOffset());
580 assertEquals(b.getBytesPerChecksum(), b2.getBytesPerChecksum());
581 assertEquals(b.getOnDiskDataSizeWithHeader(),
582 b2.getOnDiskDataSizeWithHeader());
583 assertEquals(0, HFile.getAndResetChecksumFailuresCount());
584
585 curOffset += b.getOnDiskSizeWithHeader();
586
587 if (cacheOnWrite) {
588
589
590
591 b = b.unpack(meta, hbr);
592
593
594 ByteBuffer bufRead = b.getBufferReadOnly();
595 ByteBuffer bufExpected = expectedContents.get(i);
596 boolean bytesAreCorrect = Bytes.compareTo(bufRead.array(),
597 bufRead.arrayOffset(),
598 bufRead.limit() - b.totalChecksumBytes(),
599 bufExpected.array(), bufExpected.arrayOffset(),
600 bufExpected.limit()) == 0;
601 String wrongBytesMsg = "";
602
603 if (!bytesAreCorrect) {
604
605
606 wrongBytesMsg = "Expected bytes in block #" + i + " (algo="
607 + algo + ", pread=" + pread
608 + ", cacheOnWrite=" + cacheOnWrite + "):\n";
609 wrongBytesMsg += Bytes.toStringBinary(bufExpected.array(),
610 bufExpected.arrayOffset(), Math.min(32 + 10, bufExpected.limit()))
611 + ", actual:\n"
612 + Bytes.toStringBinary(bufRead.array(),
613 bufRead.arrayOffset(), Math.min(32 + 10, bufRead.limit()));
614 if (detailedLogging) {
615 LOG.warn("expected header" +
616 HFileBlock.toStringHeader(bufExpected) +
617 "\nfound header" +
618 HFileBlock.toStringHeader(bufRead));
619 LOG.warn("bufread offset " + bufRead.arrayOffset() +
620 " limit " + bufRead.limit() +
621 " expected offset " + bufExpected.arrayOffset() +
622 " limit " + bufExpected.limit());
623 LOG.warn(wrongBytesMsg);
624 }
625 }
626 assertTrue(wrongBytesMsg, bytesAreCorrect);
627 }
628 }
629
630 assertEquals(curOffset, fs.getFileStatus(path).getLen());
631 is.close();
632 }
633 }
634 }
635 }
636
637 private Random defaultRandom() {
638 return new Random(189237);
639 }
640
641 private class BlockReaderThread implements Callable<Boolean> {
642 private final String clientId;
643 private final HFileBlock.FSReader hbr;
644 private final List<Long> offsets;
645 private final List<BlockType> types;
646 private final long fileSize;
647
648 public BlockReaderThread(String clientId,
649 HFileBlock.FSReader hbr, List<Long> offsets, List<BlockType> types,
650 long fileSize) {
651 this.clientId = clientId;
652 this.offsets = offsets;
653 this.hbr = hbr;
654 this.types = types;
655 this.fileSize = fileSize;
656 }
657
658 @Override
659 public Boolean call() throws Exception {
660 Random rand = new Random(clientId.hashCode());
661 long endTime = System.currentTimeMillis() + 10000;
662 int numBlocksRead = 0;
663 int numPositionalRead = 0;
664 int numWithOnDiskSize = 0;
665 while (System.currentTimeMillis() < endTime) {
666 int blockId = rand.nextInt(NUM_TEST_BLOCKS);
667 long offset = offsets.get(blockId);
668 boolean pread = rand.nextBoolean();
669 boolean withOnDiskSize = rand.nextBoolean();
670 long expectedSize =
671 (blockId == NUM_TEST_BLOCKS - 1 ? fileSize
672 : offsets.get(blockId + 1)) - offset;
673
674 HFileBlock b;
675 try {
676 long onDiskSizeArg = withOnDiskSize ? expectedSize : -1;
677 b = hbr.readBlockData(offset, onDiskSizeArg, pread, false);
678 } catch (IOException ex) {
679 LOG.error("Error in client " + clientId + " trying to read block at "
680 + offset + ", pread=" + pread + ", withOnDiskSize=" +
681 withOnDiskSize, ex);
682 return false;
683 }
684
685 assertEquals(types.get(blockId), b.getBlockType());
686 assertEquals(expectedSize, b.getOnDiskSizeWithHeader());
687 assertEquals(offset, b.getOffset());
688
689 ++numBlocksRead;
690 if (pread)
691 ++numPositionalRead;
692 if (withOnDiskSize)
693 ++numWithOnDiskSize;
694 }
695 LOG.info("Client " + clientId + " successfully read " + numBlocksRead +
696 " blocks (with pread: " + numPositionalRead + ", with onDiskSize " +
697 "specified: " + numWithOnDiskSize + ")");
698
699 return true;
700 }
701
702 }
703
704 @Test
705 public void testConcurrentReading() throws Exception {
706 testConcurrentReadingInternals();
707 }
708
709 protected void testConcurrentReadingInternals() throws IOException,
710 InterruptedException, ExecutionException {
711 for (Compression.Algorithm compressAlgo : COMPRESSION_ALGORITHMS) {
712 Path path = new Path(TEST_UTIL.getDataTestDir(), "concurrent_reading");
713 Random rand = defaultRandom();
714 List<Long> offsets = new ArrayList<Long>();
715 List<BlockType> types = new ArrayList<BlockType>();
716 writeBlocks(rand, compressAlgo, path, offsets, null, types, null);
717 FSDataInputStream is = fs.open(path);
718 long fileSize = fs.getFileStatus(path).getLen();
719 HFileContext meta = new HFileContextBuilder()
720 .withHBaseCheckSum(true)
721 .withIncludesMvcc(includesMemstoreTS)
722 .withIncludesTags(includesTag)
723 .withCompression(compressAlgo)
724 .build();
725 HFileBlock.FSReader hbr = new HFileBlock.FSReaderImpl(is, fileSize, meta);
726
727 Executor exec = Executors.newFixedThreadPool(NUM_READER_THREADS);
728 ExecutorCompletionService<Boolean> ecs =
729 new ExecutorCompletionService<Boolean>(exec);
730
731 for (int i = 0; i < NUM_READER_THREADS; ++i) {
732 ecs.submit(new BlockReaderThread("reader_" + (char) ('A' + i), hbr,
733 offsets, types, fileSize));
734 }
735
736 for (int i = 0; i < NUM_READER_THREADS; ++i) {
737 Future<Boolean> result = ecs.take();
738 assertTrue(result.get());
739 if (detailedLogging) {
740 LOG.info(String.valueOf(i + 1)
741 + " reader threads finished successfully (algo=" + compressAlgo
742 + ")");
743 }
744 }
745
746 is.close();
747 }
748 }
749
750 private long writeBlocks(Random rand, Compression.Algorithm compressAlgo,
751 Path path, List<Long> expectedOffsets, List<Long> expectedPrevOffsets,
752 List<BlockType> expectedTypes, List<ByteBuffer> expectedContents
753 ) throws IOException {
754 boolean cacheOnWrite = expectedContents != null;
755 FSDataOutputStream os = fs.create(path);
756 HFileContext meta = new HFileContextBuilder()
757 .withHBaseCheckSum(true)
758 .withIncludesMvcc(includesMemstoreTS)
759 .withIncludesTags(includesTag)
760 .withCompression(compressAlgo)
761 .withBytesPerCheckSum(HFile.DEFAULT_BYTES_PER_CHECKSUM)
762 .build();
763 HFileBlock.Writer hbw = new HFileBlock.Writer(null, meta);
764 Map<BlockType, Long> prevOffsetByType = new HashMap<BlockType, Long>();
765 long totalSize = 0;
766 for (int i = 0; i < NUM_TEST_BLOCKS; ++i) {
767 long pos = os.getPos();
768 int blockTypeOrdinal = rand.nextInt(BlockType.values().length);
769 if (blockTypeOrdinal == BlockType.ENCODED_DATA.ordinal()) {
770 blockTypeOrdinal = BlockType.DATA.ordinal();
771 }
772 BlockType bt = BlockType.values()[blockTypeOrdinal];
773 DataOutputStream dos = hbw.startWriting(bt);
774 int size = rand.nextInt(500);
775 for (int j = 0; j < size; ++j) {
776
777 dos.writeShort(i + 1);
778 dos.writeInt(j + 1);
779 }
780
781 if (expectedOffsets != null)
782 expectedOffsets.add(os.getPos());
783
784 if (expectedPrevOffsets != null) {
785 Long prevOffset = prevOffsetByType.get(bt);
786 expectedPrevOffsets.add(prevOffset != null ? prevOffset : -1);
787 prevOffsetByType.put(bt, os.getPos());
788 }
789
790 expectedTypes.add(bt);
791
792 hbw.writeHeaderAndData(os);
793 totalSize += hbw.getOnDiskSizeWithHeader();
794
795 if (cacheOnWrite)
796 expectedContents.add(hbw.cloneUncompressedBufferWithHeader());
797
798 if (detailedLogging) {
799 LOG.info("Written block #" + i + " of type " + bt
800 + ", uncompressed size " + hbw.getUncompressedSizeWithoutHeader()
801 + ", packed size " + hbw.getOnDiskSizeWithoutHeader()
802 + " at offset " + pos);
803 }
804 }
805 os.close();
806 LOG.info("Created a temporary file at " + path + ", "
807 + fs.getFileStatus(path).getLen() + " byte, compression=" +
808 compressAlgo);
809 return totalSize;
810 }
811
812 @Test
813 public void testBlockHeapSize() {
814 testBlockHeapSizeInternals();
815 }
816
817 protected void testBlockHeapSizeInternals() {
818 if (ClassSize.is32BitJVM()) {
819 assertEquals(HFileBlock.BYTE_BUFFER_HEAP_SIZE, 64);
820 } else {
821 assertEquals(HFileBlock.BYTE_BUFFER_HEAP_SIZE, 64);
822 }
823
824 for (int size : new int[] { 100, 256, 12345 }) {
825 byte[] byteArr = new byte[HConstants.HFILEBLOCK_HEADER_SIZE + size];
826 ByteBuffer buf = ByteBuffer.wrap(byteArr, 0, size);
827 HFileContext meta = new HFileContextBuilder()
828 .withIncludesMvcc(includesMemstoreTS)
829 .withIncludesTags(includesTag)
830 .withHBaseCheckSum(false)
831 .withCompression(Algorithm.NONE)
832 .withBytesPerCheckSum(HFile.DEFAULT_BYTES_PER_CHECKSUM)
833 .withChecksumType(ChecksumType.NULL).build();
834 HFileBlock block = new HFileBlock(BlockType.DATA, size, size, -1, buf,
835 HFileBlock.FILL_HEADER, -1, 0, -1, meta);
836 long byteBufferExpectedSize =
837 ClassSize.align(ClassSize.estimateBase(buf.getClass(), true)
838 + HConstants.HFILEBLOCK_HEADER_SIZE + size);
839 long hfileMetaSize = ClassSize.align(ClassSize.estimateBase(HFileContext.class, true));
840 long hfileBlockExpectedSize =
841 ClassSize.align(ClassSize.estimateBase(HFileBlock.class, true));
842 long expected = hfileBlockExpectedSize + byteBufferExpectedSize + hfileMetaSize;
843 assertEquals("Block data size: " + size + ", byte buffer expected " +
844 "size: " + byteBufferExpectedSize + ", HFileBlock class expected " +
845 "size: " + hfileBlockExpectedSize + ";", expected,
846 block.heapSize());
847 }
848 }
849
850 @Test
851 public void testSerializeWithoutNextBlockMetadata() {
852 int size = 100;
853 int length = HConstants.HFILEBLOCK_HEADER_SIZE + size;
854 byte[] byteArr = new byte[length];
855 ByteBuffer buf = ByteBuffer.wrap(byteArr, 0, size);
856 HFileContext meta = new HFileContextBuilder().build();
857 HFileBlock blockWithNextBlockMetadata = new HFileBlock(BlockType.DATA, size, size, -1, buf,
858 HFileBlock.FILL_HEADER, -1, 52, -1, meta);
859 HFileBlock blockWithoutNextBlockMetadata = new HFileBlock(BlockType.DATA, size, size, -1, buf,
860 HFileBlock.FILL_HEADER, -1, -1, -1, meta);
861 ByteBuffer buff1 = ByteBuffer.allocate(length);
862 ByteBuffer buff2 = ByteBuffer.allocate(length);
863 blockWithNextBlockMetadata.serialize(buff1, true);
864 blockWithoutNextBlockMetadata.serialize(buff2, true);
865 assertNotEquals(buff1, buff2);
866 buff1.clear();
867 buff2.clear();
868 blockWithNextBlockMetadata.serialize(buff1, false);
869 blockWithoutNextBlockMetadata.serialize(buff2, false);
870 assertEquals(buff1, buff2);
871 }
872 }