/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed 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 it.geosolutions.concurrencytest; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.IOException; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.stream.FileImageInputStream; import javax.media.jai.InterpolationNearest; import javax.media.jai.JAI; import javax.media.jai.TileCache; import javax.media.jai.operator.ScaleDescriptor; import org.junit.Test; import com.sun.media.imageio.plugins.tiff.TIFFImageReadParam; import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader; import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi; import com.sun.media.jai.util.SunTileCache; import it.geosolutions.concurrent.ConcurrentTileCache; import it.geosolutions.concurrent.ConcurrentTileCacheMultiMap; public class ConcurrentCacheTest { public static final long DEFAULT_MEMORY_CAPACITY = 128 * 1024 * 1024; public static final int DEFAULT_MAX_REQUEST_PER_THREAD = 100; public static final int STARTING_REQUEST_PER_FIRST_THREAD = 1000; public static final int EXPONENT = 6; public final static Logger LOGGER = Logger.getLogger(ConcurrentCacheTest.class.toString()); // diagnostics public static boolean DEFAULT_DIAGNOSTICS = false; // multiple simultaneous operations allowed public static boolean DEFAULT_MULTIOPERATIONS = false; // choice of the concurrency Level public static int DEFAULT_CONCURRENCY_LEVEL = 16; // Number of test per thread private int maxRequestPerThread; // latch for waiting all thread to finish private CountDownLatch latch; // variables for creating random image tile index private Random generator; private double minTileX; private double minTileY; private double maxTileX; private double maxTileY; // image to elaborate private RenderedImage image_; /** List of all used Caches */ public enum CacheType { SUN_TILE_CACHE(0, "SunTileCache"), CONCURRENT_TILE_CACHE(1, "ConcurrentTileCache"), CONCURRENT_MULTIMAP_TILE_CACHE( 2, "ConcurrentMultimapCache"); private final int tileCache; private final String tileCacheString; CacheType(int value, String s) { this.tileCache = value; this.tileCacheString = s; } public int valueCache() { return tileCache; } public String cacheName() { return tileCacheString; } }; /* * This test-class compares the functionality of the default Sun tilecache or the new implementation of the Concurrent tilecache. You should set * the boolean "concurrentOrDefault" to true or false for change the cache used. You can set the memory cache capacity.For the Concurrent tile * cache you also can change the concurrency level. The test performs up to 400 request with an increased number of threads from 1 to 64 and * calculate the throughput after running the test 10 times. For every run the test execute 1000 requests for the hotspot code compilation. When * you run the test it throws a ClassNotFoundException because it doesn't find the medialib accelerator but then continue working in pure java * mode. */ // @Test public double[] testwriteImageAndWatchFlag(CacheType cacheUsed, int concurrencyLevel, long memoryCacheCapacity, RenderedImage img, String path, boolean diagnostics, boolean multipleOperations) throws IOException, InterruptedException { // sets the cache and is concurrency level and diagnostics if needed switch (cacheUsed) { case SUN_TILE_CACHE: SunTileCache sunCache = new SunTileCache(); if (diagnostics) { sunCache.enableDiagnostics(); } JAI.getDefaultInstance().setTileCache(sunCache); break; case CONCURRENT_TILE_CACHE: ConcurrentTileCache cTileCache = new ConcurrentTileCache(); cTileCache.setConcurrencyLevel(concurrencyLevel); if (diagnostics) { cTileCache.enableDiagnostics(); } JAI.getDefaultInstance().setTileCache(cTileCache); break; case CONCURRENT_MULTIMAP_TILE_CACHE: ConcurrentTileCacheMultiMap cmTileCache = new ConcurrentTileCacheMultiMap(); cmTileCache.setConcurrencyLevel(concurrencyLevel); if (diagnostics) { cmTileCache.enableDiagnostics(); } JAI.getDefaultInstance().setTileCache(cmTileCache); break; } JAI.getDefaultInstance().getTileCache().setMemoryCapacity(memoryCacheCapacity); JAI.getDefaultInstance().getTileScheduler().setParallelism(10); int threadMaxNumber = (int) Math.pow(2, EXPONENT); // throughput array for storing data double[] througputArray = new double[EXPONENT + 1]; // Creation of the thread pool executor ThreadPoolExecutor pool = new ThreadPoolExecutor(threadMaxNumber, threadMaxNumber, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000000)); // Creation of a Rendered image to store the image RenderedImage image; generator = new Random(); // Thoughput array index int index = 0; TIFFImageReader reader = null; final TIFFImageReadParam param; FileImageInputStream stream_in = null; try { if (path != null) { // Instantiation of the file-reader reader = (TIFFImageReader) new TIFFImageReaderSpi().createReaderInstance(); // Instantiation of the read-params param = new TIFFImageReadParam(); final File inputFile = new File(path); // Instantiation of the imageinputstream and imageoutputstrem stream_in = new FileImageInputStream(inputFile); reader.setInput(stream_in); // Rendered image to store the image image = reader.readAsRenderedImage(0, param); } else { image = img; } // image elaboration image = ScaleDescriptor.create(image, Float.valueOf(2.0f), Float.valueOf(2.0f), Float.valueOf(0.0f), Float.valueOf(0.0f), new InterpolationNearest(), null); image_ = ScaleDescriptor.create(image, Float.valueOf(3.5f), Float.valueOf(3.5f), Float.valueOf(0.0f), Float.valueOf(0.0f), new InterpolationNearest(), null); // Saving of the tiles maximum and minimun index minTileX = image_.getMinTileX(); minTileY = image_.getMinTileY(); maxTileX = minTileX + image_.getNumXTiles(); maxTileY = minTileY + image_.getNumYTiles(); for (int s = 1; s <= threadMaxNumber; s = s * 2) { // latch for wait the completion of all threads latch = new CountDownLatch(s); // setting of the maximum number of request to do if (s <= 4) { // at the first run one starting thread performs 1000 request // allowing the hotspot // to compile the thread instructions if (s == 1) { latch = new CountDownLatch(2); maxRequestPerThread = STARTING_REQUEST_PER_FIRST_THREAD; Worker firstThread = new Worker(multipleOperations); WeigherPeriodic secondThread = new WeigherPeriodic(cacheUsed); pool.execute(firstThread); pool.execute(secondThread); latch.await(); LOGGER.log(Level.INFO, "Starting Thread Executed"); latch = new CountDownLatch(s); } maxRequestPerThread = DEFAULT_MAX_REQUEST_PER_THREAD; } else { // the total request number can grow until 400 then doesn't // change maxRequestPerThread = 4 * DEFAULT_MAX_REQUEST_PER_THREAD / s; // maxRequestPerThread = DEFAULT_MAX_REQUEST_PER_THREAD; } // number of current threads int count = 1; long startTime = System.nanoTime(); // generation of the threads while (count <= s) { // random tile selection final Worker prefetch = new Worker(multipleOperations); // retrieving the task to the executor pool.execute(prefetch); count++; } latch.await(); // the throughput is calculated as number of total request/ total // time(in seconds) double time = (System.nanoTime() - startTime) * (1E-9); througputArray[index] = (maxRequestPerThread * s) / time; LOGGER.log(Level.INFO, "Number of Threads: " + s); index++; } } finally { /* * All the readers, writers, and stream are closed even if the program throws an exception */ try { if (stream_in != null) { stream_in.flush(); stream_in.close(); } } catch (Throwable t) { // } try { if (reader != null) { reader.dispose(); } } catch (Throwable t) { // } } // executor termination pool.shutdown(); pool.awaitTermination(180, TimeUnit.SECONDS); return througputArray; } static public void main(String[] args) throws Exception { // initial settings // check if using the concurrent cache or default CacheType cacheUsed = CacheType.CONCURRENT_MULTIMAP_TILE_CACHE; // sets the concurrency level int concurrencyLevel = DEFAULT_CONCURRENCY_LEVEL; // sets the memory cache capacity long memoryCacheCapacity = DEFAULT_MEMORY_CAPACITY; // choice of using a synthetic or a real image boolean syntheticImage = false; // diagnostics boolean diagnosticEnabled = DEFAULT_DIAGNOSTICS; // multiple tiles operations boolean multipleOp = DEFAULT_MULTIOPERATIONS; // loaded data RenderedImage imageSynth = getSynthetic(1); String path = null; boolean mainData = args != null; if (mainData) { if (args.length > 0) { cacheUsed = CacheType.values()[Integer.parseInt(args[0])]; if (cacheUsed != CacheType.SUN_TILE_CACHE) { concurrencyLevel = Integer.parseInt(args[1]); memoryCacheCapacity = Long.parseLong(args[2]); diagnosticEnabled = Boolean.parseBoolean(args[3]); multipleOp = Boolean.parseBoolean(args[4]); syntheticImage = Boolean.parseBoolean(args[5]); if (syntheticImage) { imageSynth = getSynthetic(1); } else { path = args[6]; } } else { memoryCacheCapacity = Long.parseLong(args[1]); diagnosticEnabled = Boolean.parseBoolean(args[2]); multipleOp = Boolean.parseBoolean(args[3]); syntheticImage = Boolean.parseBoolean(args[4]); if (syntheticImage) { imageSynth = getSynthetic(1); } else { path = args[5]; } } } } // setting the logger LOGGER.setLevel(Level.FINE); FileHandler fileTxt = new FileHandler( "target/ConcurrentCacheTestLog.txt"); // Log Handler no used for now // LOGGER.addHandler(fileTxt); // number of tests to do int numTest = 10; // array for storing the different throughput double[][] dataTest = new double[numTest][EXPONENT + 1]; for (int f = 0; f < numTest; f++) { LOGGER.log(Level.INFO, "Test N�." + (f + 1)); dataTest[f] = new ConcurrentCacheTest().testwriteImageAndWatchFlag(cacheUsed, concurrencyLevel, memoryCacheCapacity, imageSynth, path, diagnosticEnabled, multipleOp); } // showing the result String stringConcurrent = " with " + cacheUsed.tileCacheString; if (cacheUsed != CacheType.SUN_TILE_CACHE) { stringConcurrent += " and " + concurrencyLevel + " segments"; } for (int f = 0; f < numTest; f++) { for (int g = 0; g < dataTest[f].length; g++) { LOGGER.log(Level.INFO, "Throughput in test " + (f + 1) + " for " + (int) (Math.pow(2, g)) + " threads is " + (int) dataTest[f][g] + " requests/second" + stringConcurrent); } } } private class Worker implements Runnable { private boolean multipleTestOperations; private Worker(boolean multipleTestOperations) { this.multipleTestOperations = multipleTestOperations; } // @Override public void run() { if (!multipleTestOperations) { int i = 0; while (i < maxRequestPerThread) { double dataX = generator.nextDouble(); double dataY = generator.nextDouble(); final int tilex = (int) (dataX * (maxTileX - minTileX) + minTileX); final int tiley = (int) (dataY * (maxTileY - minTileY) + minTileY); image_.getTile(tilex, tiley); i++; } JAI.getDefaultInstance().getTileCache().removeTiles(image_); latch.countDown(); } else { JAI.getDefaultInstance().getTileCache().getTiles(image_); JAI.getDefaultInstance().getTileCache().removeTiles(image_); latch.countDown(); } } } /** This Runnable is used for checking the cache weigh on runtime */ private class WeigherPeriodic implements Runnable { private CacheType typeCache; private WeigherPeriodic(CacheType cacheUsed) { this.typeCache = cacheUsed; } // @Override public void run() { int i = 0; while (i < 10) { // get the current cache TileCache cache = JAI.getDefaultInstance().getTileCache(); long memory = 0; switch (typeCache) { // select the tile cache type, for the last two types no memory usage is // calculated case SUN_TILE_CACHE: SunTileCache sunCache = (SunTileCache) cache; memory = sunCache.getCacheMemoryUsed(); break; case CONCURRENT_MULTIMAP_TILE_CACHE: ConcurrentTileCacheMultiMap cmTileCache = (ConcurrentTileCacheMultiMap) cache; memory = cmTileCache.getCacheMemoryUsed(); break; } LOGGER.log(Level.INFO, "Current Memory Used: " + memory / (1024) + " Kb"); i++; } latch.countDown(); } } // simple method for creating a synthetic grayscale image public static RenderedImage getSynthetic(final double maximum) { final float width = 1000; final float height = 1000; ParameterBlock pb = new ParameterBlock(); pb.add(width); pb.add(height); pb.add(new Integer[] { 1 }); // Create the constant operation. return JAI.create("constant", pb); } @Test public void emptyTest() { } }