package com.github.ompc.greys.core.command;
import com.github.ompc.greys.core.advisor.AdviceListener;
import com.github.ompc.greys.core.advisor.AdviceListenerAdapter;
import com.github.ompc.greys.core.command.annotation.Cmd;
import com.github.ompc.greys.core.command.annotation.IndexArg;
import com.github.ompc.greys.core.command.annotation.NamedArg;
import com.github.ompc.greys.core.server.Session;
import com.github.ompc.greys.core.textui.TTable;
import com.github.ompc.greys.core.util.InvokeCost;
import com.github.ompc.greys.core.util.PointCut;
import com.github.ompc.greys.core.util.SimpleDateFormatHolder;
import com.github.ompc.greys.core.util.matcher.ClassMatcher;
import com.github.ompc.greys.core.util.matcher.GaMethodMatcher;
import com.github.ompc.greys.core.util.matcher.PatternMatcher;
import java.lang.instrument.Instrumentation;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import static com.github.ompc.greys.core.util.GaCheckUtils.isEquals;
/**
* 监控请求命令<br/>
* 输出的内容格式为:<br/>
* <style type="text/css">
* table, th, td {
* borders:1px solid #cccccc;
* borders-collapse:collapse;
* }
* </style>
* <table>
* <tr>
* <th>时间戳</th>
* <th>统计周期(s)</th>
* <th>类全路径</th>
* <th>方法名</th>
* <th>调用总次数</th>
* <th>成功次数</th>
* <th>失败次数</th>
* <th>平均耗时(ms)</th>
* <th>失败率</th>
* </tr>
* <tr>
* <td>2012-11-07 05:00:01</td>
* <td>120</td>
* <td>com.taobao.item.ItemQueryServiceImpl</td>
* <td>queryItemForDetail</td>
* <td>1500</td>
* <td>1000</td>
* <td>500</td>
* <td>15</td>
* <td>30%</td>
* </tr>
* <tr>
* <td>2012-11-07 05:00:01</td>
* <td>120</td>
* <td>com.taobao.item.ItemQueryServiceImpl</td>
* <td>queryItemById</td>
* <td>900</td>
* <td>900</td>
* <td>0</td>
* <td>7</td>
* <td>0%</td>
* </tr>
* </table>
*
* @author oldmanpushcart@gmail.com
*/
@Cmd(name = "monitor", sort = 2, summary = "Monitor the execution of specified Class and its method",
eg = {
"monitor -c 5 -E org\\.apache\\.commons\\.lang\\.StringUtils *",
"monitor -c 5 org.apache.commons.lang.StringUtils is*",
"monitor *StringUtils isBlank"
})
public class MonitorCommand implements Command {
@IndexArg(index = 0, name = "class-pattern", summary = "Path and classname of Pattern Matching")
private String classPattern;
@IndexArg(index = 1, name = "method-pattern", summary = "Method of Pattern Matching")
private String methodPattern;
@NamedArg(name = "c", hasValue = true, summary = "The cycle of monitor")
private int cycle = 120;
@NamedArg(name = "E", summary = "Enable regular expression to match (wildcard matching by default)")
private boolean isRegEx = false;
/**
* 数据监控用的Key
*
* @author oldmanpushcart@gmail.com
*/
private static class Key {
private final String className;
private final String methodName;
private Key(String className, String methodName) {
this.className = className;
this.methodName = methodName;
}
@Override
public int hashCode() {
return className.hashCode() + methodName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj
|| !(obj instanceof Key)) {
return false;
}
Key oKey = (Key) obj;
return isEquals(oKey.className, className)
&& isEquals(oKey.methodName, methodName);
}
}
/**
* 数据监控用的value
*
* @author oldmanpushcart@gmail.com
*/
private static class Data {
private int total;
private int success;
private int failed;
private long cost;
private Long maxCost;
private Long minCost;
}
@Override
public Action getAction() {
return new GetEnhancerAction() {
@Override
public GetEnhancer action(final Session session, Instrumentation inst, final Printer printer) throws Throwable {
return new GetEnhancer() {
@Override
public PointCut getPointCut() {
return new PointCut(
new ClassMatcher(new PatternMatcher(isRegEx, classPattern)),
new GaMethodMatcher(new PatternMatcher(isRegEx, methodPattern))
);
}
@Override
public AdviceListener getAdviceListener() {
return new AdviceListenerAdapter() {
/*
* 输出定时任务
*/
private Timer timer;
/*
* 监控数据
*/
private final ConcurrentHashMap<Key, AtomicReference<Data>> monitorData
= new ConcurrentHashMap<Key, AtomicReference<Data>>();
private double div(double a, double b) {
if (b == 0) {
return 0;
}
return a / b;
}
@Override
public void create() {
timer = new Timer("Timer-for-greys-monitor-" + session.getSessionId(), true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// if (monitorData.isTop()) {
// return;
// }
final TTable tTable = new TTable(10)
.addRow(
"TIMESTAMP",
"CLASS",
"METHOD",
"TOTAL",
"SUCCESS",
"FAIL",
"FAIL-RATE",
"AVG-RT(ms)",
"MIN-RT(ms)",
"MAX-RT(ms)"
);
for (Map.Entry<Key, AtomicReference<Data>> entry : monitorData.entrySet()) {
final AtomicReference<Data> value = entry.getValue();
Data data;
while (true) {
data = value.get();
if (value.compareAndSet(data, new Data())) {
break;
}
}
if (null != data) {
final DecimalFormat df = new DecimalFormat("00.00");
tTable.addRow(
SimpleDateFormatHolder.getInstance().format(new Date()),
entry.getKey().className,
entry.getKey().methodName,
data.total,
data.success,
data.failed,
df.format(100.0d * div(data.failed, data.total)) + "%",
df.format(div(data.cost, data.total)),
data.minCost,
data.maxCost
);
}
}
tTable.padding(1);
printer.println(tTable.rendering());
}
}, 0, cycle * 1000);
}
@Override
public void destroy() {
if (null != timer) {
timer.cancel();
}
}
private final InvokeCost invokeCost = new InvokeCost();
@Override
public void before(ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args) throws Throwable {
invokeCost.begin();
}
@Override
public void afterReturning(ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args, Object returnObject) throws Throwable {
finishing(className, methodName, true);
}
@Override
public void afterThrowing(ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args, Throwable throwable) throws Throwable {
finishing(className, methodName, false);
}
public void finishing(String className, String methodName, boolean isSuccess) throws Throwable {
final Key key = new Key(className, methodName);
final long cost = invokeCost.cost();
while (true) {
final AtomicReference<Data> value = monitorData.get(key);
if (null == value) {
monitorData.putIfAbsent(key, new AtomicReference<Data>(new Data()));
// 这里不去判断返回值,用continue去强制获取一次
continue;
}
while (true) {
Data oData = value.get();
Data nData = new Data();
nData.cost = oData.cost + cost;
if (!isSuccess) {
nData.failed = oData.failed + 1;
nData.success = oData.success;
} else {
nData.failed = oData.failed;
nData.success = oData.success + 1;
}
nData.total = oData.total + 1;
// setValue max-cost
if (null == oData.maxCost) {
nData.maxCost = cost;
} else {
nData.maxCost = Math.max(oData.maxCost, cost);
}
// setValue min-cost
if (null == oData.minCost) {
nData.minCost = cost;
} else {
nData.minCost = Math.min(oData.minCost, cost);
}
if (value.compareAndSet(oData, nData)) {
break;
}
}
break;
}
}
};
}
};
}
};
}
}