/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hive.llap.cache; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import org.apache.hadoop.hive.common.io.Allocator.AllocatorOutOfMemoryException; import org.apache.hadoop.hive.common.io.encoded.MemoryBuffer; import org.apache.hadoop.hive.llap.metrics.LlapDaemonCacheMetrics; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(Parameterized.class) public class TestBuddyAllocator { private static final Logger LOG = LoggerFactory.getLogger(TestBuddyAllocator.class); private final Random rdm = new Random(2284); private final boolean isDirect; private final boolean isMapped; private final String tmpDir = System.getProperty("java.io.tmpdir", "."); @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { false, false }, { true, false }, { true, true } }); } public TestBuddyAllocator(boolean direct, boolean mmap) { isDirect = direct; isMapped = mmap; } private static class DummyMemoryManager implements MemoryManager { @Override public void reserveMemory(long memoryToReserve) { } @Override public void releaseMemory(long memUsage) { } @Override public String debugDumpForOom() { return ""; } @Override public void updateMaxSize(long maxSize) { } @Override public long forceReservedMemory(int allocationSize, int count) { return allocationSize * count; } @Override public void debugDumpShort(StringBuilder sb) { } } @Test public void testVariableSizeAllocs() throws Exception { testVariableSizeInternal(1, 2, 1); } @Test public void testVariableSizeMultiAllocs() throws Exception { testVariableSizeInternal(3, 2, 3); testVariableSizeInternal(5, 2, 5); } @Test public void testSameSizes() throws Exception { int min = 3, max = 8, maxAlloc = 1 << max; BuddyAllocator a = new BuddyAllocator(isDirect, isMapped, 1 << min, maxAlloc, maxAlloc, maxAlloc, tmpDir, new DummyMemoryManager(), LlapDaemonCacheMetrics.create("test", "1")); for (int i = max; i >= min; --i) { allocSameSize(a, 1 << (max - i), i); } } @Test public void testMultipleArenas() throws Exception { int max = 8, maxAlloc = 1 << max, allocLog2 = max - 1, arenaCount = 5; BuddyAllocator a = new BuddyAllocator(isDirect, isMapped, 1 << 3, maxAlloc, maxAlloc, maxAlloc * arenaCount, tmpDir, new DummyMemoryManager(), LlapDaemonCacheMetrics.create("test", "1")); allocSameSize(a, arenaCount * 2, allocLog2); } @Test public void testMTT() { final int min = 3, max = 8, maxAlloc = 1 << max, allocsPerSize = 3; final BuddyAllocator a = new BuddyAllocator(isDirect, isMapped, 1 << min, maxAlloc, maxAlloc * 8, maxAlloc * 24, tmpDir, new DummyMemoryManager(), LlapDaemonCacheMetrics.create("test", "1")); ExecutorService executor = Executors.newFixedThreadPool(3); final CountDownLatch cdlIn = new CountDownLatch(3), cdlOut = new CountDownLatch(1); FutureTask<Void> upTask = new FutureTask<Void>(new Callable<Void>() { public Void call() throws Exception { syncThreadStart(cdlIn, cdlOut); allocateUp(a, min, max, allocsPerSize, false); allocateUp(a, min, max, allocsPerSize, true); return null; } }), downTask = new FutureTask<Void>(new Callable<Void>() { public Void call() throws Exception { syncThreadStart(cdlIn, cdlOut); allocateDown(a, min, max, allocsPerSize, false); allocateDown(a, min, max, allocsPerSize, true); return null; } }), sameTask = new FutureTask<Void>(new Callable<Void>() { public Void call() throws Exception { syncThreadStart(cdlIn, cdlOut); for (int i = min; i <= max; ++i) { allocSameSize(a, (1 << (max - i)) * allocsPerSize, i); } return null; } }); executor.execute(sameTask); executor.execute(upTask); executor.execute(downTask); try { cdlIn.await(); // Wait for all threads to be ready. cdlOut.countDown(); // Release them at the same time. upTask.get(); downTask.get(); sameTask.get(); } catch (Throwable t) { throw new RuntimeException(t); } } @Test public void testMTTArenas() { final int min = 3, max = 4, maxAlloc = 1 << max, minAllocCount = 2048, threadCount = 4; final BuddyAllocator a = new BuddyAllocator(isDirect, isMapped, 1 << min, maxAlloc, maxAlloc, (1 << min) * minAllocCount, tmpDir, new DummyMemoryManager(), LlapDaemonCacheMetrics.create("test", "1")); ExecutorService executor = Executors.newFixedThreadPool(threadCount); final CountDownLatch cdlIn = new CountDownLatch(threadCount), cdlOut = new CountDownLatch(1); Callable<Void> testCallable = new Callable<Void>() { public Void call() throws Exception { syncThreadStart(cdlIn, cdlOut); allocSameSize(a, minAllocCount / threadCount, min); return null; } }; @SuppressWarnings("unchecked") FutureTask<Void>[] allocTasks = new FutureTask[threadCount]; for (int i = 0; i < threadCount; ++i) { allocTasks[i] = new FutureTask<>(testCallable); executor.execute(allocTasks[i]); } try { cdlIn.await(); // Wait for all threads to be ready. cdlOut.countDown(); // Release them at the same time. for (int i = 0; i < threadCount; ++i) { allocTasks[i].get(); } } catch (Throwable t) { throw new RuntimeException(t); } } private void syncThreadStart(final CountDownLatch cdlIn, final CountDownLatch cdlOut) { cdlIn.countDown(); try { cdlOut.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } private void testVariableSizeInternal( int allocCount, int arenaSizeMult, int arenaCount) throws Exception { int min = 3, max = 8, maxAlloc = 1 << max, arenaSize = maxAlloc * arenaSizeMult; BuddyAllocator a = new BuddyAllocator(isDirect, isMapped, 1 << min, maxAlloc, arenaSize, arenaSize * arenaCount, tmpDir , new DummyMemoryManager(), LlapDaemonCacheMetrics.create("test", "1")); allocateUp(a, min, max, allocCount, true); allocateDown(a, min, max, allocCount, true); allocateDown(a, min, max, allocCount, false); allocateUp(a, min, max, allocCount, true); allocateUp(a, min, max, allocCount, false); allocateDown(a, min, max, allocCount, true); } private void allocSameSize(BuddyAllocator a, int allocCount, int sizeLog2) throws Exception { MemoryBuffer[][] allocs = new MemoryBuffer[allocCount][]; long[][] testValues = new long[allocCount][]; for (int j = 0; j < allocCount; ++j) { allocateAndUseBuffer(a, allocs, testValues, 1, j, sizeLog2); } deallocUpOrDown(a, false, allocs, testValues); } private void allocateUp(BuddyAllocator a, int min, int max, int allocPerSize, boolean isSameOrderDealloc) throws Exception { int sizes = max - min + 1; MemoryBuffer[][] allocs = new MemoryBuffer[sizes][]; // Put in the beginning; relies on the knowledge of internal implementation. Pave? long[][] testValues = new long[sizes][]; for (int i = min; i <= max; ++i) { allocateAndUseBuffer(a, allocs, testValues, allocPerSize, i - min, i); } deallocUpOrDown(a, isSameOrderDealloc, allocs, testValues); } private void allocateDown(BuddyAllocator a, int min, int max, int allocPerSize, boolean isSameOrderDealloc) throws Exception { int sizes = max - min + 1; MemoryBuffer[][] allocs = new MemoryBuffer[sizes][]; // Put in the beginning; relies on the knowledge of internal implementation. Pave? long[][] testValues = new long[sizes][]; for (int i = max; i >= min; --i) { allocateAndUseBuffer(a, allocs, testValues, allocPerSize, i - min, i); } deallocUpOrDown(a, isSameOrderDealloc, allocs, testValues); } private void allocateAndUseBuffer(BuddyAllocator a, MemoryBuffer[][] allocs, long[][] testValues, int allocCount, int index, int sizeLog2) throws Exception { allocs[index] = new MemoryBuffer[allocCount]; testValues[index] = new long[allocCount]; int size = (1 << sizeLog2) - 1; try { a.allocateMultiple(allocs[index], size); } catch (AllocatorOutOfMemoryException ex) { LOG.error("Failed to allocate " + allocCount + " of " + size + "; " + a.debugDumpForOomInternal()); throw ex; } // LOG.info("Allocated " + allocCount + " of " + size + "; " + a.debugDump()); for (int j = 0; j < allocCount; ++j) { MemoryBuffer mem = allocs[index][j]; long testValue = testValues[index][j] = rdm.nextLong(); int pos = mem.getByteBufferRaw().position(); mem.getByteBufferRaw().putLong(pos, testValue); int halfLength = mem.getByteBufferRaw().remaining() >> 1; if (halfLength + 8 <= mem.getByteBufferRaw().remaining()) { mem.getByteBufferRaw().putLong(pos + halfLength, testValue); } } } private void deallocUpOrDown(BuddyAllocator a, boolean isSameOrderDealloc, MemoryBuffer[][] allocs, long[][] testValues) { if (isSameOrderDealloc) { for (int i = 0; i < allocs.length; ++i) { deallocBuffers(a, allocs[i], testValues[i]); } } else { for (int i = allocs.length - 1; i >= 0; --i) { deallocBuffers(a, allocs[i], testValues[i]); } } } private void deallocBuffers( BuddyAllocator a, MemoryBuffer[] allocs, long[] testValues) { for (int j = 0; j < allocs.length; ++j) { LlapDataBuffer mem = (LlapDataBuffer)allocs[j]; int pos = mem.getByteBufferRaw().position(); assertEquals("Failed to match (" + pos + ") on " + j + "/" + allocs.length, testValues[j], mem.getByteBufferRaw().getLong(pos)); int halfLength = mem.getByteBufferRaw().remaining() >> 1; if (halfLength + 8 <= mem.getByteBufferRaw().remaining()) { assertEquals("Failed to match half (" + (pos + halfLength) + ") on " + j + "/" + allocs.length, testValues[j], mem.getByteBufferRaw().getLong(pos + halfLength)); } a.deallocate(mem); } } }