/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2010-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.test.stress; import java.awt.image.RenderedImage; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LogRecord; import org.opengis.coverage.grid.GridEnvelope; import org.apache.sis.math.Statistics; import org.geotoolkit.coverage.grid.GeneralGridGeometry; import org.apache.sis.util.CharSequences; import org.apache.sis.util.logging.Logging; import org.geotoolkit.util.logging.LogProducer; import org.apache.sis.util.logging.PerformanceLevel; /** * Base class for stressors. * * @author Martin Desruisseaux (Geomatys) * @version 3.16 * * @since 3.14 */ public abstract class Stressor extends RequestGenerator implements Callable<Statistics>, LogProducer { /** * The logger to use for reporting information. */ protected static final Logger LOGGER = Logging.getLogger("org.geotoolkit.test.stress"); /** * The length (in characters) of the thread field. */ private static final int THREAD_NAME_FIELD_LENGTH = 9; /** * The length (in characters) of the time field (in milliseconds). */ private static final int TIME_FIELD_LENGTH = 5; /** * The length (in characters) of a single integer number for the grid size. */ private static final int GRID_SIZE_FIELD_LENGTH = 5; /** * The maximal pause to wait between two queries, in milliseconds. */ private static final int MAXIMAL_PAUSE = 250; /** * The time (in nanoseconds) when to stop the test. This is set by {@link StressorGroup} * when a stress is started, and set to the actual stop time when the stress is finished. */ long stopTime; /** * The name of the thread that executed the task. */ String threadName; /** * The level to use for logging message. If {@code null}, then the level shall * be selected by {@link PerformanceLevel#forDuration(long, TimeUnit)}. */ private Level logLevel; /** * The image viewer, or {@code null} if none. */ ImageViewer viewer; /** * Creates a new stressor. * * @param domain Contains the maximal extent of the random envelopes to be generated. */ protected Stressor(final GeneralGridGeometry domain) { super(domain); } /** * Returns {@code true} if logging is enabled. */ private boolean isLoggable() { Level level = logLevel; if (level == null) { level = PerformanceLevel.SLOWEST; } return LOGGER.isLoggable(level); } /** * Returns the current logging level. The default value is one of the {@link PerformanceLevel} * constants, determined according the duration of the read operation. */ @Override public Level getLogLevel() { final Level level = logLevel; return (level != null) ? level : PerformanceLevel.PERFORMANCE; } /** * Sets the logging level to the given value. A {@code null} value restores * the default level documented in the {@link #getLogLevel()} method. */ @Override public void setLogLevel(Level level) { logLevel = level; } /** * Invokes {@link #executeQuery(GeneralGridGeometry)} with random grid geometries * until the execution time has been elapsed. A slight pause is performed between * each query. Note that many threads may be running concurrently, so the CPU may * still busy at 100% despite this pause. * * @return The statistics about the execution time, in milliseconds. * @throws Exception If an error occurred during the test. */ @Override public Statistics call() throws Exception { final String sourceClassName = getClass().getName(); threadName = Thread.currentThread().getName(); final Statistics statistics = new Statistics(null); final StringBuilder buffer; final int bufferBase; if (isLoggable()) { buffer = new StringBuilder("Thread ").append(threadName); buffer.append(CharSequences.spaces(THREAD_NAME_FIELD_LENGTH - buffer.length())).append(": "); bufferBase = buffer.length(); } else { buffer = null; bufferBase = 0; } long nanoTime; while ((nanoTime = System.nanoTime()) < stopTime) { /* * Execute the method to stress. */ final GeneralGridGeometry geometry = getRandomGrid(); long time = System.nanoTime(); final RenderedImage image = executeQuery(geometry); time = System.nanoTime() - time; /* * Show the result, if we are allowed to. */ if (viewer != null) { viewer.setImage(image); } /* * Compute statistics about the ellapsed time. */ final double time_ms = time / 1E+6; // To milliseconds. statistics.accept(time_ms); /* * Format how long it took to execute the request, in milliseconds and in * millseconds by megabyte. Note: it should actually be "by megapixel", * but we assume that few peoples would understand a "Mp" unit... */ if (buffer != null) { buffer.setLength(bufferBase); insertSpaces(bufferBase, buffer.append(Math.round(time_ms)), TIME_FIELD_LENGTH); buffer.append(" ms. Size=("); /* * Format the grid envelope and the geographic bounding box. */ final GridEnvelope envelope = geometry.getExtent(); final int dimension = envelope.getDimension(); for (int i=0; i<dimension; i++) { if (i != 0) { buffer.append(" \u00D7"); // Multiplication symbol } insertSpaces(buffer.length(), buffer.append(envelope.getSpan(i)), GRID_SIZE_FIELD_LENGTH); } buffer.append("), scale=").append((float) mean(getScale(geometry))); buffer.append(", ").append(geometry.getEnvelope()); /* * Log progress information and wait. */ Level level = logLevel; if (level == null) { level = PerformanceLevel.forDuration(time, TimeUnit.NANOSECONDS); } final LogRecord record = new LogRecord(level, buffer.toString()); record.setSourceClassName(sourceClassName); record.setSourceMethodName("call"); record.setLoggerName(LOGGER.getName()); LOGGER.log(record); } try { Thread.sleep(random.nextInt(MAXIMAL_PAUSE) + 1); } catch (InterruptedException e) { // Go back to work... } } stopTime = nanoTime; dispose(); return statistics; } /** * Inserts spaces at the given position in the given buffer, in order to align the values * in a field of the given length. */ private static void insertSpaces(final int pos, final StringBuilder buffer, final int length) { buffer.insert(pos, CharSequences.spaces(length - (buffer.length() - pos))); } /** * Runs a query for the given grid geometry. * * @param request The grid geometry to request. * @return An image which represent the result of the request, or {@code null} if none. * @throws Exception If an error occurred during the test. */ protected abstract RenderedImage executeQuery(GeneralGridGeometry request) throws Exception; /** * Invoked when the test is done. The default implementation does nothing. * Subclasses shall override this method if they have any resources that * need to be disposed. * * @throws Exception If an error occurred while disposing the resources. * * @since 3.15 */ protected void dispose() throws Exception { } }