場景:我們在系統運行中,需要監控某個代碼段的運行時間,我們完全可以使用currentTimeMillis來做,但是做起來比較麻煩,尤其是需要階段監控的時候,那么這個工具就出現啦~~~
先說下想要實現的功能:1.能夠對代碼段進行運行時間的監控,比如代碼行a->代碼行b的運行時間。2.能夠監控代碼行嵌套的運行時間監控,比如a->b->c->d中a->d和b->c的運行時間監控(類似括號一樣,形成配對的方式)。3.能夠在運行范圍內的jvm一些指標的監控,比如內存使用量等。
/** * 性能相關的調試工具,支持,線程正式場景,做運行時間的profile,運行性能監控(不建議線上使用,因為需要開啟監控線程) * 該工具不會拋出任何異常 * @author Administrator * @version $Id: ProfileUtils.java, v 0.1 2016年9月5日 下午11:02:45 Administrator Exp $ */ public class Profiler { /** debug模式 */ // private static volatile boolean debug = false; private final static String LOG_TEMPLATE = "[messag=%s][startTime=%s][endTime=%s][durationTime=%sms][processors=%s][memUse=%s]"; private final static String SIMPLE_LOG_TEMPLATE = "[durationTime=%sms][message=%s]"; private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss"); /** profile日志,建議運行中別做修改,否則有些配置會導致殘留線程 */ private static ThreadLocal<ProfileConfig> configHolder = new ThreadLocal<ProfileConfig>() { protected ProfileConfig initialValue() { return new ProfileConfig( false, false, 0); }; }; /** 開始monitor的時間 */ private static ThreadLocal<Stack<MonitorResource>> resStackHolder = new ThreadLocal<Stack<MonitorResource>>() { protected java.util.Stack<MonitorResource> initialValue() { return new Stack<MonitorResource>(); }; }; /** 監控線程 */ private static ThreadLocal<MonitorThread> monitorThreadHolder = new ThreadLocal<MonitorThread>(); /** * 開始monitor */ public static void enter(Object msgObj) { try { Stack<MonitorResource> monitorResStack = resStackHolder.get(); monitorResStack.push(new MonitorResource(msgObj, System.currentTimeMillis())); ProfileConfig config = configHolder.get(); //開啟監控線程 if (config.isUseMonitorThread()) { if (monitorThreadHolder.get() != null) { killThread(); } MonitorThread monitorThread = new MonitorThread(getCurrentMonitorRes(), config); monitorThreadHolder.set(monitorThread); monitorThread.start(); } } catch (Throwable e) { // if (debug) { // e.printStackTrace(); // } return; } } /** * 結束monitor * @return */ public static MonitorResource release() { try { Stack<MonitorResource> monitorResStack = resStackHolder.get(); MonitorResource monitorResource = getCurrentMonitorRes(); monitorResource.setEndTime(System.currentTimeMillis()); ProfileConfig config = configHolder.get(); //監控線程關閉 if (config.isUseMonitorThread()) { killThread(); } return monitorResStack.pop(); } catch (Throwable e) { // if (debug) { // e.printStackTrace(); // } return new MonitorResource(e.getMessage(), 0); } } /** * 使用新的messageObj替換原來的 * @param messageObj * @return */ public static MonitorResource release(Object messageObj) { MonitorResource monitorResource = release(); monitorResource.setMessageObj(messageObj); return monitorResource; } /** * 結束monitor並且打印日志 * @param logger * @return */ public static MonitorResource releaseAndLog(Logger logger, Object messageObj) { MonitorResource resource = release(messageObj); LoggerUtils.info(logger, resource); return resource; } /** * 結束monitor並且打印日志 * @param logger * @return */ public static MonitorResource releaseAndLog(Logger logger) { MonitorResource resource = release(); LoggerUtils.info(logger, resource); return resource; } /** * 設置profile配置 * @param config */ public static void setProfileConfig(ProfileConfig config) { configHolder.set(config); } /** * Setter method for property <tt>debug</tt>. * * @param debug value to be assigned to property debug */ // public static void setDebug(boolean debug) { // Profiler.debug = debug; // } /** * 移除監控線程 */ private static void killThread() { try { MonitorThread futureTask = monitorThreadHolder.get(); monitorThreadHolder.remove(); futureTask.interrupt(); } catch (Throwable e) { // ignore // if (debug) { // e.printStackTrace(); // } } } /** * 獲取當前的monitorRes * @return */ public static MonitorResource getCurrentMonitorRes() { try { Stack<MonitorResource> resStack = resStackHolder.get(); return resStack.get(resStack.size() - 1); } catch (Exception e) { // if (debug) { // e.printStackTrace(); // } return new MonitorResource(e.getMessage(), 0); } } /** * 資源使用情況,比如cpu最大使用量等。 * @author Administrator * @version $Id: Profile.java, v 0.1 2016年9月5日 下午11:38:39 Administrator Exp $ */ public static class MonitorResource { /** 當前資源的標志 */ private Object messageObj = null; private long startTime = 0; private long endTime = 0; private int processorNums = 0; private List<Long> memUse = Lists.newArrayList(); /** * @param messageObj * @param startTime */ public MonitorResource(Object messageObj, long startTime) { super(); this.messageObj = messageObj; this.startTime = startTime; } /** * Setter method for property <tt>messageObj</tt>. * * @param messageObj value to be assigned to property messageObj */ public void setMessageObj(Object messageObj) { this.messageObj = messageObj; } public String getMemUse() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < memUse.size(); i++) { stringBuilder.append(memUse.get(i) / 1024L + "K"); if (i != memUse.size() - 1) { stringBuilder.append(","); } } return stringBuilder.toString(); } /** * 獲取整個profile堆棧 * @return */ public Stack<MonitorResource> getMonitorResStack() { return resStackHolder.get(); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return configHolder.get().isUseSimpleLogTemplate() ? (String.format(SIMPLE_LOG_TEMPLATE, endTime - startTime, messageObj)) : (String.format(LOG_TEMPLATE, messageObj, DATE_FORMAT.format(new Date(startTime)), DATE_FORMAT.format(new Date(endTime)), endTime - startTime, processorNums, getMemUse())); } /** * 獲取運行時間 * @return */ public long getDurTime() { return endTime - startTime; } public void putMemUse(long l) { memUse.add(l); } /** * Setter method for property <tt>endTime</tt>. * * @param endTime value to be assigned to property endTime */ public void setEndTime(long endTime) { this.endTime = endTime; } /** * Getter method for property <tt>messageObj</tt>. * * @return property value of messageObj */ public Object getMessageObj() { return messageObj; } /** * Setter method for property <tt>processorNums</tt>. * * @param processorNums value to be assigned to property processorNums */ public void setProcessorNums(int processorNums) { this.processorNums = processorNums; } } public static class ProfileConfig { private boolean useSimpleLogTemplate = false; private boolean useMonitorThread = false; private int monitorCollectDurTime = 500; /** * @param useSimpleLogTemplate * @param useMonitorThread * @param monitorCollectDurTime */ public ProfileConfig(boolean useSimpleLogTemplate, boolean useMonitorThread, int monitorCollectDurTime) { super(); this.useSimpleLogTemplate = useSimpleLogTemplate; this.useMonitorThread = useMonitorThread; this.monitorCollectDurTime = monitorCollectDurTime; } /** * Getter method for property <tt>useSimpleLogTemplate</tt>. * * @return property value of useSimpleLogTemplate */ public boolean isUseSimpleLogTemplate() { return useSimpleLogTemplate; } /** * Setter method for property <tt>useSimpleLogTemplate</tt>. * * @param useSimpleLogTemplate value to be assigned to property useSimpleLogTemplate */ public void setUseSimpleLogTemplate(boolean useSimpleLogTemplate) { this.useSimpleLogTemplate = useSimpleLogTemplate; } /** * Getter method for property <tt>useMonitorThread</tt>. * * @return property value of useMonitorThread */ public boolean isUseMonitorThread() { return useMonitorThread; } /** * Setter method for property <tt>useMonitorThread</tt>. * * @param useMonitorThread value to be assigned to property useMonitorThread */ public void setUseMonitorThread(boolean useMonitorThread) { this.useMonitorThread = useMonitorThread; } /** * Getter method for property <tt>monitorCollectDurTime</tt>. * * @return property value of monitorCollectDurTime */ public int getMonitorCollectDurTime() { return monitorCollectDurTime; } /** * Setter method for property <tt>monitorCollectDurTime</tt>. * * @param monitorCollectDurTime value to be assigned to property monitorCollectDurTime */ public void setMonitorCollectDurTime(int monitorCollectDurTime) { this.monitorCollectDurTime = monitorCollectDurTime; } } private static class MonitorThread extends Thread { private static final AtomicLong threadCount = new AtomicLong(); private MonitorResource monitorResource; private final ProfileConfig config; /** * */ public MonitorThread(MonitorResource resource, ProfileConfig config) { monitorResource = resource; setName("monitor-thread-" + threadCount.getAndIncrement()); setDaemon(true); this.config = config; } /** * @see java.lang.Thread#run() */ @Override public void run() { monitorResource.setProcessorNums(Runtime.getRuntime().availableProcessors()); while (true) { monitorResource.putMemUse( Runtime.getRuntime().maxMemory() - Runtime.getRuntime().freeMemory()); try { Thread.sleep(config.getMonitorCollectDurTime()); } catch (InterruptedException e) { // if (debug) { // e.printStackTrace(); // } return; } } } } }
可以看到,我們有個監控資源的概念,每個階段都對應一個監控資源,比如a->d和b->c都對應了一個監控資源。從實現上,每次進入監控的時候,會生成一個監控資源,並且記錄當前時間,並且把該監控資源壓棧。同時,還會啟動一個監控線程,將jvm的狀態不斷寫入該監控資源中,所以針對a->d和b->c都生成了自己的監控線程,並且吧jvm狀態寫入自己的監控資源中。 當release的時候,會kill掉監控線程,並且把監控資源出棧。
根據上面的設計可以看到,該工具的使用需要注意下面的問題:
1.如果不啟用監控線程,那么可以用於線上的場景。
2.如果啟用了監控線程,那么只適合debug分析的場景,因為如果是線上,當監控的代碼塊並發量大起來的時候,會以1:1的比例創建監控線程,這個時候會有風險。
3.啟用監控線程的場景比較適合於,並發量較小(創建的監控線程少),但是執行時間長的場景。這個時候可以對代碼塊進行執行分析。
