mirror of
https://gitee.com/huoyo/ko-time.git
synced 2025-12-07 01:08:26 +08:00
添加配置项
This commit is contained in:
parent
2360dfcd55
commit
b159bf2288
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>cn.langpy</groupId>
|
<groupId>cn.langpy</groupId>
|
||||||
<artifactId>ko-time</artifactId>
|
<artifactId>ko-time</artifactId>
|
||||||
<version>1.0</version>
|
<version>1.1</version>
|
||||||
<name>koTime</name>
|
<name>koTime</name>
|
||||||
<description>koTime</description>
|
<description>koTime</description>
|
||||||
<licenses>
|
<licenses>
|
||||||
|
|||||||
52
src/main/java/cn/langpy/kotime/config/DefaultConfig.java
Normal file
52
src/main/java/cn/langpy/kotime/config/DefaultConfig.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package cn.langpy.kotime.config;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.langpy.kotime.model.KoTimeConfig;
|
||||||
|
import cn.langpy.kotime.util.Context;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class DefaultConfig {
|
||||||
|
@Value("${koTime.log.language:chinese}")
|
||||||
|
private String logLanguage;
|
||||||
|
@Value("${koTime.log.enable:false}")
|
||||||
|
private Boolean logEnable;
|
||||||
|
@Value("${koTime.time.threshold:800.0}")
|
||||||
|
private Double timeThreshold;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void function() {
|
||||||
|
KoTimeConfig config = new KoTimeConfig();
|
||||||
|
config.setLogEnable(logEnable);
|
||||||
|
config.setLogLanguage(logLanguage);
|
||||||
|
config.setTimeThreshold(timeThreshold);
|
||||||
|
Context.setConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getTimeThreshold() {
|
||||||
|
return timeThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeThreshold(Double timeThreshold) {
|
||||||
|
this.timeThreshold = timeThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogLanguage() {
|
||||||
|
return logLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogLanguage(String logLanguage) {
|
||||||
|
this.logLanguage = logLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getLogEnable() {
|
||||||
|
return logEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogEnable(Boolean logEnable) {
|
||||||
|
this.logEnable = logEnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,8 @@ package cn.langpy.kotime.controller;
|
|||||||
|
|
||||||
import cn.langpy.kotime.model.RunTimeNode;
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
import cn.langpy.kotime.model.SystemStatistic;
|
import cn.langpy.kotime.model.SystemStatistic;
|
||||||
|
import cn.langpy.kotime.service.RunTimeNodeService;
|
||||||
import cn.langpy.kotime.util.Context;
|
import cn.langpy.kotime.util.Context;
|
||||||
import cn.langpy.kotime.util.MethodType;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@ -20,16 +20,17 @@ import java.util.List;
|
|||||||
public class KoTimeController {
|
public class KoTimeController {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String index(Model model, HttpServletRequest request) {
|
public String index(Model model, HttpServletRequest request) {
|
||||||
List<RunTimeNode> list = Context.get(MethodType.Controller);
|
List<RunTimeNode> list = RunTimeNodeService.getControllers();
|
||||||
model.addAttribute("list",list);
|
model.addAttribute("list",list);
|
||||||
SystemStatistic system = Context.getStatistic();
|
SystemStatistic system = RunTimeNodeService.getRunStatistic();
|
||||||
model.addAttribute("system",system);
|
model.addAttribute("system",system);
|
||||||
|
model.addAttribute("config",Context.getConfig());
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
@GetMapping("/getTree")
|
@GetMapping("/getTree")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public RunTimeNode getTree(String methodName,Model model, HttpServletRequest request) {
|
public RunTimeNode getTree(String methodName,Model model, HttpServletRequest request) {
|
||||||
return Context.getTree(methodName);
|
return RunTimeNodeService.getGraph(methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ public class ComputeTimeHandler {
|
|||||||
long begin = System.nanoTime();
|
long begin = System.nanoTime();
|
||||||
Object obj=pjp.proceed();
|
Object obj=pjp.proceed();
|
||||||
long end =System.nanoTime();
|
long end =System.nanoTime();
|
||||||
|
StackTraceElement stacks[] = Thread.currentThread().getStackTrace();
|
||||||
if ("chinese".equals(computeTime.value())) {
|
if ("chinese".equals(computeTime.value())) {
|
||||||
log.info("调用方法={},耗时={}毫秒",pjp.getTarget().getClass().getName()+"."+pjp.getSignature().getName(),(end-begin)/1000000);
|
log.info("调用方法={},耗时={}毫秒",pjp.getTarget().getClass().getName()+"."+pjp.getSignature().getName(),(end-begin)/1000000);
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
@ -2,8 +2,7 @@ package cn.langpy.kotime.handler;
|
|||||||
|
|
||||||
|
|
||||||
import cn.langpy.kotime.model.RunTimeNode;
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
import cn.langpy.kotime.util.Common;
|
import cn.langpy.kotime.service.InvokeService;
|
||||||
import cn.langpy.kotime.util.Context;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
@ -11,7 +10,6 @@ import org.aspectj.lang.annotation.Pointcut;
|
|||||||
|
|
||||||
@Aspect
|
@Aspect
|
||||||
public interface ComputeTimeHandlerInterface {
|
public interface ComputeTimeHandlerInterface {
|
||||||
|
|
||||||
@Pointcut("")
|
@Pointcut("")
|
||||||
void prog();
|
void prog();
|
||||||
|
|
||||||
@ -21,9 +19,9 @@ public interface ComputeTimeHandlerInterface {
|
|||||||
Object obj=pjp.proceed();
|
Object obj=pjp.proceed();
|
||||||
long end =System.nanoTime();
|
long end =System.nanoTime();
|
||||||
String packName = this.getClass().getPackage().getName();
|
String packName = this.getClass().getPackage().getName();
|
||||||
RunTimeNode parent = Common.getParentRunTimeNode(packName);
|
RunTimeNode parent = InvokeService.getParentRunTimeNode(packName);
|
||||||
RunTimeNode current = Common.getCurrentRunTimeNode(pjp,((end-begin)/1000000.0));
|
RunTimeNode current = InvokeService.getCurrentRunTimeNode(pjp,((end-begin)/1000000.0));
|
||||||
Context.set(parent,current);
|
InvokeService.createGraph(parent,current);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
src/main/java/cn/langpy/kotime/model/KoTimeConfig.java
Normal file
41
src/main/java/cn/langpy/kotime/model/KoTimeConfig.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package cn.langpy.kotime.model;
|
||||||
|
|
||||||
|
|
||||||
|
public class KoTimeConfig {
|
||||||
|
private String logLanguage;
|
||||||
|
private Boolean logEnable;
|
||||||
|
private Double timeThreshold;
|
||||||
|
|
||||||
|
public Double getTimeThreshold() {
|
||||||
|
return timeThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeThreshold(Double timeThreshold) {
|
||||||
|
this.timeThreshold = timeThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogLanguage() {
|
||||||
|
return logLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogLanguage(String logLanguage) {
|
||||||
|
this.logLanguage = logLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getLogEnable() {
|
||||||
|
return logEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogEnable(Boolean logEnable) {
|
||||||
|
this.logEnable = logEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "KoTimeConfig{" +
|
||||||
|
"logLanguage='" + logLanguage + '\'' +
|
||||||
|
"timeThreshold='" + timeThreshold + '\'' +
|
||||||
|
", logEnable=" + logEnable +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -65,4 +65,17 @@ public class SystemStatistic {
|
|||||||
public void setNormalNum(Integer normalNum) {
|
public void setNormalNum(Integer normalNum) {
|
||||||
this.normalNum = normalNum;
|
this.normalNum = normalNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SystemStatistic{" +
|
||||||
|
"name='" + name + '\'' +
|
||||||
|
", avgRunTime=" + avgRunTime +
|
||||||
|
", maxRunTime=" + maxRunTime +
|
||||||
|
", minRunTime=" + minRunTime +
|
||||||
|
", totalNum=" + totalNum +
|
||||||
|
", delayNum=" + delayNum +
|
||||||
|
", normalNum=" + normalNum +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
src/main/java/cn/langpy/kotime/service/InvokeService.java
Normal file
65
src/main/java/cn/langpy/kotime/service/InvokeService.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package cn.langpy.kotime.service;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
|
import cn.langpy.kotime.util.Common;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class InvokeService {
|
||||||
|
|
||||||
|
public static RunTimeNode getParentRunTimeNode(String packName) {
|
||||||
|
String parentClassName = "";
|
||||||
|
String parentMothodName = "";
|
||||||
|
StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
|
||||||
|
StackTraceElement stack = Common.filter(stacks,packName);
|
||||||
|
if (stack!=null) {
|
||||||
|
parentClassName = stack.getClassName();
|
||||||
|
parentMothodName = stack.getMethodName();
|
||||||
|
}
|
||||||
|
RunTimeNode parent = new RunTimeNode();
|
||||||
|
parent.setClassName(parentClassName);
|
||||||
|
parent.setMethodName(parentMothodName);
|
||||||
|
parent.setName(parentClassName.substring(parentClassName.lastIndexOf(".")+1)+"."+parentMothodName);
|
||||||
|
parent.setMethodType(Common.getMethodType(parentClassName));
|
||||||
|
parent.setChildren(new ArrayList<>());
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RunTimeNode getCurrentRunTimeNode(ProceedingJoinPoint pjp, Double runTime) {
|
||||||
|
String className = pjp.getTarget().getClass().getName();
|
||||||
|
String methodName = pjp.getSignature().getName();
|
||||||
|
RunTimeNode current = new RunTimeNode();
|
||||||
|
current.setName(className.substring(className.lastIndexOf(".")+1)+"."+methodName);
|
||||||
|
current.setClassName(className);
|
||||||
|
current.setMethodName(methodName);
|
||||||
|
current.setAvgRunTime(runTime);
|
||||||
|
current.setChildren(new ArrayList<>());
|
||||||
|
current.setMethodType(Common.getMethodType(pjp));
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createGraph(RunTimeNode parent, RunTimeNode current) {
|
||||||
|
if (current.getMethodName().contains("$")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Common.showLog(current);
|
||||||
|
String parentKey = parent.getClassName()+"."+parent.getMethodName();
|
||||||
|
String currentKey = current.getClassName()+"."+current.getMethodName();
|
||||||
|
if (".".equals(parentKey)) {
|
||||||
|
RunTimeNodeService.addOrUpdate(currentKey,current);
|
||||||
|
}else if (RunTimeNodeService.containsNode(parent)) {
|
||||||
|
RunTimeNodeService.addOrUpdateChildren(parent,current);
|
||||||
|
}else{
|
||||||
|
List<RunTimeNode> list = new ArrayList<>();
|
||||||
|
list.add(current);
|
||||||
|
parent.setChildren(list);
|
||||||
|
RunTimeNodeService.add(parentKey,parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
package cn.langpy.kotime.service;
|
||||||
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
|
import cn.langpy.kotime.model.SystemStatistic;
|
||||||
|
import cn.langpy.kotime.util.Context;
|
||||||
|
import cn.langpy.kotime.util.GraphMap;
|
||||||
|
import cn.langpy.kotime.util.MethodType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class RunTimeNodeService {
|
||||||
|
|
||||||
|
public static void addOrUpdate(String key, RunTimeNode runTimeNode) {
|
||||||
|
if (GraphMap.containsKey(key)) {
|
||||||
|
GraphMap.get(key).setAvgRunTime(runTimeNode.getAvgRunTime());
|
||||||
|
}else{
|
||||||
|
GraphMap.put(key,runTimeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void add(String key, RunTimeNode runTimeNode) {
|
||||||
|
GraphMap.put(key,runTimeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containsKey(String key) {
|
||||||
|
return GraphMap.containsKey(key);
|
||||||
|
}
|
||||||
|
public static boolean containsNode(RunTimeNode node) {
|
||||||
|
String key = node.getClassName()+"."+node.getMethodName();
|
||||||
|
return GraphMap.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RunTimeNode getRunTimeNode(String key) {
|
||||||
|
return GraphMap.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addOrUpdateChildren(RunTimeNode parent, RunTimeNode current) {
|
||||||
|
String parentKey = parent.getClassName()+"."+parent.getMethodName();
|
||||||
|
RunTimeNode hisRunTimeNode = RunTimeNodeService.getRunTimeNode(parentKey);
|
||||||
|
List<RunTimeNode> hisRunTimeNodeChildren = hisRunTimeNode.getChildren();
|
||||||
|
if (hisRunTimeNodeChildren!=null) {
|
||||||
|
if (hisRunTimeNodeChildren.contains(current)) {
|
||||||
|
updateChildren(current,hisRunTimeNodeChildren);
|
||||||
|
}else{
|
||||||
|
hisRunTimeNodeChildren.add(current);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<RunTimeNode> list = new ArrayList<>();
|
||||||
|
list.add(current);
|
||||||
|
hisRunTimeNode.setChildren(list);
|
||||||
|
}
|
||||||
|
GraphMap.put(parentKey,hisRunTimeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateChildren(RunTimeNode child, List<RunTimeNode> hisRunTimeNodeChildren) {
|
||||||
|
int hisLength = hisRunTimeNodeChildren.size();
|
||||||
|
for (int i = 0; i < hisLength; i++) {
|
||||||
|
if (hisRunTimeNodeChildren.get(i)==child) {
|
||||||
|
child.setAvgRunTime((child.getAvgRunTime()+hisRunTimeNodeChildren.get(i).getAvgRunTime())/2.0);
|
||||||
|
hisRunTimeNodeChildren.set(i,child) ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static SystemStatistic getRunStatistic() {
|
||||||
|
List<RunTimeNode> controllerApis = GraphMap.get(MethodType.Controller);
|
||||||
|
SystemStatistic systemStatistic = new SystemStatistic();
|
||||||
|
int delayNum = (int)controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() >= Context.getConfig().getTimeThreshold()).count();
|
||||||
|
systemStatistic.setDelayNum(delayNum);
|
||||||
|
int normalNum = (int)controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() < Context.getConfig().getTimeThreshold()).count();
|
||||||
|
systemStatistic.setNormalNum(normalNum);
|
||||||
|
int totalNum = (int)controllerApis.stream().count();
|
||||||
|
systemStatistic.setTotalNum(totalNum);
|
||||||
|
Double max = controllerApis.stream().map(api->api.getAvgRunTime()).max(Double::compareTo).get();
|
||||||
|
Double min = controllerApis.stream().map(api->api.getAvgRunTime()).min(Double::compareTo).get();
|
||||||
|
Double avg = controllerApis.stream().map(api->api.getAvgRunTime()).collect(Collectors.averagingDouble(Double::doubleValue));
|
||||||
|
systemStatistic.setMaxRunTime(max);
|
||||||
|
systemStatistic.setMinRunTime(min);
|
||||||
|
systemStatistic.setAvgRunTime(avg);
|
||||||
|
return systemStatistic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<RunTimeNode> getControllers() {
|
||||||
|
List<RunTimeNode> list = GraphMap.get(MethodType.Controller);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
public static RunTimeNode getGraph(String methodName) {
|
||||||
|
return GraphMap.getTree(methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,52 +1,30 @@
|
|||||||
package cn.langpy.kotime.util;
|
package cn.langpy.kotime.util;
|
||||||
|
|
||||||
import cn.langpy.kotime.model.RunTimeNode;
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
@Slf4j
|
||||||
|
|
||||||
public class Common {
|
public class Common {
|
||||||
|
|
||||||
public static RunTimeNode getParentRunTimeNode(String packName) {
|
public static StackTraceElement filter(StackTraceElement[] stacks,String packName) {
|
||||||
String parentClassName = "";
|
|
||||||
String parentMothodName = "";
|
|
||||||
StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
|
|
||||||
String[] packNameSplit = packName.split("\\.");
|
String[] packNameSplit = packName.split("\\.");
|
||||||
String filter = packNameSplit.length>1 ? packNameSplit[0]+"."+packNameSplit[1] : packNameSplit[0];
|
String filter = packNameSplit.length>1 ? packNameSplit[0]+"."+packNameSplit[1] : packNameSplit[0];
|
||||||
int stacksLength = stacks.length;
|
int stacksLength = stacks.length;
|
||||||
for (int i = 0; i < stacksLength; i++) {
|
for (int i = 0; i < stacksLength; i++) {
|
||||||
StackTraceElement stack = stacks[i];
|
StackTraceElement stack = stacks[i];
|
||||||
if (stack.getClassName().startsWith(filter)&& !stack.getClassName().contains("$")) {
|
if (stack.getClassName().startsWith(filter)&& !stack.getClassName().contains("$")) {
|
||||||
parentClassName = stack.getClassName();
|
return stack;
|
||||||
parentMothodName = stack.getMethodName();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RunTimeNode parent = new RunTimeNode();
|
return null;
|
||||||
parent.setClassName(parentClassName);
|
|
||||||
parent.setMethodName(parentMothodName);
|
|
||||||
parent.setName(parentClassName.substring(parentClassName.lastIndexOf(".")+1)+"."+parentMothodName);
|
|
||||||
parent.setMethodType(getMethodType(parentClassName));
|
|
||||||
parent.setChildren(new ArrayList<>());
|
|
||||||
return parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RunTimeNode getCurrentRunTimeNode(ProceedingJoinPoint pjp,Double runTime) {
|
|
||||||
String className = pjp.getTarget().getClass().getName();
|
|
||||||
String methodName = pjp.getSignature().getName();
|
|
||||||
RunTimeNode current = new RunTimeNode();
|
|
||||||
current.setName(className.substring(className.lastIndexOf(".")+1)+"."+methodName);
|
|
||||||
current.setClassName(className);
|
|
||||||
current.setMethodName(methodName);
|
|
||||||
current.setAvgRunTime(runTime);
|
|
||||||
current.setChildren(new ArrayList<>());
|
|
||||||
current.setMethodType(getMethodType(pjp));
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodType getMethodType(ProceedingJoinPoint pjp) {
|
public static MethodType getMethodType(ProceedingJoinPoint pjp) {
|
||||||
MethodType methodType = null;
|
MethodType methodType = null;
|
||||||
@ -64,7 +42,7 @@ public class Common {
|
|||||||
methodType = MethodType.Controller;
|
methodType = MethodType.Controller;
|
||||||
}else if (className.contains("service")) {
|
}else if (className.contains("service")) {
|
||||||
methodType = MethodType.Service;
|
methodType = MethodType.Service;
|
||||||
}else if (className.contains("dao") || className.contains("mapper")) {
|
}else if (className.contains("dao") || className.contains("mapper")|| className.contains( "com.sun.proxy.$Proxy")) {
|
||||||
methodType = MethodType.Dao;
|
methodType = MethodType.Dao;
|
||||||
}else{
|
}else{
|
||||||
methodType = MethodType.Others;
|
methodType = MethodType.Others;
|
||||||
@ -80,7 +58,7 @@ public class Common {
|
|||||||
methodType = MethodType.Controller;
|
methodType = MethodType.Controller;
|
||||||
}else if (className.contains("service")) {
|
}else if (className.contains("service")) {
|
||||||
methodType = MethodType.Service;
|
methodType = MethodType.Service;
|
||||||
}else if (className.contains("dao") || className.contains("mapper")) {
|
}else if (className.contains("dao") || className.contains("mapper")|| className.contains( "com.sun.proxy.$Proxy")) {
|
||||||
methodType = MethodType.Dao;
|
methodType = MethodType.Dao;
|
||||||
}else{
|
}else{
|
||||||
methodType = MethodType.Others;
|
methodType = MethodType.Others;
|
||||||
@ -88,4 +66,14 @@ public class Common {
|
|||||||
return methodType;
|
return methodType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void showLog(RunTimeNode current) {
|
||||||
|
String currentKey = current.getClassName()+"."+current.getMethodName();
|
||||||
|
if (Context.getConfig().getLogEnable() && "chinese".equals(Context.getConfig().getLogLanguage())) {
|
||||||
|
log.info("调用方法="+currentKey+",耗时="+current.getAvgRunTime()+"毫秒");
|
||||||
|
}else if (Context.getConfig().getLogEnable() && "english".equals(Context.getConfig().getLogLanguage())) {
|
||||||
|
log.info("method="+currentKey+",runTime="+current.getAvgRunTime()+"ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,112 +1,16 @@
|
|||||||
package cn.langpy.kotime.util;
|
package cn.langpy.kotime.util;
|
||||||
|
import cn.langpy.kotime.model.KoTimeConfig;
|
||||||
import cn.langpy.kotime.model.RunTimeNode;
|
|
||||||
import cn.langpy.kotime.model.SystemStatistic;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Context {
|
public class Context {
|
||||||
|
private static KoTimeConfig config;
|
||||||
|
|
||||||
private static Map<String, RunTimeNode> runTimeNodeMap;
|
public static void setConfig(KoTimeConfig koTimeConfig) {
|
||||||
static {
|
config = koTimeConfig;
|
||||||
runTimeNodeMap = new HashMap<>();
|
}
|
||||||
|
public static KoTimeConfig getConfig() {
|
||||||
|
return config ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void set(RunTimeNode parent,RunTimeNode current) {
|
|
||||||
|
|
||||||
String parentKey = parent.getClassName()+"."+parent.getMethodName();
|
|
||||||
String currentKey = current.getClassName()+"."+current.getMethodName();
|
|
||||||
if (!currentKey.contains("$")) {
|
|
||||||
log.info("调用方法="+currentKey+",耗时="+current.getAvgRunTime()+"毫秒");
|
|
||||||
}else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (".".equals(parentKey)) {
|
|
||||||
if (runTimeNodeMap.containsKey(currentKey)) {
|
|
||||||
runTimeNodeMap.get(currentKey).setAvgRunTime(current.getAvgRunTime());
|
|
||||||
}else{
|
|
||||||
runTimeNodeMap.put(currentKey,current);
|
|
||||||
}
|
|
||||||
}else if (runTimeNodeMap.containsKey(parentKey)) {
|
|
||||||
RunTimeNode hisRunTimeNode = runTimeNodeMap.get(parentKey);
|
|
||||||
List<RunTimeNode> hisRunTimeNodeChildren = hisRunTimeNode.getChildren();
|
|
||||||
if (hisRunTimeNodeChildren!=null) {
|
|
||||||
if (hisRunTimeNodeChildren.contains(current)) {
|
|
||||||
int hisLength = hisRunTimeNodeChildren.size();
|
|
||||||
for (int i = 0; i < hisLength; i++) {
|
|
||||||
if (hisRunTimeNodeChildren.get(i)==current) {
|
|
||||||
current.setAvgRunTime((current.getAvgRunTime()+hisRunTimeNode.getChildren().get(i).getAvgRunTime())/2.0);
|
|
||||||
hisRunTimeNodeChildren.set(i,current) ;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
hisRunTimeNodeChildren.add(current);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
List<RunTimeNode> list = new ArrayList<>();
|
|
||||||
list.add(current);
|
|
||||||
hisRunTimeNode.setChildren(list);
|
|
||||||
}
|
|
||||||
runTimeNodeMap.put(parentKey,hisRunTimeNode);
|
|
||||||
}else{
|
|
||||||
List<RunTimeNode> list = new ArrayList<>();
|
|
||||||
list.add(current);
|
|
||||||
parent.setChildren(list);
|
|
||||||
runTimeNodeMap.put(parentKey,parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RunTimeNode get(String key) {
|
|
||||||
return runTimeNodeMap.get(key);
|
|
||||||
}
|
|
||||||
public static List<RunTimeNode> get(MethodType methodType) {
|
|
||||||
return runTimeNodeMap.values().stream()
|
|
||||||
.filter(runTimeNode -> runTimeNode.getMethodType()==methodType)
|
|
||||||
.sorted(Comparator.reverseOrder())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
public static RunTimeNode getTree(String key) {
|
|
||||||
RunTimeNode root = runTimeNodeMap.get(key);
|
|
||||||
if (root==null) {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
root.setValue(root.getAvgRunTime());
|
|
||||||
List<RunTimeNode> children = root.getChildren();
|
|
||||||
if (children!=null&&children.size()>0) {
|
|
||||||
children.forEach(child->{
|
|
||||||
String childKey = child.getClassName()+"."+child.getMethodName();
|
|
||||||
RunTimeNode newChild = getTree(childKey);
|
|
||||||
if (newChild!=null) {
|
|
||||||
child.setChildren(newChild.getChildren());
|
|
||||||
child.setValue(child.getAvgRunTime());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SystemStatistic getStatistic() {
|
|
||||||
List<RunTimeNode> controllerApis = get(MethodType.Controller);
|
|
||||||
SystemStatistic systemStatistic = new SystemStatistic();
|
|
||||||
int delayNum = (int)controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() >= 800).count();
|
|
||||||
systemStatistic.setDelayNum(delayNum);
|
|
||||||
int normalNum = (int)controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() < 800).count();
|
|
||||||
systemStatistic.setNormalNum(normalNum);
|
|
||||||
int totalNum = (int)controllerApis.stream().count();
|
|
||||||
systemStatistic.setTotalNum(totalNum);
|
|
||||||
Double max = controllerApis.stream().map(api->api.getAvgRunTime()).max(Double::compareTo).get();
|
|
||||||
Double min = controllerApis.stream().map(api->api.getAvgRunTime()).min(Double::compareTo).get();
|
|
||||||
Double avg = controllerApis.stream().map(api->api.getAvgRunTime()).collect(Collectors.averagingDouble(Double::doubleValue));
|
|
||||||
systemStatistic.setMaxRunTime(max);
|
|
||||||
systemStatistic.setMinRunTime(min);
|
|
||||||
systemStatistic.setAvgRunTime(avg);
|
|
||||||
return systemStatistic;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/main/java/cn/langpy/kotime/util/GraphMap.java
Normal file
57
src/main/java/cn/langpy/kotime/util/GraphMap.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package cn.langpy.kotime.util;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.langpy.kotime.model.RunTimeNode;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class GraphMap {
|
||||||
|
/*只需保证可见性,无需保证线程安全*/
|
||||||
|
private volatile static Map<String, RunTimeNode> runTimeNodeMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
runTimeNodeMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RunTimeNode get(String key) {
|
||||||
|
return runTimeNodeMap.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RunTimeNode put(String key, RunTimeNode runTimeNode) {
|
||||||
|
return runTimeNodeMap.put(key,runTimeNode);
|
||||||
|
}
|
||||||
|
public static boolean containsKey(String key) {
|
||||||
|
return GraphMap.runTimeNodeMap.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<RunTimeNode> get(MethodType methodType) {
|
||||||
|
return runTimeNodeMap.values().stream()
|
||||||
|
.filter(runTimeNode -> runTimeNode.getMethodType()==methodType)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RunTimeNode getTree(String key) {
|
||||||
|
RunTimeNode root = runTimeNodeMap.get(key);
|
||||||
|
if (root==null) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
root.setValue(root.getAvgRunTime());
|
||||||
|
List<RunTimeNode> children = root.getChildren();
|
||||||
|
if (children!=null&&children.size()>0) {
|
||||||
|
children.forEach(child->{
|
||||||
|
String childKey = child.getClassName()+"."+child.getMethodName();
|
||||||
|
RunTimeNode newChild = getTree(childKey);
|
||||||
|
if (newChild!=null) {
|
||||||
|
child.setChildren(newChild.getChildren());
|
||||||
|
child.setValue(child.getAvgRunTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/main/resources/static/config.js
Normal file
98
src/main/resources/static/config.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
function getOption(data){
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
triggerOn: 'mousemove'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'tree',
|
||||||
|
// initialTreeDepth: 3,
|
||||||
|
data: [data],
|
||||||
|
top: '1%',
|
||||||
|
left: '15%',
|
||||||
|
bottom: '1%',
|
||||||
|
right: '10%',
|
||||||
|
roam: true,
|
||||||
|
symbolSize: 20,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'green'
|
||||||
|
},
|
||||||
|
|
||||||
|
label: {
|
||||||
|
position: 'right',
|
||||||
|
formatter: function(params){
|
||||||
|
var bg = "titleBgGreen"
|
||||||
|
if (params.value>800) {
|
||||||
|
bg = "titleBgRed"
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'{'+bg+'| 指标}',
|
||||||
|
' {aa|}方法:'+params.name+" ",
|
||||||
|
'{hr|}',
|
||||||
|
' {aa|}耗时: '+params.data.avgRunTime+" ms ",
|
||||||
|
'{hr|}',
|
||||||
|
' {aa|}类型: '+params.data.methodType+" "
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
backgroundColor: '#ddd',
|
||||||
|
borderColor: '#88e781',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 5,
|
||||||
|
color: '#000',
|
||||||
|
fontSize: 12,
|
||||||
|
rich: {
|
||||||
|
titleBgGreen: {
|
||||||
|
align: 'left',
|
||||||
|
backgroundColor: '#59977e',
|
||||||
|
height: 20,
|
||||||
|
borderRadius: [5, 5, 0, 0],
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
width: '100%',
|
||||||
|
color: '#eee'
|
||||||
|
},
|
||||||
|
titleBgRed: {
|
||||||
|
align: 'left',
|
||||||
|
backgroundColor: '#dc1d16',
|
||||||
|
height: 20,
|
||||||
|
borderRadius: [5, 5, 0, 0],
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
width: '100%',
|
||||||
|
color: '#eee'
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
borderColor: '#777',
|
||||||
|
width: '100%',
|
||||||
|
borderWidth: 0.5,
|
||||||
|
height: 0
|
||||||
|
},
|
||||||
|
aa: {
|
||||||
|
lineHeight: 20,
|
||||||
|
borderColor: '#111111',
|
||||||
|
height: 20,
|
||||||
|
borderRadius: [5, 5, 0, 0],
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
width: '0%'
|
||||||
|
|
||||||
|
},
|
||||||
|
t: {
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
leaves: {
|
||||||
|
label: {
|
||||||
|
position: 'right',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
align: 'left'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expandAndCollapse: true,
|
||||||
|
animationDuration: 550,
|
||||||
|
animationDurationUpdate: 750
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -48,15 +48,15 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<ul class="site-doc-icon site-doc-anim">
|
<ul class="site-doc-icon site-doc-anim">
|
||||||
<li>
|
<li>
|
||||||
<div class="layui-anim" style=" <#if system.avgRunTime gt 800 >background-color: #da3f0b;</#if>" data-anim="layui-anim-up">${system.avgRunTime}</div>
|
<div class="layui-anim" style=" <#if system.avgRunTime gt config.timeThreshold >background-color: #da3f0b;</#if>" data-anim="layui-anim-up">${system.avgRunTime}</div>
|
||||||
<div class="code">平均响应(ms)</div>
|
<div class="code">平均响应(ms)</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="layui-anim" style=" <#if system.maxRunTime gt 800 >background-color: #da3f0b;</#if>" data-anim="layui-anim-upbit">${system.maxRunTime}</div>
|
<div class="layui-anim" style=" <#if system.maxRunTime gt config.timeThreshold >background-color: #da3f0b;</#if>" data-anim="layui-anim-upbit">${system.maxRunTime}</div>
|
||||||
<div class="code">最大响应(ms)</div>
|
<div class="code">最大响应(ms)</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="layui-anim" style=" <#if system.minRunTime gt 800 >background-color: #da3f0b;</#if>" data-anim="layui-anim-scale">${system.minRunTime}</div>
|
<div class="layui-anim" style=" <#if system.minRunTime gt config.timeThreshold >background-color: #da3f0b;</#if>" data-anim="layui-anim-scale">${system.minRunTime}</div>
|
||||||
<div class="code">最小响应(ms)</div>
|
<div class="code">最小响应(ms)</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -68,7 +68,7 @@
|
|||||||
<div class="layui-colla-item" >
|
<div class="layui-colla-item" >
|
||||||
<h2 class="layui-colla-title" id="${runtime.className}.${runtime.methodName}">
|
<h2 class="layui-colla-title" id="${runtime.className}.${runtime.methodName}">
|
||||||
${runtime.className}#${runtime.methodName} 
|
${runtime.className}#${runtime.methodName} 
|
||||||
<span class="layui-badge <#if runtime.avgRunTime gt 800 >layui-bg-red
|
<span class="layui-badge <#if runtime.avgRunTime gt config.timeThreshold >layui-bg-red
|
||||||
<#else>layui-bg-green
|
<#else>layui-bg-green
|
||||||
</#if>">平均响应 ${runtime.avgRunTime} 毫秒</span>
|
</#if>">平均响应 ${runtime.avgRunTime} 毫秒</span>
|
||||||
</h2>
|
</h2>
|
||||||
@ -100,26 +100,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">-->
|
|
||||||
<!-- <legend>接口方法列表</legend>-->
|
|
||||||
<!--</fieldset>-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="${ctx.contextPath}/static/layui/layui.js" charset="utf-8"></script>
|
<script src="${ctx.contextPath}/static/layui/layui.js" charset="utf-8"></script>
|
||||||
<script src="${ctx.contextPath}/static/jquery.min.js"></script>
|
<script src="${ctx.contextPath}/static/jquery.min.js"></script>
|
||||||
<script src="${ctx.contextPath}/static/echarts.min.js"></script>
|
<script src="${ctx.contextPath}/static/echarts.min.js"></script>
|
||||||
|
<script src="${ctx.contextPath}/static/config.js"></script>
|
||||||
<!--<script src="https://cdn.jsdelivr.net/npm/echarts@4/dist/echarts.min.js?_v_=1607268016278" rel="external nofollow" ></script>-->
|
<script >
|
||||||
<!-- 注意:如果你直接复制所有代码到本地,上述js路径需要改成你本地的 -->
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
layui.use(['element', 'layer'], function () {
|
layui.use(['element', 'layer'], function () {
|
||||||
var element = layui.element;
|
var element = layui.element;
|
||||||
var layer = layui.layer;
|
var layer = layui.layer;
|
||||||
|
|
||||||
//监听折叠
|
|
||||||
element.on('collapse(test)', function (data) {
|
element.on('collapse(test)', function (data) {
|
||||||
id = data.title['0'].id
|
id = data.title['0'].id
|
||||||
show(data.content['0'],id)
|
show(data.content['0'],id)
|
||||||
@ -135,102 +127,7 @@
|
|||||||
echarts.util.each(data.children, function (datum, index) {
|
echarts.util.each(data.children, function (datum, index) {
|
||||||
index % 2 === 0 && (datum.collapsed = true);
|
index % 2 === 0 && (datum.collapsed = true);
|
||||||
});
|
});
|
||||||
|
myChart.setOption(option = getOption(data));
|
||||||
myChart.setOption(option = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
triggerOn: 'mousemove'
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'tree',
|
|
||||||
// initialTreeDepth: 3,
|
|
||||||
data: [data],
|
|
||||||
top: '1%',
|
|
||||||
left: '15%',
|
|
||||||
bottom: '1%',
|
|
||||||
right: '10%',
|
|
||||||
roam: true,
|
|
||||||
symbolSize: 20,
|
|
||||||
itemStyle: {
|
|
||||||
borderColor: 'green'
|
|
||||||
},
|
|
||||||
|
|
||||||
label: {
|
|
||||||
position: 'right',
|
|
||||||
formatter: function(params){
|
|
||||||
var bg = "titleBgGreen"
|
|
||||||
if (params.value>800) {
|
|
||||||
bg = "titleBgRed"
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
'{'+bg+'| 指标}',
|
|
||||||
' {aa|}方法:'+params.name+" ",
|
|
||||||
'{hr|}',
|
|
||||||
' {aa|}耗时: '+params.data.avgRunTime+" ms ",
|
|
||||||
'{hr|}',
|
|
||||||
' {aa|}类型: '+params.data.methodType+" "
|
|
||||||
].join('\n');
|
|
||||||
},
|
|
||||||
backgroundColor: '#ddd',
|
|
||||||
borderColor: '#88e781',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 5,
|
|
||||||
color: '#000',
|
|
||||||
fontSize: 12,
|
|
||||||
rich: {
|
|
||||||
titleBgGreen: {
|
|
||||||
align: 'left',
|
|
||||||
backgroundColor: '#59977e',
|
|
||||||
height: 20,
|
|
||||||
borderRadius: [5, 5, 0, 0],
|
|
||||||
padding: [0, 0, 0, 0],
|
|
||||||
width: '100%',
|
|
||||||
color: '#eee'
|
|
||||||
},
|
|
||||||
titleBgRed: {
|
|
||||||
align: 'left',
|
|
||||||
backgroundColor: '#dc1d16',
|
|
||||||
height: 20,
|
|
||||||
borderRadius: [5, 5, 0, 0],
|
|
||||||
padding: [0, 0, 0, 0],
|
|
||||||
width: '100%',
|
|
||||||
color: '#eee'
|
|
||||||
},
|
|
||||||
hr: {
|
|
||||||
borderColor: '#777',
|
|
||||||
width: '100%',
|
|
||||||
borderWidth: 0.5,
|
|
||||||
height: 0
|
|
||||||
},
|
|
||||||
aa: {
|
|
||||||
lineHeight: 20,
|
|
||||||
borderColor: '#111111',
|
|
||||||
height: 20,
|
|
||||||
borderRadius: [5, 5, 0, 0],
|
|
||||||
padding: [0, 0, 0, 0],
|
|
||||||
width: '0%'
|
|
||||||
|
|
||||||
},
|
|
||||||
t: {
|
|
||||||
align: 'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
leaves: {
|
|
||||||
label: {
|
|
||||||
position: 'right',
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
align: 'left'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
expandAndCollapse: true,
|
|
||||||
animationDuration: 550,
|
|
||||||
animationDurationUpdate: 750
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,5 +135,6 @@
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user