package org.geowebcache.storage; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.verify; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; import org.geowebcache.grid.BoundingBox; import org.geowebcache.grid.GridSet; import org.geowebcache.grid.GridSetBroker; import org.geowebcache.grid.GridSetFactory; import org.geowebcache.grid.GridSubset; import org.geowebcache.grid.GridSubsetFactory; import org.geowebcache.grid.SRS; import org.geowebcache.mime.MimeType; import org.springframework.util.StopWatch; public class TileRangeIteratorTest extends TestCase { private MimeType mimeType; private String parameters; private GridSet gridSet; private GridSubset gridSubSet; private long[][] gridCoverages; /** * If non null, {@link #traverseTileRangeIter(int, long[][], int, int, int[])} will create a * {@link DiscontinuousTileRange} instead of a simple {@link TileRange} */ private RasterMask rasterMask; @Override public void setUp() throws Exception { mimeType = MimeType.createFromFormat("image/png"); parameters = null; gridSet = new GridSetBroker(true, false).WORLD_EPSG3857; BoundingBox extent = new BoundingBox(0, 0, 100, 100); boolean alignTopLeft = false; int levels = 12; Double metersPerUnit = Double.valueOf(1); double pixelSize = 1; int tileWidth = 100; int tileHeight = 100; boolean yCoordinateFirst = false; gridSet = GridSetFactory.createGridSet("TestGridSet", SRS.getSRS(100000), extent, alignTopLeft, levels, metersPerUnit, pixelSize, tileWidth, tileHeight, yCoordinateFirst); gridSubSet = GridSubsetFactory.createGridSubSet(gridSet); gridCoverages = gridSubSet.getCoverages(); } /** * */ public void testTraverseIndividualZoomLevelsNoMetaTiling() throws Exception { int zoomStart = gridSubSet.getZoomStart(); int zoomStop = gridSubSet.getZoomStop(); int[] metaTilingFactors = { 1, 1 }; for (int zLevel = zoomStart; zLevel <= zoomStop; zLevel++) { long tilesProcessed = traverseTileRangeIter(1, gridCoverages, zLevel, zLevel, metaTilingFactors); long expected = countMetaTiles(gridCoverages, zLevel, zLevel, metaTilingFactors); assertEquals("Expected tile count mismatch at zoom level " + zLevel, expected, tilesProcessed); } } /** * */ public void testTraverseIndividualZoomLevelsNoMetaTilingMultiThreading() throws Exception { int zoomStart = gridSubSet.getZoomStart(); int zoomStop = gridSubSet.getZoomStop(); int[] metaTilingFactors = { 1, 1 }; final int nThreads = 64; StopWatch sw = new StopWatch(); sw.start(); //for (int zLevel = zoomStart; zLevel <= zoomStop; zLevel++) { long tilesProcessed = traverseTileRangeIter(nThreads, gridCoverages, zoomStart, zoomStop, metaTilingFactors); long expected = countMetaTiles(gridCoverages, zoomStart, zoomStop, metaTilingFactors); assertEquals("Expected tile count mismatch at zoom level " + zoomStart, expected, tilesProcessed); //} sw.stop(); System.err.println(nThreads + " threads finished in " + sw.getTotalTimeMillis() + " to count " + expected); } /** * */ public void testTraverseIndividualZoomLevelsMetaTiling() throws Exception { int zoomStart = gridSubSet.getZoomStart(); int zoomStop = gridSubSet.getZoomStop(); int[] metaTilingFactors = { 3, 3 }; for (int zLevel = zoomStart; zLevel <= zoomStop; zLevel++) { long tilesProcessed = traverseTileRangeIter(1, gridCoverages, zLevel, zLevel, metaTilingFactors); long expected = countMetaTiles(gridCoverages, zLevel, zLevel, metaTilingFactors); assertEquals("Expected tile count mismatch at zoom level " + zLevel, expected, tilesProcessed); } } /** * */ public void testWholeRangeMultiThreaded() throws Exception { int zoomStart = gridSubSet.getZoomStart(); int zoomStop = gridSubSet.getZoomStop(); int[] metaTilingFactors = { 1, 1 }; int nThreads = 32; long tilesProcessed = traverseTileRangeIter(nThreads, gridCoverages, zoomStart, zoomStop, metaTilingFactors); long expected = countMetaTiles(gridCoverages, zoomStart, zoomStop, metaTilingFactors); assertEquals(expected, tilesProcessed); } /** * */ public void testWholeRangeMultiThreadedMetaTiling() throws Exception { int zoomStart = gridSubSet.getZoomStart(); int zoomStop = gridSubSet.getZoomStop(); int[] metaTilingFactors = { 3, 3 }; int nThreads = 32; long tilesProcessed = traverseTileRangeIter(nThreads, gridCoverages, zoomStart, zoomStop, metaTilingFactors); long expected = countMetaTiles(gridCoverages, zoomStart, zoomStop, metaTilingFactors); assertEquals(expected, tilesProcessed); } /** * */ public void testDiscontinuousTileRange() throws Exception { rasterMask = createMock(RasterMask.class); expect(rasterMask.getGridCoverages()).andReturn(gridCoverages); // mock up RasterMask to return TRUE only for tiles 0,0,0 and 1,1,1 expect(rasterMask.lookup(eq(0L), eq(0L), eq(0))).andReturn(Boolean.TRUE); expect(rasterMask.lookup(eq(0L), eq(0L), eq(1))).andReturn(Boolean.FALSE); expect(rasterMask.lookup(eq(1L), eq(0L), eq(1))).andReturn(Boolean.FALSE); expect(rasterMask.lookup(eq(0L), eq(1L), eq(1))).andReturn(Boolean.FALSE); expect(rasterMask.lookup(eq(1L), eq(1L), eq(1))).andReturn(Boolean.TRUE); replay(rasterMask); final int zoomStart = 0; final int zoomStop = 1; final int[] metaTilingFactors = { 1, 1 }; final int nThreads = 1; long tilesProcessed = traverseTileRangeIter(nThreads, gridCoverages, zoomStart, zoomStop, metaTilingFactors); final long expected = 2; assertEquals(expected, tilesProcessed); verify(rasterMask); } /** * @return */ private long traverseTileRangeIter(final int nThreads, final long[][] coveredGridLevels, final int zoomStart, final int zoomStop, final int[] metaTilingFactors) throws Exception { final ExecutorService executorService = Executors.newFixedThreadPool(nThreads); final TileRange tileRange; if (rasterMask == null) { tileRange = new TileRange("layer", "gridset", zoomStart, zoomStop, coveredGridLevels, mimeType, parameters); } else { tileRange = new DiscontinuousTileRange("layer", "gridset", zoomStart, zoomStop, rasterMask, mimeType, parameters); } final TileRangeIterator tri = new TileRangeIterator(tileRange, metaTilingFactors); Collection<Callable<Long>> tasks = new ArrayList<Callable<Long>>(nThreads); for (int taskN = 0; taskN < nThreads; taskN++) { tasks.add(new TileRangeIteratorConsumer(tri)); } List<Future<Long>> values = executorService.invokeAll(tasks); executorService.shutdown(); try { executorService.awaitTermination(120, TimeUnit.SECONDS); } catch (InterruptedException ie) { fail("Executor service timeout: " + ie.getMessage()); } long totalProcessed = sumValues(values); return totalProcessed; } private long sumValues(List<Future<Long>> values) throws InterruptedException, ExecutionException { long total = 0; for (Future<Long> value : values) { long countedByTask = value.get().longValue(); total += countedByTask; } return total; } /** * Simple Callable that traverses a {@link TileRangeIterator} and returns the number of grid * locations processed */ private static final class TileRangeIteratorConsumer implements Callable<Long> { private final TileRangeIterator tri; private TileRangeIteratorConsumer(TileRangeIterator tri) { this.tri = tri; } public Long call() throws Exception { long nprocessed = 0; long[] gridLoc = new long[3]; while (null != (gridLoc = tri.nextMetaGridLocation(gridLoc))) { ++nprocessed; } return Long.valueOf(nprocessed); } } private long countMetaTiles(long[][] coveredGridLevels, int startZoom, int stopZoom, int[] metaTilingFactors) { long count = 0; final int metaX = metaTilingFactors[0]; final int metaY = metaTilingFactors[1]; for (int i = startZoom; i <= stopZoom; i++) { long[] gridBounds = coveredGridLevels[i]; long boundsMinX = gridBounds[0]; long boundsMaxX = gridBounds[2]; long boundsMinY = gridBounds[1]; long boundsMaxY = gridBounds[3]; long tilesX = 1 + boundsMaxX - boundsMinX; long tilesY = 1 + boundsMaxY - boundsMinY; long metaTilesX = (long) Math.ceil(tilesX / (double) metaX); long metaTilesY = (long) Math.ceil(tilesY / (double) metaY); long thisLevelMetaTiles = metaTilesX * metaTilesY; count += thisLevelMetaTiles; } return count; } }