package com.ctriposs.bigcache; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import com.ctriposs.bigcache.CacheConfig.StorageMode; import com.ctriposs.bigcache.utils.FileUtil; import com.ctriposs.bigcache.utils.TestUtil; @RunWith(Parameterized.class) public class BigCachePerfTestB { /********************* configurable parameters *********************/ private static final int LOOP = 5; private static final int ITEM_COUNT = 1000000; private static final int PRODUCER_COUNT = 4; private static final int CONSUMER_COUNT = 4; private static final int STRING_LEN = 16; /******************************************************************/ private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "performance/bigcache/"; private final AtomicInteger producingItemCount = new AtomicInteger(0); private final AtomicInteger consumingItemCount = new AtomicInteger(0); private final BlockingQueue<String> keysInMemoryQueue = new LinkedBlockingQueue<String>(); private static BigCache<String> cache; @Parameter(value = 0) public StorageMode storageMode; @Parameters public static Collection<StorageMode[]> data() throws IOException { StorageMode[][] data = { { StorageMode.PureFile }, { StorageMode.MemoryMappedPlusFile }, { StorageMode.OffHeapPlusFile } }; return Arrays.asList(data); } private BigCache<String> cache() throws IOException { CacheConfig config = new CacheConfig(); config.setStorageMode(storageMode); BigCache<String> cache = new BigCache<String>(TEST_DIR, config); return cache; } @Test public void testProduceThenConsume() throws IOException, InterruptedException { System.out.println("SDB performance test begin ..."); for (int i = 0; i < LOOP; i++) { cache = cache(); System.out.println("[doRunProduceThenConsume] round " + (i + 1) + " of " + LOOP); this.doRunProduceThenConsume(); producingItemCount.set(0); consumingItemCount.set(0); this.close(); } System.out.println("[doRunProduceThenConsume] test ends"); } @Test public void testProduceMixedConsume() throws IOException, InterruptedException { for (int i = 0; i < LOOP; i++) { cache = cache(); System.out.println("[doRunMixed] round " + (i + 1) + " of " + LOOP); this.doRunProduceMixedConsume(); producingItemCount.set(0); consumingItemCount.set(0); this.close(); } System.out.println("[doRunMixed] test ends"); } private void doRunProduceThenConsume() throws InterruptedException { CountDownLatch producerLatch = new CountDownLatch(PRODUCER_COUNT); CountDownLatch consumerLatch = new CountDownLatch(CONSUMER_COUNT); BlockingQueue<Result> producerResults = new LinkedBlockingQueue<Result>(); BlockingQueue<Result> consumerResults = new LinkedBlockingQueue<Result>(); long start = System.nanoTime(); long totalProducingTime = 0; long totalConsumingTime = 0; for (int i = 0; i < PRODUCER_COUNT; i++) { Producer p = new Producer(producerLatch, producerResults); p.start(); } for (int i = 0; i < PRODUCER_COUNT; i++) { Result result = producerResults.take(); assertEquals(result.status, Status.SUCCESS); totalProducingTime += result.duration; } long duration = System.nanoTime() - start; System.out.println("-----------------------------------------------"); System.out.println("Producing test result:"); System.out.printf("Total test time = %d ns.\n", duration); System.out.printf("Total item count = %d\n", ITEM_COUNT); System.out.printf("Producer thread number = %d\n", PRODUCER_COUNT); System.out.printf("Item message length = %d bytes\n", STRING_LEN); System.out.printf("Total producing time = %d ns.\n", totalProducingTime); System.out.printf("Average producint time = %d ns.\n", totalProducingTime / PRODUCER_COUNT); System.out.println("-----------------------------------------------"); // the consumer start start = System.nanoTime(); for (int i = 0; i < CONSUMER_COUNT; i++) { Consumer c = new Consumer(consumerLatch, consumerResults); c.start(); } for (int i = 0; i < CONSUMER_COUNT; i++) { Result result = consumerResults.take(); assertEquals(result.status, Status.SUCCESS); totalConsumingTime += result.duration; } duration = System.nanoTime() - start; assertEquals(producingItemCount.get(), consumingItemCount.get()); assertTrue(keysInMemoryQueue.isEmpty()); System.out.println("Consuming test result:"); System.out.printf("Total test time = %d ns.\n", duration); System.out.printf("Total item count = %d\n", ITEM_COUNT); System.out.printf("Consumer thread number = %d\n", CONSUMER_COUNT); System.out.printf("Item message length = %d bytes\n", STRING_LEN); System.out.printf("Total consuming time = %d ns.\n", totalConsumingTime); System.out.printf("Average consuming time = %d ns.\n", totalConsumingTime / CONSUMER_COUNT); System.out.println("-----------------------------------------------"); } private void doRunProduceMixedConsume() throws InterruptedException { CountDownLatch allLatch = new CountDownLatch(PRODUCER_COUNT + CONSUMER_COUNT); BlockingQueue<Result> producerResults = new LinkedBlockingQueue<Result>(); BlockingQueue<Result> consumerResults = new LinkedBlockingQueue<Result>(); long totalProducingTime = 0; long totalConsumingTime = 0; long start = System.nanoTime(); //run testing for (int i = 0; i < PRODUCER_COUNT; i++) { Producer p = new Producer(allLatch, producerResults); p.start(); } for (int i = 0; i < CONSUMER_COUNT; i++) { Consumer c = new Consumer(allLatch, consumerResults); c.start(); } //verify and report for (int i = 0; i < PRODUCER_COUNT; i++) { Result result = producerResults.take(); assertEquals(result.status, Status.SUCCESS); totalProducingTime += result.duration; } for (int i = 0; i < CONSUMER_COUNT; i++) { Result result = consumerResults.take(); assertEquals(result.status, Status.SUCCESS); totalConsumingTime += result.duration; } long duration = System.nanoTime() - start; assertEquals(producingItemCount.get(), consumingItemCount.get()); System.out.println("-----------------------------------------------"); System.out.printf("Total test time = %d ns.\n", duration); System.out.printf("Total item count = %d\n", ITEM_COUNT); System.out.printf("Producer thread number = %d\n", PRODUCER_COUNT); System.out.printf("Consumer thread number = %d\n", CONSUMER_COUNT); System.out.printf("Item message length = %d bytes\n", STRING_LEN); System.out.printf("Total consuming time = %d ns.\n", totalConsumingTime); System.out.printf("Average consuming time = %d ns.\n", totalConsumingTime / CONSUMER_COUNT); System.out.printf("Total producing time = %d ns.\n", totalProducingTime); System.out.printf("Average producing time = %d ns.\n", totalProducingTime / PRODUCER_COUNT); System.out.println("-----------------------------------------------"); } private void close() throws IOException { try { cache.close(); FileUtil.deleteDirectory(new File(TEST_DIR)); } catch (IllegalStateException e) { System.gc(); try { FileUtil.deleteDirectory(new File(TEST_DIR)); } catch (IllegalStateException e1) { try { Thread.sleep(3000); } catch (InterruptedException e2) { } FileUtil.deleteDirectory(new File(TEST_DIR)); } } } private class Producer extends Thread { private final CountDownLatch latch; private final Queue<Result> resultQueue; public Producer(CountDownLatch latch, Queue<Result> resultQueue) { this.latch = latch; this.resultQueue = resultQueue; } public void run() { Result result = new Result(); try { latch.countDown(); latch.await(); long start = System.nanoTime(); while (true) { int count = producingItemCount.incrementAndGet(); if (count > ITEM_COUNT) break; String string = TestUtil.randomString(STRING_LEN); keysInMemoryQueue.offer(string); cache.put(string, string.getBytes()); } result.status = Status.SUCCESS; result.duration = System.nanoTime() - start; } catch (Exception e) { e.printStackTrace(); result.status = Status.ERROR; } resultQueue.offer(result); } } private class Consumer extends Thread { private final CountDownLatch latch; private final Queue<Result> resultQueue; public Consumer(CountDownLatch latch, Queue<Result> resultQueue) { this.latch = latch; this.resultQueue = resultQueue; } public void run() { Result result = new Result(); result.status = Status.SUCCESS; try { latch.countDown(); latch.await(); long start = System.nanoTime(); while (true) { int count = consumingItemCount.getAndIncrement(); if (count >= ITEM_COUNT) break; String key = keysInMemoryQueue.take(); if (key != null && !key.isEmpty()) { byte[] valueBytes = cache.get(key); // wait a moment for k/v to be put in the DB // may cause dead lock while (valueBytes == null) { valueBytes = cache.get(key); } if (!key.equals(new String(valueBytes))) { result.status = Status.ERROR; } } } result.duration = System.nanoTime() - start; } catch (Exception e) { e.printStackTrace(); result.status = Status.ERROR; } resultQueue.offer(result); } } private static enum Status { ERROR, SUCCESS } private static class Result { Status status; long duration; } }