diff --git a/com.creditease.uav.base/src/main/java/com/creditease/agent/spi/ActionEngine.java b/com.creditease.uav.base/src/main/java/com/creditease/agent/spi/ActionEngine.java index f89671b0..7a1324d6 100644 --- a/com.creditease.uav.base/src/main/java/com/creditease/agent/spi/ActionEngine.java +++ b/com.creditease.uav.base/src/main/java/com/creditease/agent/spi/ActionEngine.java @@ -78,7 +78,7 @@ public ActionContext execute(String actionId, ActionContext context) { } catch (Exception e) { if (log.isTraceEnable()) { - log.err(this, "Exception occured " + e.getMessage()); + log.err(this, "Exception occured " + e.getMessage(), e); } context.setE(e); context.setJumpTargetActionId(null); diff --git a/com.creditease.uav.console/src/main/webapp/apphub/js/common/dialog.js b/com.creditease.uav.console/src/main/webapp/apphub/js/common/dialog.js index 424f1cf9..50744373 100644 --- a/com.creditease.uav.console/src/main/webapp/apphub/js/common/dialog.js +++ b/com.creditease.uav.console/src/main/webapp/apphub/js/common/dialog.js @@ -59,7 +59,8 @@ function AppHubDialog(cfg) { sb.append(""); - document.body.innerHTML=sb.toString()+document.body.innerHTML; + // document.body.innerHTML=sb.toString()+document.body.innerHTML; + $(document.body).prepend(sb.toString()); // FIX miss DOM Event when rewriting 'innerHTML' directly var closeBtnElem=HtmlHelper.id(closeBtnId); @@ -130,6 +131,7 @@ function AppHubDialogManager() { }; this.open=function(id,obj) { + var tl=list.get(id); if (undefined==tl) { return; diff --git a/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/js/uav.jta.js b/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/js/uav.jta.js index df1c9cb8..4b59e97c 100644 --- a/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/js/uav.jta.js +++ b/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/js/uav.jta.js @@ -25,6 +25,7 @@ function JTATool(app) { return html; } + /* ********** jta list window ********** */ this.buildAppJTAListWnd = function(sObj) { var appInfo = { appuuid: "", @@ -39,8 +40,7 @@ function JTATool(app) { ? sObj["appid"] : sObj["appname"]; } - var html = ''; - html="
" + + var html="
" + "
" + ""+appInfo["appname"]+"
"+ ""+appInfo["appurl"]+"" + @@ -48,10 +48,10 @@ function JTATool(app) { "
"; html+="
" + - " "+ " " + - + " " + "
"; + html+="
"; return html; @@ -104,8 +104,29 @@ function JTATool(app) { 'AppJTADetailWnd', 'buildAppJTADetailWnd', 'runAppJTADetailWnd'); } + this.queryThreadAnalysisList(sObj.hostport, true); + + var dialog = { + id: 'threadAnalysisDialog', + title: '线程分析', + height: 160, // 160px + event: { + onbody: function(id){ + var html = '
' + + '

' + + '
' + + '
持续秒 间隔
' + + '
' + + '
'; + return html; + } + } + }; + window["appdialogmgr"].remove('threadAnalysisDialog'); + window["appdialogmgr"].build(dialog); } + /* ********** jta detail window ********** */ this.buildAppJTADetailWnd = function(sObj) { var html = ''; html="
" + @@ -115,8 +136,9 @@ function JTATool(app) { "
" + "
"; -// html+= "
"; -// html+="
"; +// html += "
" + +// "
" + +// "
"; html+="
"; return html; @@ -141,8 +163,10 @@ function JTATool(app) { tid: ['线程号', '10%'], percpu: ['CPU(%)', '10%'], permem: ['内存(%)', '10%'], - state: ['线程状态', '20%'], - info: ['线程信息', '50%'] + state: ['线程状态', '10%'], + info: ['线程信息', '50%'], + actionShow: ['线程栈', '10%'] +// actionQChain: ['查找等待', '10%'] }, cloHideStrategy : { 1000: [0, 1, 2, 3, 4, 5, 6, 7], @@ -167,150 +191,179 @@ function JTATool(app) { this.detailList.initTable(); - this.detailList.cellClickUser = function(id, pNode) { - var infoEl = pNode.getElementsByTagName('td')[4]; - alert(infoEl.innerHTML); - } - this.queryThreadDetail(sObj.time, sObj.ipport); } + this.showThreadAnalysisDialog = function(hostport, ip) { + window["appdialogmgr"].open('threadAnalysisDialog', {}); + } + // this.invokeThreadAnalysis = function(hostport, ip) { var apmParam = { - supporter: 'com.creditease.uav.apm.supporters.ThreadAnalysisSupporter', - method: 'captureJavaThreadAnalysis', - param: ['', (new Date().getTime()) + '', ip] - } + supporter: 'com.creditease.uav.apm.supporters.ThreadAnalysisSupporter', + method: 'captureJavaThreadAnalysis', + param: ['', (new Date().getTime()) + '', ip] + } + this.uavhm.nodeCtrl('threadanalysis', { + url: 'http://' + ip + ':10101/node/ctrl', + server: 'http://' + hostport, + user: window.parent.loginUser.userId, + actparam: JSON.stringify(apmParam) + }, function(){ + alert('分析线程成功,请稍后刷新列表'); + window["appdialogmgr"].close('threadAnalysisDialog'); + }); + } + + this.invokeMultiThreadAnalysis = function(hostport, ip){ - var req = { - intent: 'threadanalysis', - request: { - url: 'http://' + ip + ':10101/node/ctrl', - server: 'http://' + hostport, - user: window.parent.loginUser.userId, - actparam: JSON.stringify(apmParam) - } - } + var duration = $('#jta_mta_duration').val(); + var interval = $('#jta_mta_interval').val(); - AjaxHelper.call({ - url: '../../rs/godeye/node/ctrl', - data: JSON.stringify(req), - cache: false, - type: 'POST', - dataType: 'html', - timeout: 10000, - success: function(resp) { - var ret = JSON.parse(resp); - if(ret.rs == 'OK') { - alert('分析线程成功,请稍后刷新列表'); - } else { - console.log(resp); - if(ret.msg) { - alert(ret.msg); - }else{ - alert('启动分析出错'); - } + var apmParam = { + supporter: 'com.creditease.uav.apm.supporters.ThreadAnalysisSupporter', + method: 'captureJavaThreadAnalysis', + param: ['', (new Date().getTime()) + '', ip] } - }, - error: function(resp) { - console.log(resp); - alert('启动线程分析异常'); - } - }); + this.uavhm.nodeCtrl('threadanalysis', { + url: 'http://' + ip + ':10101/node/ctrl', + server: 'http://' + hostport, + user: window.parent.loginUser.userId, + actparam: JSON.stringify(apmParam), + multiple: 'true', + duration: duration, + interval: interval + }, function(ret){ + alert('分析线程成功,请稍后刷新列表'); + window["appdialogmgr"].close('threadAnalysisDialog'); + }); } // - this.queryThreadAnalysisList = function(ipport) { - - var req = { - intent: 'qDistinct', - request: { - ipport: ipport - } - } + this.queryThreadAnalysisList = function(ipport, initQuery) { var that = this; - AjaxHelper.call({ - url: '../../rs/apm/jta/q', - data: JSON.stringify(req), - cache: false, - type: 'POST', - dataType: 'html', - timeout: 30000, - success: function(resp) { - var rt = JSON.parse(resp); - if(!rt.rs || rt.rs=='ERR') { - alert('获取线程分析结果失败'); - return; - } - - if(rt.rs == 'NO_INDEX') { - alert('没有搜索到该应用线程相关内容'); - return; - } - - var data = eval(rt.rs); - that.mainList.clearTable(); - that.mainList.setTotalRow(parseInt(rt.count)); - that.mainList.renderPagination(); - that.mainList.addRows(data); - }, - error: function(resp) { - console.log('error >>> ' + resp); - alert('获取线程分析结果失败'); - } - }) + this.uavhm.query('qDistinct', {ipport: ipport}, + function(data, count){ + if(count == 0 && initQuery) { + return; + } + that.mainList.clearTable(); + that.mainList.setTotalRow(parseInt(count)); + that.mainList.renderPagination(); + that.mainList.addRows(data); + }); } // this.queryThreadDetail = function(time, ipport) { - var req = { - intent: 'qField', - request: { - stime: time + '', - etime: time + '', - ipport: ipport, - from: '0', - size: '5000', - sort: 'percpu=DESC' - } - } var that = this; - AjaxHelper.call({ - url: '../../rs/apm/jta/q', - data: JSON.stringify(req), - cache: false, - type: 'POST', - dataType: 'html', - timeout: 30000, - success: function(resp){ - var rt = JSON.parse(resp); - if(!rt.rs || rt.rs=='ERR') { - alert('查询线程分析失败'); - return; - } - - var data = eval(rt.rs); - for(var k in data) { - if(k == 'info') { - data[k] = data[k].replace(//g,">").replace(/"/g, """).replace(/'/g, "'"); - } - } - - that.detailList.clearTable(); - that.detailList.setTotalRow(parseInt(rt.count)); - that.detailList.renderPagination(); - that.detailList.addRows(data); - }, - error: function(resp){ - console.log('error >>> ' + resp); - alert('查询线程分析失败'); - } - }); + this.uavhm.query('qField', { + stime: time + '', + etime: time + '', + ipport: ipport, + from: '0', + size: '5000', + sort: 'percpu=DESC' + }, function(data, count){ + for(var k in data) { + data[k]['actionShow'] = ''; + // data[k]['actionQChain'] = ''; + } + that.detailList.clearTable(); + that.detailList.setTotalRow(parseInt(count)); + that.detailList.renderPagination(); + that.detailList.addRows(data); + + // that.findDeadlock(time, ipport); + }); + } + + this.showStacktrace = function(line){ + alert($(line).parent().prevAll('.clum4')[0].innerHTML); + } + + /* ********** jta deep analysis ********** */ +/* + this.findDeadlock = function(time, ipport) { + this.uavhm.query('findDeadlock', { + stime: time + '', + etime: time + '', + ipport: ipport, + from: '0', + size: '5000' + }, function(data, count){ + // TODO + }); + } + + this.findThreadChain = function(time, ipport, line){ + + var threadId = $(line).parent().prevAll('.clum0')[0].innerHTML; + this.uavhm.query('queryThreadChain', { + stime: time + '', + etime: time + '', + ipport: ipport, + from: '0', + size: '5000', + threadId: threadId + }, function(data, count){ + // TODO + }); } +*/ + this.uavhm = { + + queryURL: '../../rs/apm/jta/q', + nodeCtrlURL: '../../rs/godeye/node/ctrl', + + query: function(intent, params, then) { + this.call(this.queryURL, {intent: intent, request: params}, + function(ret) { + if(!ret.rs || ret.rs == 'ERR') { + alert('请求处理失败'); + return; + } + if(ret.rs == 'NO_INDEX') { + alert('没有搜索到该应用线程相关内容'); + return; + } + then(eval(ret.rs), ret.count); + }); + }, + + nodeCtrl: function(intent, params, then) { + this.call(this.nodeCtrlURL, {intent: intent, request: params}, + function(ret) { + if(ret.rs != 'OK') { + alert(ret.msg ? ret.msg: '启动分析出错'); + } else { + then(ret); + } + }); + }, + + call: function(url, req, then, fail) { + AjaxHelper.call({ + url: url, + data: JSON.stringify(req), + cache: false, + type: 'POST', + dataType: 'html', + timeout: 30000, + success: function(resp) { + then(JSON.parse(resp)); + }, + error: fail || function(o) { + console.log(o); + alert('请求失败,可能是网络异常'); + } + }); + } + } } \ No newline at end of file diff --git a/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/main.html b/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/main.html index 80ba554c..7d0282fe 100644 --- a/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/main.html +++ b/com.creditease.uav.console/src/main/webapp/uavapp_godeye/appmonitor/main.html @@ -46,6 +46,7 @@ + diff --git a/com.creditease.uav.healthmanager/src/main/java/com/creditease/uav/feature/runtimenotify/task/JudgeNotifyTask.java b/com.creditease.uav.healthmanager/src/main/java/com/creditease/uav/feature/runtimenotify/task/JudgeNotifyTask.java index e02591a8..dd972a23 100644 --- a/com.creditease.uav.healthmanager/src/main/java/com/creditease/uav/feature/runtimenotify/task/JudgeNotifyTask.java +++ b/com.creditease.uav.healthmanager/src/main/java/com/creditease/uav/feature/runtimenotify/task/JudgeNotifyTask.java @@ -197,12 +197,12 @@ private NotificationEvent newNotificationEvent(Map result) { String appgroup = this.curSlice.getMdf().getExt("appgroup"); appgroup = (appgroup == null) ? "" : appgroup; - StringBuilder description = new StringBuilder(); - StringBuffer conditionIndex = new StringBuffer(); + StringBuilder desc = new StringBuilder(); + StringBuilder conditionIndex = new StringBuilder(); for (Map.Entry cause : result.entrySet()) { // description - description.append("触发条件[" + cause.getKey() + "]:").append(cause.getValue()).append("\r\n"); + desc.append("触发条件[" + cause.getKey() + "]:").append(cause.getValue()).append("\r\n"); // condition index conditionIndex.append(" " + cause.getKey()); } @@ -210,8 +210,11 @@ private NotificationEvent newNotificationEvent(Map result) { String title = ip + "[" + this.curSlice.getKey() + "]触发" + result.size() + "个报警(条件序号:" + conditionIndex.toString() + ")"; - NotificationEvent ne = new NotificationEvent(NotificationEvent.EVENT_RT_ALERT_THRESHOLD, title, - description.toString(), curSlice.getTime(), ip, host); + // fix  (\u00A0) can be shown in email + String description = desc.toString().replace('\u00A0', ' '); + + NotificationEvent ne = new NotificationEvent(NotificationEvent.EVENT_RT_ALERT_THRESHOLD, title, description, + curSlice.getTime(), ip, host); // add appgroup ne.addArg("appgroup", appgroup); diff --git a/com.creditease.uav.monitorframework.apm/src/main/java/com/creditease/uav/apm/supporters/ThreadAnalysisSupporter.java b/com.creditease.uav.monitorframework.apm/src/main/java/com/creditease/uav/apm/supporters/ThreadAnalysisSupporter.java index 49934fd2..525c1009 100644 --- a/com.creditease.uav.monitorframework.apm/src/main/java/com/creditease/uav/apm/supporters/ThreadAnalysisSupporter.java +++ b/com.creditease.uav.monitorframework.apm/src/main/java/com/creditease/uav/apm/supporters/ThreadAnalysisSupporter.java @@ -21,8 +21,6 @@ package com.creditease.uav.apm.supporters; import java.io.File; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import com.creditease.agent.helpers.DataConvertHelper; import com.creditease.agent.helpers.DateTimeHelper; @@ -40,19 +38,17 @@ public class ThreadAnalysisSupporter extends Supporter { private UAVMonitor monitor = new UAVMonitor(logger, 60000); - // 执行时间map,key为进程号,value为执行时间;用于判断在限定时间段内不需要发起多次请求 - private volatile Map timeIntervalMap = new ConcurrentHashMap(); + private static final String SUPPORTED_METHOD = "captureJavaThreadAnalysis"; + + private static Object lock = new Object(); + private static final long FROZON_TIME_LIMIT = 1000L; + private static volatile long lastInvokeTime = System.currentTimeMillis(); private final String SYMBOL = "_"; - private int timeInterval = 60000; @Override public void start() { - // 从配置文件中获取限定时间段,没有使用默认值 - timeInterval = DataConvertHelper.toInt(System.getProperty("com.creditease.uav.threadanalysis.timeinterval"), - 60000); - } @Override @@ -64,51 +60,42 @@ public void stop() { @Override public Object run(String methodName, Object... params) { - if (!"captureJavaThreadAnalysis".equals(methodName) || null == params || params.length < 4) { - - return "ERR:METHOD NOT SUPPORT"; - } - return captureJavaThreadAnalysis(params); - } - - private Object captureJavaThreadAnalysis(Object... params) { + long now = System.currentTimeMillis(); /** - * TODO: window系统不支持线程分析功能,以后再做适配 - * + * concurrency control */ - if (JVMToolHelper.isWindows() == true) { - return "ERR:NOT SUPPORT WINDOWNS"; + long lastTime = lastInvokeTime; + lastInvokeTime = now; + if (now + FROZON_TIME_LIMIT < lastTime) { + return "ERR:BE RUNNING"; } - long stTime = System.currentTimeMillis(); - - // 获取java_home路径 - String javahome = System.getProperty("java.home"); - // 路径是否由“/”结束,没有则添加 - if (!javahome.endsWith("/")) { - javahome = javahome + "/"; - } - // 获取java_home bin的路径 - final String jdk_binpath = javahome + "../bin"; - // 如果没有获取到java_home,返回结果 - if (!IOHelper.exists(jdk_binpath)) { - return "ERR:NO JDK"; + // valid arguments + if (!SUPPORTED_METHOD.equals(methodName) || params == null || params.length < 4) { + return "ERR:ILLEGAL ARGUMENT"; } + Object ret = captureJavaThreadAnalysis(params); + + lastInvokeTime = now; + monitor.logPerf(now, "THREAD_ANALYSIS"); + return ret; + } + + private Object captureJavaThreadAnalysis(Object... params) { + // 进程号 String pid = (String) params[0]; - // 如果没有传入线程分析的进程号,取当前MOF所在程序的PID if (StringHelper.isEmpty(pid)) { + // 如果没有传入线程分析的进程号,取当前MOF所在程序的PID pid = JVMToolHelper.getCurrentProcId(); - // return "ERR:NO PID"; } - // 执行时间(从请求端获取),传递long型字符串,然后转化成long型 - Long exectime = DataConvertHelper.toLong(params[1], -1); - // 如果时间值不对,则取MOF所在系统的当前时间 - if (-1 == exectime) { - exectime = System.currentTimeMillis(); - // return "ERR:EXECTIME ERROR"; + + // 执行时间戳(从请求端获取) + long execTime = DataConvertHelper.toLong(params[1], -1); + if (execTime == -1) { + execTime = System.currentTimeMillis(); } // IP地址如果没传,则获取MOF所在系统的IP @@ -116,8 +103,6 @@ private Object captureJavaThreadAnalysis(Object... params) { if (StringHelper.isEmpty(ip)) { ip = NetworkHelper.getLocalIP(); } - // 端口号 - String port = UAVServer.instance().getServerInfo(CaptureConstants.INFO_APPSERVER_LISTEN_PORT) + ""; // 文件路径 String fileBase = (String) params[3]; @@ -125,32 +110,45 @@ private Object captureJavaThreadAnalysis(Object... params) { return "ERR:NO STORE FILE BASE"; } - // 并发控制 - if (!controlConcurrency(pid, exectime)) { - return "ERR:IS RUNNING"; - } + // 端口号 + String port = UAVServer.instance().getServerInfo(CaptureConstants.INFO_APPSERVER_LISTEN_PORT) + ""; - // 生成线程分析文件即将开始运行,在日志中记录开始运行记录 - if (logger.isDebugable()) { - logger.debug("RUN Java Thread Analysis START: pid=" + pid, null); + String jdkBinPath = getJdkBinPath(); + if (!IOHelper.exists(jdkBinPath)) { + // 如果没有获取到java_home,返回结果 + return "ERR:NO JDK"; } if (!checkDirPermission(fileBase)) { return "ERR:FILE PERMISSION DENIED"; } - String dateTime = DateTimeHelper.toFormat("yyyy-MM-dd_HH-mm-ss.SSS", exectime); + if (JVMToolHelper.isWindows()) { + /** + * TODO: window系统不支持线程分析功能,以后再做适配 + */ + return "ERR:NOT SUPPORT WINDOWNS"; + } + + // 生成线程分析文件即将开始运行,在日志中记录开始运行记录 + if (logger.isDebugable()) { + logger.debug("RUN Java Thread Analysis START: pid=" + pid, null); + } + + String dateTime = DateTimeHelper.toFormat("yyyy-MM-dd_HH-mm-ss.SSS", execTime); // 规定线程分析结果文件名 String name = ip + SYMBOL + port + SYMBOL + dateTime + ".log"; String file = fileBase + "/" + name; - + // 生成线程分析结果文件需要执行的命令 - String cmd = " top -Hp " + pid + " bn 1 > " + file + " && echo '=====' >> " + file + " && " + jdk_binpath + String cmd = " top -Hp " + pid + " bn 1 > " + file + " && echo '=====' >> " + file + " && " + jdkBinPath + "/jstack " + pid + " >> " + file; try { - // 执行命令 - RuntimeHelper.exec(10000, "/bin/sh", "-c", cmd); + synchronized (lock) { + // 执行命令 + RuntimeHelper.exec(10000, "/bin/sh", "-c", cmd); + } } catch (Exception e) { logger.warn("RUN Java Thread Analysis FAIL: ", e); @@ -168,44 +166,10 @@ private Object captureJavaThreadAnalysis(Object... params) { return "ERR:FILE PERMISSION DENIED"; } - monitor.logPerf(stTime, "THREAD_ANALYSIS"); return file; } - /** - * controlConcurrency - * - * @param pid - * @param exectime - * @return - */ - private boolean controlConcurrency(String pid, Long exectime) { - - // initial - if (!timeIntervalMap.containsKey(pid)) { - synchronized (timeIntervalMap) { - if (!timeIntervalMap.containsKey(pid)) { - // 在exectimeMap记录进程号和执行时间 - timeIntervalMap.put(pid, exectime); - return true; - } - } - } - // only one can entrance - if ((exectime - timeIntervalMap.get(pid)) > timeInterval) { - synchronized (timeIntervalMap) { - if ((exectime - timeIntervalMap.get(pid)) > timeInterval) { - // 在exectimeMap记录进程号和执行时间 - timeIntervalMap.put(pid, exectime); - return true; - } - } - } - // thread analysis is running, abandon - return false; - } - /** * deleteFileByFuzzyName * @@ -244,4 +208,16 @@ private boolean checkDirPermission(String dir) { return false; } } + + private String getJdkBinPath() { + + // 获取java_home路径 + String javahome = System.getProperty("java.home"); + // 路径是否由“/”结束,没有则添加 + if (!javahome.endsWith("/")) { + javahome = javahome + "/"; + } + // 获取java_home bin的路径 + return javahome + "../bin"; + } } diff --git a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/ThreadAnalysisAgent.java b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/ThreadAnalysisAgent.java index f5da2de0..bb7ba6de 100644 --- a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/ThreadAnalysisAgent.java +++ b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/ThreadAnalysisAgent.java @@ -22,6 +22,9 @@ import com.creditease.agent.spi.AgentFeatureComponent; import com.creditease.agent.spi.IActionEngine; +import com.creditease.uav.threadanalysis.client.action.CountCtrlAction; +import com.creditease.uav.threadanalysis.client.action.DumpThreadAction; +import com.creditease.uav.threadanalysis.client.action.SuspendAction; import com.creditease.uav.threadanalysis.client.action.ThreadAnalysisAction; /** @@ -32,6 +35,8 @@ */ public class ThreadAnalysisAgent extends AgentFeatureComponent { + private IActionEngine actionEngine; + public ThreadAnalysisAgent(String cName, String feature) { super(cName, feature); } @@ -39,11 +44,24 @@ public ThreadAnalysisAgent(String cName, String feature) { @Override public void start() { - IActionEngine engine = this.getActionEngineMgr().getActionEngine("NodeOperActionEngine"); + actionEngine = getActionEngineMgr().newActionEngine("JTAActionEngine", feature); + new DumpThreadAction("DumpThreadAction", feature, actionEngine); + new CountCtrlAction("CountCtrlAction", feature, actionEngine); + new SuspendAction("SuspendAction", feature, actionEngine); + // setup actions + IActionEngine engine = this.getActionEngineMgr().getActionEngine("NodeOperActionEngine"); new ThreadAnalysisAction("threadanalysis", feature, engine); } + @Override + public void stop() { + + actionEngine.clean(); + + super.stop(); + } + @Override public Object exchange(String eventKey, Object... data) { diff --git a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/CountCtrlAction.java b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/CountCtrlAction.java new file mode 100644 index 00000000..c46a8176 --- /dev/null +++ b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/CountCtrlAction.java @@ -0,0 +1,85 @@ +/*- + * << + * UAVStack + * == + * Copyright (C) 2016 - 2017 UAVStack + * == + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * >> + */ + +package com.creditease.uav.threadanalysis.client.action; + +import com.creditease.agent.helpers.JSONHelper; +import com.creditease.agent.spi.AbstractBaseAction; +import com.creditease.agent.spi.ActionContext; +import com.creditease.agent.spi.IActionEngine; + +public class CountCtrlAction extends AbstractBaseAction { + + public CountCtrlAction(String cName, String feature, IActionEngine engine) { + super(cName, feature, engine); + } + + @Override + public void doAction(final ActionContext context) throws Exception { + + if(log.isDebugEnable()) { + log.debug(this, "CountCtrlAction.doAction context=" + JSONHelper.toString(context)); + } + + String fileName = (String) context.getParam("msg"); + if (fileName.startsWith("ERR")) { + context.setSucessful(false); + return; + } + + int times = (int) context.getParam("times"); + context.putParam("times", --times); + if (times > 0) { + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + + getActionEngineMgr().getActionEngine("JTAActionEngine").execute("SuspendAction", context); + } + }); + t.start(); + } + + context.setSucessful(true); + } + + @Override + public String getSuccessNextActionId() { + + // TODO Auto-generated method stub + return null; + } + + @Override + public String getFailureNextActionId() { + + // TODO Auto-generated method stub + return null; + } + + @Override + public String getExceptionNextActionId() { + + // TODO Auto-generated method stub + return null; + } + +} diff --git a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/DumpThreadAction.java b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/DumpThreadAction.java new file mode 100644 index 00000000..54ecacc8 --- /dev/null +++ b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/DumpThreadAction.java @@ -0,0 +1,207 @@ +/*- + * << + * UAVStack + * == + * Copyright (C) 2016 - 2017 UAVStack + * == + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * >> + */ + +package com.creditease.uav.threadanalysis.client.action; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.creditease.agent.helpers.JSONHelper; +import com.creditease.agent.helpers.StringHelper; +import com.creditease.agent.spi.AbstractBaseAction; +import com.creditease.agent.spi.ActionContext; +import com.creditease.agent.spi.AgentFeatureComponent; +import com.creditease.agent.spi.IActionEngine; +import com.creditease.uav.httpasync.HttpAsyncClient; +import com.creditease.uav.httpasync.HttpAsyncClientFactory; +import com.creditease.uav.httpasync.HttpClientCallback; +import com.creditease.uav.httpasync.HttpClientCallbackResult; + +public class DumpThreadAction extends AbstractBaseAction { + + private HttpAsyncClient httpClient; + + public DumpThreadAction(String cName, String feature, IActionEngine engine) { + super(cName, feature, engine); + + httpClient = HttpAsyncClientFactory.build(2, 50, 10000, 10000, 10000); + } + + @Override + public void doAction(ActionContext context) throws Exception { + + String url = (String) context.getParam("url"); + @SuppressWarnings("unchecked") + Map paramMap = (Map) context.getParam("paramMap"); + @SuppressWarnings("unchecked") + List paramsList = (List) paramMap.get("param"); + paramsList.set(1, System.currentTimeMillis() + ""); + + if(log.isDebugEnable()) { + log.debug(this, "DumpThreadAction.doAction context=" + JSONHelper.toString(context)); + } + + String fileName = invokeJTASupporter(url, JSONHelper.toString(paramMap)); + + if (fileName.startsWith("ERR:")) { + context.putParam("msg", fileName); + context.setSucessful(false); + return; + } + + fileName = collectFile(fileName, (String) context.getParam("user"), paramsList); + context.putParam("msg", fileName); + + context.setSucessful(true); + } + + private String invokeJTASupporter(final String url, final String content) { + + long start = System.currentTimeMillis(); + final CountDownLatch cdl = new CountDownLatch(1); + final StringBuffer sb = new StringBuffer(); + + httpClient.doAsyncHttpPost(url, content, new HttpClientCallback() { + + @Override + public void completed(HttpClientCallbackResult result) { + + String res = result.getReplyDataAsString(); + if (!StringHelper.isEmpty(res) && !res.contains("ERR") && !res.contains("Err")) { + sb.append(res); + } + else { + log.err(this, "MOFCtrlAction process FAILED. url=" + url + ", req=" + content + ", resp=" + res); + sb.append("ERR:MOF PROCESS FAILED:" + res); + } + + cdl.countDown(); + } + + @Override + public void failed(HttpClientCallbackResult result) { + + Exception e = result.getException(); + sb.append("ERR:INVOKE MOF FAILED:" + e.getMessage()); + + log.err(this, "invoke MOFCtrlAction FAILED. url=" + url + ", retcode=" + result.getRetCode() + ", resp=" + + result.getReplyDataAsString(), e); + + cdl.countDown(); + } + }); + + try { + cdl.await(10000, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) { + sb.append("ERR:INVOKE MOF TIMEOUT"); + } + + if (log.isDebugEnable()) { + log.debug(this, "invoke MOFCtrlAction url=" + url + ", req=" + content + ", resp=" + sb.toString() + + ", cost=" + (System.currentTimeMillis() - start)); + } + return sb.toString(); + } + + private String collectFile(String fileName, String user, List paramsList) { + + String ipport = getIPPort(fileName); + String pid = "unknown"; + String time = System.currentTimeMillis() + ""; + if (paramsList.size() > 2) { + pid = paramsList.get(0).toString(); + time = paramsList.get(1).toString(); + } + String target = ipport + "_" + pid + "_" + time + "_" + user; + + AgentFeatureComponent afc = (AgentFeatureComponent) this.getConfigManager().getComponent("collectclient", + "CollectDataAgent"); + + if (afc == null) { + return "ERR:归集客户端未启动"; + } + + String collectAct = "collectdata.add"; + String mqTopic = "JQ_JTA"; + + // call CollectDataAgent, prepare parameters + Map task = new HashMap(); + task.put("target", target); + task.put("action", mqTopic); + task.put("file", fileName); + task.put("topic", mqTopic); + task.put("unsplit", true); + List> tasks = new ArrayList>(); + tasks.add(task); + Map params = new HashMap(); + params.put("tasks", tasks); + Map call = new HashMap(); + call.put("feature", "threadanalysis"); + call.put("component", "ThreadAnalysisAgent"); + call.put("eventKey", "collect.callback"); + params.put("callback", call); + + String collectTasks = JSONHelper.toString(params); + afc.exchange(collectAct, collectTasks); + + return fileName; + } + + private String getIPPort(String fileName) { + + String file = StringHelper.getFilename(fileName); + String[] args = file.split("_"); + if (args.length > 2) { + return args[0] + ":" + args[1]; + } + return "127.0.0.1:8080"; + } + + @Override + public String getSuccessNextActionId() { + + return "CountCtrlAction"; + } + + @Override + public String getFailureNextActionId() { + + return null; + } + + @Override + public String getExceptionNextActionId() { + + return null; + } + + @Override + public void destroy() { + + httpClient.shutdown(); + } + +} diff --git a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/SuspendAction.java b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/SuspendAction.java new file mode 100644 index 00000000..7655dc49 --- /dev/null +++ b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/SuspendAction.java @@ -0,0 +1,66 @@ +/*- + * << + * UAVStack + * == + * Copyright (C) 2016 - 2017 UAVStack + * == + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * >> + */ + +package com.creditease.uav.threadanalysis.client.action; + +import com.creditease.agent.helpers.JSONHelper; +import com.creditease.agent.helpers.ThreadHelper; +import com.creditease.agent.spi.AbstractBaseAction; +import com.creditease.agent.spi.ActionContext; +import com.creditease.agent.spi.IActionEngine; + +public class SuspendAction extends AbstractBaseAction { + + public SuspendAction(String cName, String feature, IActionEngine engine) { + super(cName, feature, engine); + } + + @Override + public void doAction(ActionContext context) throws Exception { + + if(log.isDebugEnable()) { + log.debug(this, "SuspendAction.doAction context=" + JSONHelper.toString(context)); + } + + int suspendTime = (int) context.getParam("suspendTime"); + ThreadHelper.suspend(suspendTime); + + context.setSucessful(true); + } + + @Override + public String getSuccessNextActionId() { + + return "DumpThreadAction"; + } + + @Override + public String getFailureNextActionId() { + + return null; + } + + @Override + public String getExceptionNextActionId() { + + return null; + } + +} diff --git a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/ThreadAnalysisAction.java b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/ThreadAnalysisAction.java index 175ff8d3..97bb59d3 100644 --- a/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/ThreadAnalysisAction.java +++ b/com.creditease.uav.threadanalysis/src/main/java/com/creditease/uav/threadanalysis/client/action/ThreadAnalysisAction.java @@ -20,14 +20,11 @@ package com.creditease.uav.threadanalysis.client.action; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.ConcurrentHashMap; +import com.creditease.agent.helpers.DataConvertHelper; import com.creditease.agent.helpers.IOHelper; import com.creditease.agent.helpers.JSONHelper; import com.creditease.agent.helpers.RuntimeHelper; @@ -35,113 +32,95 @@ import com.creditease.agent.http.api.UAVHttpMessage; import com.creditease.agent.spi.AbstractBaseAction; import com.creditease.agent.spi.ActionContext; -import com.creditease.agent.spi.AgentFeatureComponent; import com.creditease.agent.spi.IActionEngine; import com.creditease.agent.spi.IConfigurationManager; -import com.creditease.uav.httpasync.HttpAsyncClient; -import com.creditease.uav.httpasync.HttpAsyncClientFactory; -import com.creditease.uav.httpasync.HttpClientCallback; -import com.creditease.uav.httpasync.HttpClientCallbackResult; public class ThreadAnalysisAction extends AbstractBaseAction { - private HttpAsyncClient client; + private static final String SERVICE_POSTFIX = "/com.creditease.uav/server?action=runSupporter"; - private String rootMetaPath; + // 执行时间map,key为进程号,value为执行时间;用于判断在限定时间段内不需要发起多次请求 + private static volatile Map timeIntervalMap = new ConcurrentHashMap(); + + private String dumpFileDirectory; + + private long timeInterval = 30000L; // 30s public ThreadAnalysisAction(String cName, String feature, IActionEngine engine) { super(cName, feature, engine); - client = HttpAsyncClientFactory.build(2, 50, 10000, 10000, 10000); - rootMetaPath = this.getConfigManager().getContext(IConfigurationManager.METADATAPATH) + "thread.analysis"; // 线程分析的文件位置,不存在则创建。只有一个MA,存在多个用户的情况,考虑权限问题,设置这个文件夹对别的用户可读写 + dumpFileDirectory = getConfigManager().getContext(IConfigurationManager.METADATAPATH) + "thread.analysis"; try { - IOHelper.createFolder(rootMetaPath); - RuntimeHelper.exec(10000, "/bin/sh", "-c", "chmod 777 " + rootMetaPath); + IOHelper.createFolder(dumpFileDirectory); + RuntimeHelper.exec(10000, "/bin/sh", "-c", "chmod 777 " + dumpFileDirectory); } - catch (Exception e) { + catch (Exception ignore) { // ignore } } - @SuppressWarnings("unchecked") @Override public void doAction(ActionContext context) throws Exception { try { UAVHttpMessage data = (UAVHttpMessage) context.getParam("msg"); - String action = "runSupporter"; - String server = data.getRequest("server") + "/com.creditease.uav/server?action=" + action; - final String url = server; - String param = data.getRequest("actparam"); + if (!controlConcurrency(data)) { + data.putResponse("rs", "ERR"); + data.putResponse("msg", "ERR:THREAD DUMP IS RUNNING"); + return; + } String user = data.getRequest("user"); if (StringHelper.isEmpty(user)) { user = "UNKNOWN"; } + String url = data.getRequest("server") + SERVICE_POSTFIX; + if (!url.startsWith("http")) { + url = "http://" + url; + } + + String param = data.getRequest("actparam"); + @SuppressWarnings("unchecked") Map paramMap = JSONHelper.toObject(param, Map.class); + @SuppressWarnings("unchecked") List paramsList = (List) paramMap.get("param"); - paramsList.add(this.rootMetaPath); + paramsList.add(this.dumpFileDirectory); paramMap.put("param", paramsList); - final AtomicBoolean isSuccess = new AtomicBoolean(false); - - final CountDownLatch cdl = new CountDownLatch(1); - - final StringBuilder response = new StringBuilder(); - - client.doAsyncHttpPost(url, JSONHelper.toString(paramMap), new HttpClientCallback() { - - @Override - public void completed(HttpClientCallbackResult result) { - - String res = result.getReplyDataAsString(); - - log.info(this, "MOFCtrlAction Success: url=" + url + ", res=" + res); - - if (!StringHelper.isEmpty(res) && !res.contains("ERR") && !res.contains("Err")) { - isSuccess.set(true); - } - - response.append(res); - - cdl.countDown(); - } - - @Override - public void failed(HttpClientCallbackResult result) { - - String exp = result.getException().getMessage(); - response.append(exp); - - log.err(this, "MOFCtrlAction FAIL: url=" + url + ", err=" + exp); + ActionContext ac = new ActionContext(); + ac.putParam("user", user); + ac.putParam("url", url); + ac.putParam("paramMap", paramMap); + + if ("true".equals(data.getRequest("multiple"))) { + ac.putParam("multiple", true); + int duration = DataConvertHelper.toInt(data.getRequest("duration"), 0); + int interval = DataConvertHelper.toInt(data.getRequest("interval"), 5); + int times = duration / interval + 1; + ac.putParam("times", times); + ac.putParam("suspendTime", interval * 1000); + } + else { + ac.putParam("multiple", false); + ac.putParam("times", 1); + ac.putParam("suspendTime", 0); + } - isSuccess.set(false); - cdl.countDown(); - } - }); + ac = getActionEngineMgr().getActionEngine("JTAActionEngine").execute("DumpThreadAction", ac); - cdl.await(10000, TimeUnit.MILLISECONDS); + String ret = (String) ac.getParam("msg"); - if (isSuccess.get() == false) { + if (ret.contains("ERR:")) { data.putResponse("rs", "ERR"); - data.putResponse("msg", response.toString()); - return; + data.putResponse("msg", ret); } - // 成功则返回文件名 - String fileName = response.toString(); - // 如果传入的参数不正确,则不会生成文件。至此可知参数符合规则。 - String ipport = getIpport(fileName); - String pid = "unknown"; - String time = System.currentTimeMillis() + ""; - if (paramsList.size() > 2) { - pid = paramsList.get(0).toString(); - time = paramsList.get(1).toString(); + else { + data.putResponse("rs", "OK"); + data.putResponse("msg", ret); } - String target = ipport + "_" + pid + "_" + time + "_" + user; - doCollectFiles(data, fileName, target); } catch (Exception e) { log.err(this, "do thread analysis FAILED.", e); @@ -149,61 +128,6 @@ public void failed(HttpClientCallbackResult result) { } } - private String getIpport(String fileName) { - - String file = StringHelper.getFilename(fileName); - String[] args = file.split("_"); - if (args.length > 2) { - return args[0] + ":" + args[1]; - } - return "127.0.0.1:8080"; - - } - - /** - * doCollectFiles - * - * @param data - * @param fileName - */ - private void doCollectFiles(UAVHttpMessage data, String fileName, String target) { - - AgentFeatureComponent afc = (AgentFeatureComponent) this.getConfigManager().getComponent("collectclient", - "CollectDataAgent"); - - if (afc == null) { - data.putResponse("rs", "ERR"); - data.putResponse("msg", "归集客户端未启动"); - return; - } - - String collectAct = "collectdata.add"; - String mqTopic = "JQ_JTA"; - - // call CollectDataAgent, prepare parameters - Map task = new HashMap(); - task.put("target", target); - task.put("action", mqTopic); - task.put("file", fileName); - task.put("topic", mqTopic); - task.put("unsplit", true); - List> tasks = new ArrayList>(); - tasks.add(task); - Map params = new HashMap(); - params.put("tasks", tasks); - Map call = new HashMap(); - call.put("feature", "threadanalysis"); - call.put("component", "ThreadAnalysisAgent"); - call.put("eventKey", "collect.callback"); - params.put("callback", call); - - String collectTasks = JSONHelper.toString(params); - afc.exchange(collectAct, collectTasks); - - data.putResponse("rs", "OK"); - data.putResponse("msg", fileName); - } - @Override public String getSuccessNextActionId() { @@ -222,4 +146,39 @@ public String getExceptionNextActionId() { return null; } + /** + * controlConcurrency + * + * @param pid + * @param exectime + * @return + */ + private boolean controlConcurrency(UAVHttpMessage data) { + + String server = data.getRequest("server"); + long exectime = System.currentTimeMillis(); + long duration = 1000L * DataConvertHelper.toInt(data.getRequest("duration"), 0); + // initial + if (!timeIntervalMap.containsKey(server)) { + synchronized (timeIntervalMap) { + if (!timeIntervalMap.containsKey(server)) { + // 在exectimeMap记录进程号和执行时间 + timeIntervalMap.put(server, exectime + duration); + return true; + } + } + } + // only one can entrance + if ((exectime - timeIntervalMap.get(server)) > timeInterval) { + synchronized (timeIntervalMap) { + if ((exectime - timeIntervalMap.get(server)) > timeInterval) { + // 在exectimeMap记录进程号和执行时间 + timeIntervalMap.put(server, exectime + duration); + return true; + } + } + } + // thread analysis is running, abandon + return false; + } }