package org.googlecode.perftrace.stat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalListeners; import com.google.common.cache.RemovalNotification; /** * @author zhongfeng * */ public class StatMonitor { private final static Logger logger = LoggerFactory .getLogger(StatMonitor.class); private static final int DEFAULT_NUM = 1; private final static Executor LOG_EXEC = Executors .newSingleThreadExecutor(); private String serviceName; private int maxNum = DEFAULT_NUM; private LoadingCache<String, RuntimeStat> statCache; private volatile RuntimeStat currentStat; /** * 当前并发数 */ private AtomicInteger currentNum = new AtomicInteger(0); /** * @param serviceName */ public StatMonitor(String serviceName) { this(serviceName, DEFAULT_NUM); } /** * @param serviceName */ public StatMonitor(String serviceName, int maxNum) { this.serviceName = serviceName; this.maxNum = maxNum; this.statCache = initStatCache(maxNum); } private final static Logger STAT_LOGGER = LoggerFactory .getLogger("org.googlecode.statmonitor.statlog"); /** * * @param maxNum * */ private LoadingCache<String, RuntimeStat> initStatCache(int maxNum) { RemovalListener<String, RuntimeStat> removalListener = new RemovalListener<String, RuntimeStat>() { @Override public void onRemoval( RemovalNotification<String, RuntimeStat> notification) { STAT_LOGGER.info("{}", notification.getValue().getLogString()); } }; CacheLoader<String, RuntimeStat> runStatsLoader = new CacheLoader<String, RuntimeStat>() { @Override public RuntimeStat load(String key) throws Exception { return new RuntimeStat(key); } }; return CacheBuilder.newBuilder().maximumSize(maxNum).expireAfterAccess( DEFAULT_NUM, TimeUnit.SECONDS).removalListener( RemovalListeners.asynchronous(removalListener, LOG_EXEC)) .build(runStatsLoader); } public void start() { RuntimeStat runtimeStat = getCurrentRuntimeStat(); if (runtimeStat != null) { runtimeStat.start(currentNum.incrementAndGet()); setCurrentStat(runtimeStat); } } public void stop(long elapsedTime, boolean isFault) { RuntimeStat runtimeStat = getCurrentRuntimeStat(); if (runtimeStat != null) { runtimeStat .stop(elapsedTime, isFault, currentNum.decrementAndGet()); setCurrentStat(runtimeStat); } // logger.info("------------"+runtimeStat.getLogString()); } /** * @return * @throws ExecutionException */ private RuntimeStat getCurrentRuntimeStat() { //使用缓存的时间,提高效率 Date date = new Date(SystemTimer.currentTimeMillis()); String key = KeyStrategy.getKey(serviceName, date); RuntimeStat runtimeStat = null; try { runtimeStat = statCache.get(key); } catch (ExecutionException e) { logger.error("Init error", e); } return runtimeStat; } public Collection<RuntimeStat> getLatestStatArray() { return Collections.unmodifiableCollection(getStatCache().asMap() .values()); } public static class KeyStrategy { /** * 格式:精确到秒 */ private final static String DATE_FORMAT_PATTERN = "yyyy-MM-dd-HH:mm:ss"; /** * SimpleDateFormat非线程安全 */ private static ThreadLocal<SimpleDateFormat> TL_SDF = new ThreadLocal<SimpleDateFormat>() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(DATE_FORMAT_PATTERN); } }; public static String getKey(String tag, Date date) { return Joiner.on("|").join( Arrays.asList(tag, TL_SDF.get().format(date))); } } /** * 时间缓存 * * @author zhongfeng * */ public static class SystemTimer { private final static ScheduledExecutorService executor = Executors .newSingleThreadScheduledExecutor(); private static final long tickUnit = Long.parseLong(System.getProperty( "notify.systimer.tick", "50")); static { executor.scheduleAtFixedRate(new TimerTicker(), tickUnit, tickUnit, TimeUnit.MILLISECONDS); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { executor.shutdown(); } }); } private static volatile long time = System.currentTimeMillis(); private static class TimerTicker implements Runnable { public void run() { time = System.currentTimeMillis(); } } public static long currentTimeMillis() { return time; } } public int getMaxNum() { return maxNum; } public void setMaxNum(int maxNum) { this.maxNum = maxNum; } public String getServiceName() { return serviceName; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public LoadingCache<String, RuntimeStat> getStatCache() { return statCache; } public void setStatCache(LoadingCache<String, RuntimeStat> statCache) { this.statCache = statCache; } public RuntimeStat getCurrentStat() { return currentStat; } public void setCurrentStat(RuntimeStat currentStat) { this.currentStat = currentStat; } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); final StatMonitor statMgr = new StatMonitor("TEST"); for (int i = 0; i < 100; i++) { exec.execute(new Runnable() { @Override public void run() { while (true) { statMgr.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } statMgr.stop(1000, false); } } }); } exec.awaitTermination(1, TimeUnit.DAYS); } }