001package org.javasimon; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import java.util.concurrent.*; 007 008/** 009 * This class implements periodical removing of old incremental Simons for specified Manager. 010 * Unused incremental samples can cause memory leaks and performance degradation and should be purged regularly. 011 * 012 * Purger can be in one of two states: stopped or started. When an instance of the class is create it is in the 013 * stopped stated. Method {@link IncrementalSimonsPurger#start(long, java.util.concurrent.TimeUnit)} 014 * changes state of the class to started. In this state periodical Simons purging is performed. To stop 015 * purging one need to call {@link IncrementalSimonsPurger#cancel()}. This method will change current state of the object 016 * to stopped. All other state transitions except from stopped to started or from started to stopped are forbidden. 017 * 018 * This class is thread safe. 019 * 020 * Here is a code example of how to configure {@link org.javasimon.IncrementalSimonsPurger} to clean all outdated incremental 021 * Simons once a day for default manager: 022 * <pre> 023 * {@code 024 * Manager manager = SimonManager.manager(); 025 * IncrementalSimonsPurger incrementalSimonsPurger = new IncrementalSimonsPurger(manager); 026 * incrementalSimonsPurger.start(1, TimeUnit.DAYS); 027 * } 028 * </pre> 029 * 030 * @author <a href="mailto:ivan.mushketyk@gmail.com">Ivan Mushketyk</a> 031 */ 032public final class IncrementalSimonsPurger { 033 034 /** Manager where old incremental Simons will be purged */ 035 private final Manager manager; 036 037 /** Scheduled executor service that should periodically execute purging task */ 038 private final ScheduledExecutorService executorService; 039 040 /** Currently started purging task */ 041 private ScheduledFuture<?> scheduledFuture; 042 043 /** 044 * Create Simons purger for the specified Manager. 045 * 046 * @param manager manager for which old incremental Simons will be purged 047 */ 048 public IncrementalSimonsPurger(Manager manager) { 049 this(manager, createExecutorService()); 050 } 051 052 IncrementalSimonsPurger(Manager manager, ScheduledExecutorService executorService) { 053 this.manager = manager; 054 this.executorService = executorService; 055 } 056 057 private static ScheduledExecutorService createExecutorService() { 058 ThreadFactory threadFactory = new DaemonThreadFactory(); 059 return Executors.newSingleThreadScheduledExecutor(threadFactory); 060 } 061 062 /** 063 * Start periodical Simons purging with the specified period. 064 * 065 * @param period duration of purging period 066 * @param timeUnit time unit of period duration 067 */ 068 public synchronized void start(long period, TimeUnit timeUnit) { 069 if (scheduledFuture == null) { 070 PurgerRunnable runnable = new PurgerRunnable(manager); 071 scheduledFuture = executorService.scheduleWithFixedDelay(runnable, period, period, timeUnit); 072 } else { 073 throw new IllegalStateException("IncrementSimonPurger has already been started"); 074 } 075 } 076 077 /** 078 * Cancel periodical Simons purging if it was started. 079 */ 080 public synchronized void cancel() { 081 if (scheduledFuture != null) { 082 scheduledFuture.cancel(false); 083 scheduledFuture = null; 084 } else { 085 throw new IllegalStateException("IncrementSimonPurger is either cancelled or was not started"); 086 } 087 } 088 089 /** 090 * Task submitted to scheduled executor. It is periodically executed to remove 091 * old incremental Simons. 092 */ 093 static class PurgerRunnable implements Runnable { 094 095 private static final Logger logger = LoggerFactory.getLogger(PurgerRunnable.class); 096 097 private Manager manager; 098 private long periodStartMs; 099 100 public PurgerRunnable(Manager manager) { 101 this(manager, manager.milliTime()); 102 } 103 104 PurgerRunnable(Manager manager, long periodStartMs) { 105 this.manager = manager; 106 this.periodStartMs = periodStartMs; 107 } 108 109 @Override 110 public void run() { 111 logger.debug("Purging old incremental Simons"); 112 if (manager instanceof EnabledManager) { 113 ((EnabledManager) manager).purgeIncrementalSimonsOlderThan(periodStartMs); 114 } else if (manager instanceof SwitchingManager) { 115 ((SwitchingManager) manager).purgeIncrementalSimonsOlderThan(periodStartMs); 116 } 117 periodStartMs = manager.milliTime(); 118 } 119 120 Manager getManager() { 121 return manager; 122 } 123 } 124 125 /** 126 * Thread factory that creates daemon thread for each Runnable. Using this factory 127 * for executor service will not prevent application stopping. 128 */ 129 static class DaemonThreadFactory implements ThreadFactory { 130 131 private int threadNumber; 132 133 @Override 134 public synchronized Thread newThread(Runnable runnable) { 135 Thread daemonThread = new Thread(runnable); 136 daemonThread.setDaemon(true); 137 daemonThread.setName("javasimon-simonsPurger-" + (++threadNumber)); 138 return daemonThread; 139 } 140 } 141}