支持统计一段时间执行的sql语句,打印出平均耗时和总耗时最长的10条sql语句

pull/1879/head
xiaobingyang 3 years ago
parent 75ad585fc6
commit d24b90f49c

@ -1,5 +1,6 @@
package com.taobao.arthas.core.command.model;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -10,15 +11,10 @@ import java.util.List;
*/
public class SqlProfilerModel extends ResultModel {
private Date ts;
private String className;
private String methodName;
private double cost;
private boolean success;
private String sql;
private List<String> params;
private List<String> batchSql;
private List<List<String>> batchParams;
private TraceData traceData;
private MonitorData monitorData;
public SqlProfilerModel() {
}
@ -36,67 +32,184 @@ public class SqlProfilerModel extends ResultModel {
this.ts = ts;
}
public String getClassName() {
return className;
public TraceData getTraceData() {
return traceData;
}
public void setClassName(String className) {
this.className = className;
public void setTraceData(TraceData traceData) {
this.traceData = traceData;
}
public String getMethodName() {
return methodName;
public MonitorData getMonitorData() {
return monitorData;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
public void setMonitorData(MonitorData monitorData) {
this.monitorData = monitorData;
}
public double getCost() {
return cost;
}
public static class TraceData {
private String className;
private String methodName;
private double cost;
private boolean success;
private String sql;
private List<String> params;
public void setCost(double cost) {
this.cost = cost;
}
private List<String> batchSql;
private List<List<String>> batchParams;
public String getSql() {
return sql;
}
public String getClassName() {
return className;
}
public void setSql(String sql) {
this.sql = sql;
}
public void setClassName(String className) {
this.className = className;
}
public boolean isSuccess() {
return success;
}
public String getMethodName() {
return methodName;
}
public void setSuccess(boolean success) {
this.success = success;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public List<String> getParams() {
return params;
}
public double getCost() {
return cost;
}
public void setParams(List<String> params) {
this.params = params;
}
public void setCost(double cost) {
this.cost = cost;
}
public List<String> getBatchSql() {
return batchSql;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public List<String> getParams() {
return params;
}
public void setParams(List<String> params) {
this.params = params;
}
public List<String> getBatchSql() {
return batchSql;
}
public void setBatchSql(List<String> batchSql) {
this.batchSql = batchSql;
}
public List<List<String>> getBatchParams() {
return batchParams;
}
public void setBatchSql(List<String> batchSql) {
this.batchSql = batchSql;
public void setBatchParams(List<List<String>> batchParams) {
this.batchParams = batchParams;
}
}
public List<List<String>> getBatchParams() {
return batchParams;
public static class MonitorData {
private List<SqlStat> topByTotalCost = new ArrayList<SqlStat>();
private List<SqlStat> topByAvgCost = new ArrayList<SqlStat>();
public List<SqlStat> getTopByTotalCost() {
return topByTotalCost;
}
public void setTopByTotalCost(List<SqlStat> topByTotalCost) {
this.topByTotalCost = topByTotalCost;
}
public List<SqlStat> getTopByAvgCost() {
return topByAvgCost;
}
public void setTopByAvgCost(List<SqlStat> topByAvgCost) {
this.topByAvgCost = topByAvgCost;
}
}
public void setBatchParams(List<List<String>> batchParams) {
this.batchParams = batchParams;
public static class SqlStat {
private String sql;
private double avgCost = 0d;
private double totalCost = 0d;
private Integer count = 0;
private Integer successCount = 0;
private Integer failedCount = 0;
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public double getAvgCost() {
return avgCost;
}
public void setAvgCost(double avgCost) {
this.avgCost = avgCost;
}
public double getTotalCost() {
return totalCost;
}
public void setTotalCost(double totalCost) {
this.totalCost = totalCost;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getSuccessCount() {
return successCount;
}
public void setSuccessCount(Integer successCount) {
this.successCount = successCount;
}
public Integer getFailedCount() {
return failedCount;
}
public void setFailedCount(Integer failedCount) {
this.failedCount = failedCount;
}
public synchronized void addSample(double cost) {
count++;
totalCost += cost;
}
public synchronized void calc() {
if (count > 0) {
avgCost = totalCost / count;
}
}
}
}

@ -19,11 +19,13 @@ import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.*;
/**
* @author yangxiaobing 2021/8/4.
*/
class SqlProfilerAdviceListener extends AdviceListenerAdapter {
private ScheduledExecutorService scheduledExecutorService;
private static final Logger logger = LoggerFactory.getLogger(SqlProfilerAdviceListener.class);
public static final List<String> STATEMENT_EXECUTE_METHOD_LIST = Arrays.asList("executeQuery", "executeUpdate", "execute");
@ -49,6 +51,8 @@ class SqlProfilerAdviceListener extends AdviceListenerAdapter {
// 用来解决connectionProxy、statementProxy会导致sql语句被记录多次的问题
private ThreadLocal<SqlExecuteStack> sqlStack = new ThreadLocal<SqlExecuteStack>();
private ConcurrentHashMap<String, SqlProfilerModel.SqlStat> sqlStatMap;
static {
PREPARED_STATEMENT_SET_PARAM_METHOD_MAP = MethodWithSignature.build(PreparedStatement.class, new Function<Method, Boolean>() {
@Override
@ -118,6 +122,32 @@ class SqlProfilerAdviceListener extends AdviceListenerAdapter {
super.setVerbose(verbose);
}
@Override
public synchronized void create() {
if (scheduledExecutorService == null && "monitor".equalsIgnoreCase(command.getAction())) {
sqlStatMap = new ConcurrentHashMap<String, SqlProfilerModel.SqlStat>();
scheduledExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("Timer-for-arthas-sqlprofiler-" + process.session().getSessionId());
return thread;
}
});
scheduledExecutorService.schedule(new MonitorCollectTask(sqlStatMap, process),
command.getMonitorDuration(), TimeUnit.SECONDS);
}
}
@Override
public synchronized void destroy() {
if (null != scheduledExecutorService) {
scheduledExecutorService.shutdown();
scheduledExecutorService = null;
}
}
@Override
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
throws Throwable {
@ -429,16 +459,44 @@ class SqlProfilerAdviceListener extends AdviceListenerAdapter {
}
}
if ("trace".equalsIgnoreCase(command.getAction())) {
appendTraceData(advice, cost, isSuccess, sql, paramObjectList, batchSql, batchParamObjectList);
} else {
appendMonitorStat(advice, cost, isSuccess, sql, paramObjectList, batchSql, batchParamObjectList);
}
}
private void appendMonitorStat(Advice advice, double cost, boolean isSuccess, String sql, Collection<Object> paramObjectList, List<String> batchSql, List<? extends Map<Integer, Object>> batchParamObjectList) {
if (StringUtils.isEmpty(sql)) {
return;
}
SqlProfilerModel.SqlStat sqlStat = sqlStatMap.get(sql);
if (sqlStat == null) {
sqlStat = new SqlProfilerModel.SqlStat();
sqlStat.setSql(sql);
if (sqlStatMap.putIfAbsent(sql, sqlStat) != null) {
sqlStat = sqlStatMap.get(sql);
}
}
sqlStat.addSample(cost);
}
private void appendTraceData(Advice advice, double cost, boolean isSuccess, String sql, Collection<Object> paramObjectList, List<String> batchSql, List<? extends Map<Integer, Object>> batchParamObjectList) {
SqlProfilerModel model = new SqlProfilerModel();
model.setTs(new Date());
model.setClassName(advice.getClazz().getName());
model.setMethodName(advice.getMethod().getName());
model.setCost(cost);
model.setSuccess(isSuccess);
model.setSql(sql);
model.setParams(convertSqlObjectToString(paramObjectList));
model.setBatchSql(batchSql);
model.setBatchParams(convertSqlObjectToString(batchParamObjectList));
SqlProfilerModel.TraceData traceData = new SqlProfilerModel.TraceData();
model.setTraceData(traceData);
traceData.setClassName(advice.getClazz().getName());
traceData.setMethodName(advice.getMethod().getName());
traceData.setCost(cost);
traceData.setSuccess(isSuccess);
traceData.setSql(sql);
traceData.setParams(convertSqlObjectToString(paramObjectList));
traceData.setBatchSql(batchSql);
traceData.setBatchParams(convertSqlObjectToString(batchParamObjectList));
process.appendResult(model);
@ -719,4 +777,58 @@ class SqlProfilerAdviceListener extends AdviceListenerAdapter {
return false;
}
}
private class MonitorCollectTask implements Runnable {
private ConcurrentHashMap<String, SqlProfilerModel.SqlStat> sqlStatMap;
private CommandProcess process;
MonitorCollectTask(ConcurrentHashMap<String, SqlProfilerModel.SqlStat> sqlStatMap, CommandProcess process) {
this.sqlStatMap = sqlStatMap;
this.process = process;
}
@Override
public void run() {
SqlProfilerModel profilerModel = new SqlProfilerModel();
SqlProfilerModel.MonitorData monitorData = new SqlProfilerModel.MonitorData();
profilerModel.setMonitorData(monitorData);
ArrayList<SqlProfilerModel.SqlStat> statArrayList = new ArrayList<SqlProfilerModel.SqlStat>(sqlStatMap.values());
for (SqlProfilerModel.SqlStat sqlStat : statArrayList) {
sqlStat.calc();
}
monitorData.setTopByAvgCost(getTopK(statArrayList, new Comparator<SqlProfilerModel.SqlStat>() {
@Override
public int compare(SqlProfilerModel.SqlStat o1, SqlProfilerModel.SqlStat o2) {
return (int) (o2.getAvgCost() - o1.getAvgCost());
}
}, 10));
monitorData.setTopByTotalCost(getTopK(statArrayList, new Comparator<SqlProfilerModel.SqlStat>() {
@Override
public int compare(SqlProfilerModel.SqlStat o1, SqlProfilerModel.SqlStat o2) {
return (int) (o2.getTotalCost() - o1.getTotalCost());
}
}, 10));
process.appendResult(profilerModel);
process.end();
}
private <T> List<T> getTopK(List<T> list, Comparator<T> comparator, int k) {
Collections.sort(list, comparator);
List<T> result = new ArrayList<T>(k);
int i = 0;
for (T t : list) {
result.add(t);
if (++i >= k) {
break;
}
}
return result;
}
}
}

@ -27,6 +27,8 @@ public class SqlProfilerCommand extends EnhancerCommand {
private String sqlPattern;
private String conditionExpress;
private boolean isRegEx = false;
private String action = "trace";
private Long monitorDuration = 5L;
private Matcher sqlMatcher;
@ -59,6 +61,18 @@ public class SqlProfilerCommand extends EnhancerCommand {
isRegEx = regEx;
}
@Option(shortName = "a", longName = "action", required = false)
@Description("Action to execute, monitor or trace")
public void setAction(String action) {
this.action = action;
}
@Option(shortName = "d", longName = "duration", required = false)
@Description("run profiling for <duration> seconds")
public void setDuration(long duration) {
this.monitorDuration = duration;
}
public String getConditionExpress() {
return conditionExpress;
}
@ -67,6 +81,14 @@ public class SqlProfilerCommand extends EnhancerCommand {
return sqlPattern;
}
public String getAction() {
return action;
}
public Long getMonitorDuration() {
return monitorDuration;
}
public boolean isRegEx() {
return isRegEx;
}
@ -106,6 +128,17 @@ public class SqlProfilerCommand extends EnhancerCommand {
return sqlMatcher;
}
@Override
public void process(CommandProcess process) {
if (!"trace".equalsIgnoreCase(this.action)
&& !"monitor".equalsIgnoreCase(this.action)) {
process.end(-1, "invalid action argument");
return;
}
super.process(process);
}
@Override
protected AdviceListener getAdviceListener(CommandProcess process) {
final AdviceListener listener = new SqlProfilerAdviceListener(this, process, GlobalOptions.verbose || this.verbose);

@ -1,12 +1,21 @@
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.SqlProfilerModel;
import com.taobao.arthas.core.command.monitor200.MonitorData;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.DateUtils;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.text.Decoration;
import com.taobao.text.ui.TableElement;
import com.taobao.text.util.RenderUtil;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import static com.taobao.text.ui.Element.label;
/**
* Term view for SqlProfilerModel
*
@ -16,10 +25,56 @@ public class SqlProfilerView extends ResultView<SqlProfilerModel> {
@Override
public void draw(CommandProcess process, SqlProfilerModel model) {
if (model.getTraceData() != null) {
drawTraceView(process, model.getTs(), model.getTraceData());
} else {
drawMonitorView(process, model.getTs(), model.getMonitorData());
}
}
private void drawMonitorView(CommandProcess process, Date ts, SqlProfilerModel.MonitorData monitorData) {
process.write(String.format("Top %s by total cost: \n", monitorData.getTopByTotalCost().size()));
for (int i = 0; i < monitorData.getTopByTotalCost().size(); i++) {
SqlProfilerModel.SqlStat sqlStat = monitorData.getTopByTotalCost().get(i);
process.write(String.format("[Top %s sql]: \n" +
"[count]: %s\n" +
"[avgCost(ms)]: %s\n" +
"[totalCost(ms)]: %s\n" +
"[success]: %s\n" +
"[fail]: %s\n",
i,
sqlStat.getSql(),
sqlStat.getCount(),
sqlStat.getAvgCost(),
sqlStat.getTotalCost(),
sqlStat.getSuccessCount(),
sqlStat.getFailedCount()));
}
process.write(String.format("Top %s by avg cost: \n", monitorData.getTopByAvgCost().size()));
for (int i = 0; i < monitorData.getTopByAvgCost().size(); i++) {
SqlProfilerModel.SqlStat sqlStat = monitorData.getTopByAvgCost().get(i);
process.write(String.format("[Top %s sql]: %s\n" +
"[count]: %s\n" +
"[avgCost(ms)]: %s\n" +
"[totalCost(ms)]: %s\n" +
"[success]: %s\n" +
"[fail]: %s\n",
i,
sqlStat.getSql(),
sqlStat.getCount(),
sqlStat.getAvgCost(),
sqlStat.getTotalCost(),
sqlStat.getSuccessCount(),
sqlStat.getFailedCount()));
}
}
private void drawTraceView(CommandProcess process, Date ts, SqlProfilerModel.TraceData model) {
StringBuilder outputBuilder = new StringBuilder(String.format("ts=%s; [method=%s.%s] [cost=%sms]\n" +
"sql: %s\n" +
"args: ",
DateUtils.formatDate(model.getTs()),
DateUtils.formatDate(ts),
model.getClassName(),
model.getMethodName(),
model.getCost(),

Loading…
Cancel
Save