de.hybris.platform.cache.CachePerformanceTest.java Source code

Java tutorial

Introduction

Here is the source code for de.hybris.platform.cache.CachePerformanceTest.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2013 hybris AG
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 * 
 *  
 */
package de.hybris.platform.cache;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.when;

import de.hybris.bootstrap.annotations.PerformanceTest;
import de.hybris.platform.core.Tenant;
import de.hybris.platform.test.TestThreadsHolder;
import de.hybris.platform.util.config.ConfigIntf;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

import org.apache.commons.lang.math.RandomUtils;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.bethecoder.ascii_table.ASCIITable;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;

/**
 * RegionCache performance tests
 */
@PerformanceTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test/EHCacheRegionPerformanceTest-context.xml" })
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class CachePerformanceTest {
    private static final String REGION_CACHE_NAME = "testRegionCache";
    private static final String REGION_CACHE_KEY_NAME = "testRegionCacheKey";
    private static final String OLD_CACHE_KEY_NAME = "testOldCacheKey";
    private static final String CACHE_KEY_NAME = "testCacheKey";

    private static final int THREADS = 30;
    private static final int TOTALHITS = THREADS * 500 * 1000;
    private static final int MAX_WAIT_SECONDS = 240;
    private static final int FILL = 10000;

    @Mock
    Tenant tenantMock;

    @Mock
    ConfigIntf configMock;

    @After
    public void clean() {
        CacheManager.getInstance().clearAll();
        System.gc();
    }

    @Test
    public void testOldCache() {
        final BaseCacheYFastFIFOMapStub oldCacheMap = createOldCacheMap();

        fillOldCacheMap(oldCacheMap, FILL);
        assertEquals(FILL, oldCacheMap.getMaxReachedSize());

        writeResultTable("old cache", 100, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(oldCacheMap, FILL, true, THREADS, TOTALHITS, MAX_WAIT_SECONDS));

        writeResultTable("old cache", 0, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(oldCacheMap, FILL, false, THREADS, TOTALHITS, MAX_WAIT_SECONDS));
    }

    @Test
    public void testRegionCachePerformance() {
        final Cache regionCacheMap = createRegionCacheMap();

        fillRegionCacheMap(regionCacheMap, FILL);
        assertEquals(FILL, regionCacheMap.getKeys().size());
        regionCacheMap.setStatisticsEnabled(false);

        writeResultTable("region cache", 100, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(regionCacheMap, FILL, true, THREADS, TOTALHITS, MAX_WAIT_SECONDS));

        writeResultTable("region cache", 0, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(regionCacheMap, FILL, false, THREADS, TOTALHITS, MAX_WAIT_SECONDS));
    }

    @Test
    public void testConcurrentHashMap() {
        final ConcurrentHashMap<String, Object> chMap = new ConcurrentHashMap<String, Object>(FILL * 2, 0.75f, 8);

        fillCacheMap(chMap, FILL);
        assertEquals(FILL, chMap.size());

        writeResultTable("ConcurrentHashMap", 100, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(chMap, FILL, true, THREADS, TOTALHITS, MAX_WAIT_SECONDS));

        writeResultTable("ConcurrentHashMap", 0, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(chMap, FILL, false, THREADS, TOTALHITS, MAX_WAIT_SECONDS));
    }

    @Test
    public void testConcurrentLinkedHashMap() {
        final ConcurrentMap<String, Object> chMap = new ConcurrentLinkedHashMap.Builder<String, Object>()
                .maximumWeightedCapacity(FILL).concurrencyLevel(8).initialCapacity(FILL).build();

        fillCacheMap(chMap, FILL);
        assertEquals(FILL, chMap.size());

        writeResultTable("ConcurrentLinkedHashMap", 100, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(chMap, FILL, true, THREADS, TOTALHITS, MAX_WAIT_SECONDS));

        writeResultTable("ConcurrentLinkedHashMap", 0, FILL, TOTALHITS, THREADS,
                executeMultithreadedCacheAccess(chMap, FILL, false, THREADS, TOTALHITS, MAX_WAIT_SECONDS));
    }

    static void fillRegionCacheMap(final Cache regionCacheMap, final int size) {
        for (int i = 0; i < size; i++) {
            regionCacheMap.putQuiet(new Element(REGION_CACHE_KEY_NAME + i, "RegionCacheValue" + i));
        }
    }

    static void fillOldCacheMap(final BaseCacheYFastFIFOMapStub oldCacheMap, final int size) {
        for (int i = 0; i < size; i++) {
            oldCacheMap.put(OLD_CACHE_KEY_NAME + i, "OldCacheValue" + i);
        }
    }

    private void fillCacheMap(final Map<String, Object> cacheMap, final int size) {
        for (int i = 0; i < size; i++) {
            cacheMap.put(CACHE_KEY_NAME + i, "CacheValue" + i);
        }
    }

    BaseCacheYFastFIFOMapStub createOldCacheMap() {
        MockitoAnnotations.initMocks(this);

        when(tenantMock.getConfig()).thenReturn(configMock);
        when(tenantMock.getTenantID()).thenReturn("master");
        when(configMock.getParameter("cache.main.map"))
                .thenReturn("de.hybris.platform.cache.BaseCacheYFastFIFOMapStub");
        when(configMock.getParameter("cache.experimental.cachemap"))
                .thenReturn("de.hybris.platform.cache.BaseCacheYFastFIFOMapStub");
        //
        when(configMock.getParameter("cache.evictionpolicy")).thenReturn("FIFO");
        when(Integer.valueOf(configMock.getInt("cache.concurrency.level", 8))).thenReturn(Integer.valueOf(8));

        //init oldCacheMap      
        final CacheBaseStub cacheBase = new CacheBaseStub(tenantMock, FILL + 1000);
        return new BaseCacheYFastFIFOMapStub(cacheBase, FILL + 1000);
    }

    static Cache createRegionCacheMap() {

        //init regionCacheMap
        final CacheManager manager = CacheManager.getInstance();
        if (manager.cacheExists(REGION_CACHE_NAME)) {
            manager.removeCache(REGION_CACHE_NAME);
        }
        final Cache regionCacheMap = new Cache(createRegionCacheConfiguration());

        manager.addCache(regionCacheMap);
        assertNotNull(regionCacheMap);

        return regionCacheMap;
    }

    private static CacheConfiguration createRegionCacheConfiguration() {
        final CacheConfiguration config = new CacheConfiguration();
        config.setStatistics(false);
        config.setMemoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU.toString());
        config.setMaxEntriesLocalHeap(FILL + 1000);
        config.setCopyOnRead(false);
        config.setCopyOnWrite(false);
        config.setName(REGION_CACHE_NAME);
        config.overflowToDisk(false);
        config.setEternal(true);
        config.setLogging(false);

        return config;
    }

    private void writeResultTable(final String cacheType, final int hitRate, final int fill, final int totalhits,
            final int threads, final long duration) {
        final String[] header = { "cache type", "hit rate", "fill", "totalhits", "threads", "duration" };
        ASCIITable.getInstance().printTable(header,
                new String[][] { { cacheType, hitRate + " %", String.valueOf(fill), String.valueOf(totalhits), // 
                        String.valueOf(threads), duration + " ms" } });
    }

    private long executeMultithreadedCacheAccess(final Object cacheMap, final int cacheFill,
            final boolean hitAlways, final int numberOfThreads, final long hitcount, final int maxWaitSeconds) {
        final long hitsPerThread = hitcount / numberOfThreads;
        final long hitsRemainder = hitcount % numberOfThreads;

        final de.hybris.platform.test.RunnerCreator<Runnable> runnerCreator = new de.hybris.platform.test.RunnerCreator<Runnable>() {
            @Override
            public Runnable newRunner(final int threadNumber) {
                final long hits = threadNumber < hitsRemainder ? hitsPerThread + 1 : hitsPerThread;
                if (cacheMap instanceof Cache) {
                    return new RegionCacheReader(threadNumber, hits, cacheFill, hitAlways, (Cache) cacheMap);
                } else if (cacheMap instanceof BaseCacheYFastFIFOMapStub) {
                    return new OldCacheReader(threadNumber, hits, cacheFill, hitAlways,
                            (BaseCacheYFastFIFOMapStub) cacheMap);
                } else if (cacheMap instanceof Map) {
                    return new PlainMapReader(threadNumber, hits, cacheFill, hitAlways,
                            (Map<String, Object>) cacheMap);
                } else {
                    throw new IllegalArgumentException("unknown cache map type " + cacheMap);
                }
            }
        };

        final TestThreadsHolder workerThreads = new TestThreadsHolder<Runnable>(numberOfThreads, runnerCreator);

        workerThreads.startAll();
        assertTrue("not all workers finished after " + maxWaitSeconds + " seconds",
                workerThreads.waitAndDestroy(maxWaitSeconds));

        return workerThreads.getStartToFinishMillis();
    }

    private static abstract class AbstractCacheReader implements Runnable {
        final int number;
        final long hits;
        final int cacheFill;
        final int sleepMs;
        final boolean hitAlways;

        public AbstractCacheReader(final int number, final long hits, final int cacheFill, final boolean hitAlways,
                final int sleepMs) {
            this.number = number;
            this.hits = hits;
            this.cacheFill = cacheFill;
            this.hitAlways = hitAlways;
            this.sleepMs = sleepMs;
        }

        @Override
        final public void run() {
            for (int h = 0; h < hits && !Thread.currentThread().isInterrupted(); h++) {
                if (hitAlways) {
                    doHitAlways();
                } else {
                    doHitNever();
                }
                if (sleepMs > 0) {
                    try {
                        Thread.sleep(sleepMs);
                    } catch (final InterruptedException e) {
                        break;
                    }
                }
            }
        }

        abstract void doHitAlways();

        abstract void doHitNever();
    }

    private static class RegionCacheReader extends AbstractCacheReader {
        private final Cache cacheMap;

        public RegionCacheReader(final int number, final long hits, final int cacheFill, final boolean hitAlways,
                final Cache cacheMap) {
            super(number, hits, cacheFill, hitAlways, 0);
            this.cacheMap = cacheMap;
        }

        @Override
        void doHitAlways() {
            final Element elem = cacheMap.get(REGION_CACHE_KEY_NAME + RandomUtils.nextInt(cacheFill));
            assertNotNull(elem);
        }

        @Override
        void doHitNever() {
            final Element notExistingElem = cacheMap.get("RegionCacheKeyNotInMap");
            assertNull(notExistingElem);
        }
    }

    static class OldCacheReader extends AbstractCacheReader {
        final BaseCacheYFastFIFOMapStub cacheMap;

        OldCacheReader(final int number, final long hits, final int cacheFill, final boolean hitAlways,
                final BaseCacheYFastFIFOMapStub cacheMap) {
            super(number, hits, cacheFill, hitAlways, 0);
            this.cacheMap = cacheMap;
        }

        @Override
        void doHitAlways() {
            final Object elem = cacheMap.get(OLD_CACHE_KEY_NAME + RandomUtils.nextInt(cacheFill));
            assertNotNull(elem);
        }

        @Override
        void doHitNever() {
            final Object notExistingElem = cacheMap.get("OldCacheKeyNotInMap");
            assertNull(notExistingElem);
        }
    }

    static class PlainMapReader extends AbstractCacheReader {
        final Map<String, Object> cacheMap;

        PlainMapReader(final int number, final long hits, final int cacheFill, final boolean hitAlways,
                final Map<String, Object> cacheMap) {
            super(number, hits, cacheFill, hitAlways, 0);
            this.cacheMap = cacheMap;
        }

        @Override
        void doHitAlways() {
            final Object elem = cacheMap.get(CACHE_KEY_NAME + RandomUtils.nextInt(cacheFill));
            assertNotNull(elem);
        }

        @Override
        void doHitNever() {
            final Object notExistingElem = cacheMap.get("CHMCacheKeyNotInMap");
            assertNull(notExistingElem);
        }
    }

}