提交 07b657b0 编写于 作者: lakernote's avatar lakernote

增加长事务监控

上级 b1d376f9
package com.laker.admin.config;
import com.laker.admin.framework.ext.transaction.LakerDataSourceTransactionManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class LakerTransactionManagerConfig {
@Bean
DataSourceTransactionManager transactionManager(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = new LakerDataSourceTransactionManager(dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
}
......@@ -11,5 +11,6 @@ public enum SpanType {
Redis,
Remote,
Kafka,
Transaction,
Others;
}
\ No newline at end of file
......@@ -15,6 +15,8 @@ public class Trace {
*
*/
private int depth = 0;
private TreeView treeView = new TreeView(true, "");
/**
* 存储 span结果
*/
......
......@@ -2,7 +2,6 @@ package com.laker.admin.framework.aop.trace;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
......@@ -46,17 +45,27 @@ public class TraceContext {
span.setSpanType(spanType);
span.setStartTime(System.currentTimeMillis());
trace.addSpan(span);
trace.getTreeView().begin(spanType + "-" + spanName);
}
public static void stopSpan() {
stopSpan(1000);
}
public static void stopSpan(long time) {
Trace trace = traceThreadLocal.get();
Span current = trace.current();
current.setEndTime(System.currentTimeMillis());
current.setCost(current.getEndTime() - current.getStartTime());
trace.getTreeView().end();
if (trace.stopSpan()) {
if (current.getCost() > time) {
logSpan(trace.getSpans(), StringUtils.SPACE);
// 打印日志方式一
// logSpan(trace.getSpans(), StringUtils.SPACE);
// 打印日志方式二
String draw = trace.getTreeView().draw();
log.info(draw);
}
traceThreadLocal.remove();
}
......
package com.laker.admin.framework.aop.trace;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 树形控件 Created by vlinux on 15/5/26.
* 来自 阿里的arthas中的代码
*/
public class TreeView {
private static final String STEP_FIRST_CHAR = "`---";
private static final String STEP_NORMAL_CHAR = "+---";
private static final String STEP_HAS_BOARD = "| ";
private static final String STEP_EMPTY_BOARD = " ";
private static final String TIME_UNIT = "ms";
// 是否输出耗时
private final boolean isPrintCost;
// 根节点
private final Node root;
// 当前节点
private Node current;
// 最耗时的节点
private Node maxCost;
public TreeView(boolean isPrintCost, String title) {
this.root = new Node(title).markBegin().markEnd();
this.current = root;
this.isPrintCost = isPrintCost;
}
public String draw() {
findMaxCostNode(root);
final StringBuilder treeSB = new StringBuilder();
recursive(0, true, "", root, new Callback() {
@Override
public void callback(int deep, boolean isLast, String prefix,
Node node) {
treeSB.append(prefix).append(
isLast ? STEP_FIRST_CHAR : STEP_NORMAL_CHAR);
if (isPrintCost && !node.isRoot()) {
if (node == maxCost) {
// the node with max cost will be highlighted
treeSB.append("[max]:" + node.toString().toString());
} else {
treeSB.append(node.toString());
}
}
treeSB.append(node.data);
if (node.mark != null && node.mark.length() != 0) {
treeSB.append(" [").append(node.mark)
.append(node.marks > 1 ? "," + node.marks : "")
.append("]");
}
treeSB.append("\n");
}
});
return treeSB.toString();
}
/**
* 递归遍历
*/
private void recursive(int deep, boolean isLast, String prefix, Node node,
Callback callback) {
callback.callback(deep, isLast, prefix, node);
if (!node.isLeaf()) {
final int size = node.children.size();
for (int index = 0; index < size; index++) {
final boolean isLastFlag = index == size - 1;
final String currentPrefix = isLast ? prefix + STEP_EMPTY_BOARD
: prefix + STEP_HAS_BOARD;
recursive(deep + 1, isLastFlag, currentPrefix,
node.children.get(index), callback);
}
}
}
/**
* 查找耗时最大的节点,便于后续高亮展示
*
* @param node
*/
private void findMaxCostNode(Node node) {
if (!node.isRoot() && !node.parent.isRoot()) {
if (maxCost == null) {
maxCost = node;
} else if (maxCost.totalCost < node.totalCost) {
maxCost = node;
}
}
if (!node.isLeaf()) {
for (Node n : node.children) {
findMaxCostNode(n);
}
}
}
/**
* 创建一个分支节点
*
* @param data 节点数据
* @return this
*/
public TreeView begin(String data) {
Node n = current.find(data);
if (n != null) {
current = n;
} else {
current = new Node(current, data);
}
current.markBegin();
return this;
}
/**
* 结束一个分支节点
*
* @return this
*/
public TreeView end() {
if (current.isRoot()) {
throw new IllegalStateException("current node is root.");
}
current.markEnd();
current = current.parent;
return this;
}
/**
* 结束一个分支节点,并带上备注
*
* @return this
*/
public TreeView end(String mark) {
if (current.isRoot()) {
throw new IllegalStateException("current node is root.");
}
current.markEnd().mark(mark);
current = current.parent;
return this;
}
/**
* 树节点
*/
private static class Node {
/**
* 父节点
*/
final Node parent;
/**
* 节点数据
*/
final String data;
/**
* 子节点
*/
final List<Node> children = new ArrayList<Node>();
final Map<String, Node> map = new HashMap<String, Node>();
/**
* 开始时间戳
*/
private long beginTimestamp;
/**
* 结束时间戳
*/
private long endTimestamp;
/**
* 备注
*/
private String mark;
/**
* 构造树节点(根节点)
*/
private Node(String data) {
this.parent = null;
this.data = data;
}
/**
* 构造树节点
*
* @param parent 父节点
* @param data 节点数据
*/
private Node(Node parent, String data) {
this.parent = parent;
this.data = data;
parent.children.add(this);
parent.map.put(data, this);
}
/**
* 查找已经存在的节点
*/
Node find(String data) {
return map.get(data);
}
/**
* 是否根节点
*
* @return true / false
*/
boolean isRoot() {
return null == parent;
}
/**
* 是否叶子节点
*
* @return true / false
*/
boolean isLeaf() {
return children.isEmpty();
}
Node markBegin() {
beginTimestamp = System.nanoTime();
return this;
}
Node markEnd() {
endTimestamp = System.nanoTime();
long cost = getCost();
if (cost < minCost) {
minCost = cost;
}
if (cost > maxCost) {
maxCost = cost;
}
times++;
totalCost += cost;
return this;
}
Node mark(String mark) {
this.mark = mark;
marks++;
return this;
}
long getCost() {
return endTimestamp - beginTimestamp;
}
/**
* convert nano-seconds to milli-seconds
*/
double getCostInMillis(long nanoSeconds) {
return nanoSeconds / 1000000.0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (times <= 1) {
sb.append("[").append(getCostInMillis(getCost()))
.append(TIME_UNIT).append("] ");
} else {
sb.append("[min=").append(getCostInMillis(minCost))
.append(TIME_UNIT).append(",max=")
.append(getCostInMillis(maxCost)).append(TIME_UNIT)
.append(",total=").append(getCostInMillis(totalCost))
.append(TIME_UNIT).append(",count=").append(times)
.append("] ");
}
return sb.toString();
}
/**
* 合并统计相同调用,并计算最小\最大\总耗时
*/
private long minCost = Long.MAX_VALUE;
private long maxCost = Long.MIN_VALUE;
private long totalCost = 0;
private long times = 0;
private long marks = 0;
}
/**
* 遍历回调接口
*/
private interface Callback {
void callback(int deep, boolean isLast, String prefix, Node node);
}
}
package com.laker.admin.framework.ext.transaction;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.laker.admin.framework.aop.trace.SpanType;
import com.laker.admin.framework.aop.trace.TraceContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import javax.sql.DataSource;
import java.util.Date;
/**
* @author: laker
* @date: 2022/10/17
**/
@Slf4j
public class LakerDataSourceTransactionManager extends DataSourceTransactionManager {
ThreadLocal<Transaction> dateThreadLocal = new ThreadLocal<>();
public LakerDataSourceTransactionManager(DataSource dataSource) {
super(dataSource);
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
String name = definition.getName();
TraceContext.addSpan("LakerDataSourceTransactionManager.doBegin", SpanType.Transaction);
dateThreadLocal.set(new Transaction(new Date(), name));
super.doBegin(transaction, definition);
}
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
TraceContext.stopSpan();
Transaction transactionT = dateThreadLocal.get();
log.info("transaction:{},time:{}ms", transactionT.name, DateUtil.between(new Date(), transactionT.begin, DateUnit.MS));
dateThreadLocal.remove();
}
class Transaction {
Date begin;
String name;
Transaction(Date begin, String name) {
this.begin = begin;
this.name = name;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册