package org.radargun.stages.monitor; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.radargun.DistStageAck; import org.radargun.config.Property; import org.radargun.config.Stage; import org.radargun.stages.AbstractDistStage; import org.radargun.state.ServiceListener; import org.radargun.utils.TimeConverter; import org.radargun.utils.Utils; /** * // TODO: Allow heap dumps of remote (server) process, too. * * @author Radim Vansa <rvansa@redhat.com> */ @Stage(doc = "Periodically generates heap dumps.") public class PeriodicHeapDumpStage extends AbstractDistStage { protected static final String CLEANUP = PeriodicHeapDumpStage.class.getSimpleName() + "_CLEANUP"; protected static final String FUTURE = PeriodicHeapDumpStage.class.getSimpleName() + "_FUTURE"; @Property(doc = "Location on disk where the heap dumps should be stored.", optional = false) protected String dir; @Property(doc = "How often should be the heap dumps created. Default is every 30 minutes.", converter = TimeConverter.class) protected long period = 1800000; @Property(doc = "Delay before the first heap dump. Default is 0.", converter = TimeConverter.class) protected long initialDelay = 0; @Property(doc = "Set this flag to true in order to terminate the heap dumper. Default is false.") protected boolean stop = false; @Override public DistStageAck executeOnSlave() { ScheduledFuture<?> future = (ScheduledFuture<?>) slaveState.get(FUTURE); if (stop) { if (future == null) { return errorResponse("Heap dumps have not been scheduled!"); } future.cancel(false); slaveState.remove(FUTURE); Cleanup cleanup = (Cleanup) slaveState.get(CLEANUP); if (cleanup == null) { return errorResponse("Cleanup procedure was not found!"); } cleanup.serviceDestroyed(); slaveState.removeListener(cleanup); slaveState.remove(CLEANUP); } else { if (future != null) { return errorResponse("Periodic heap dumps are already running!"); } File dir = new File(this.dir); if (!dir.exists()) { if (!dir.mkdirs()) { log.error("Failed to create directory " + this.dir); } } else if (!dir.isDirectory()) { return errorResponse("Path specified for heap dumps is not a directory"); } ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { File heapDumpFile = new File(PeriodicHeapDumpStage.this.dir, slaveState.getConfigName() + "." + slaveState.getSlaveIndex() + "." + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".hprof"); log.info("Dumping heap into " + heapDumpFile.getAbsolutePath()); Utils.dumpHeap(heapDumpFile.getAbsolutePath()); log.info("Successfully written heap dump."); } catch (Exception e) { log.error("Cannot write heap dump!", e); } } }, initialDelay, period, TimeUnit.MILLISECONDS); slaveState.put(FUTURE, future); Cleanup cleanup = new Cleanup(executor); slaveState.addListener(cleanup); slaveState.put(CLEANUP, cleanup); } return successfulResponse(); } protected static class Cleanup implements ServiceListener{ private final ScheduledThreadPoolExecutor executor; public Cleanup(ScheduledThreadPoolExecutor executor) { this.executor = executor; } @Override public void serviceDestroyed() { executor.shutdown(); } } }