diff --git a/pom.xml b/pom.xml index 3146cde..4a66692 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ cn.langpy ko-time - 2.0.0 + 2.0.1-SNAPSHOT koTime koTime diff --git a/src/main/java/cn/langpy/kotime/config/DefaultConfig.java b/src/main/java/cn/langpy/kotime/config/DefaultConfig.java index 7265647..59de3e2 100644 --- a/src/main/java/cn/langpy/kotime/config/DefaultConfig.java +++ b/src/main/java/cn/langpy/kotime/config/DefaultConfig.java @@ -1,64 +1,30 @@ package cn.langpy.kotime.config; -import cn.langpy.kotime.handler.RunTimeHandler; -import cn.langpy.kotime.model.KoTimeConfig; -import cn.langpy.kotime.util.Context; -import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import javax.annotation.PostConstruct; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; -@Configuration +@Component +@ConfigurationProperties(prefix = "ko-time") public class DefaultConfig { - @Value("${koTime.enable:true}") - private Boolean kotimeEnable; - @Value("${koTime.log.language:chinese}") + private Boolean enable; private String logLanguage; - @Value("${koTime.log.enable:false}") private Boolean logEnable; - @Value("${koTime.time.threshold:800.0}") - private Double timeThreshold; - @Value("${koTime.pointcut:execution(* cn.langpy.kotime.controller.KoTimeController.*(..))}") + private Double threshold; private String pointcut; - @Value("${koTime.exception.enable:false}") private Boolean exceptionEnable; - @Value("${koTime.save.saver:memory}") private String saveSaver; - @Value("${koTime.save.async:false}") private Boolean saveAsync; - @Value("${koTime.save.thread-num:4}") private Integer threadNum; + private String uiTemplate; - - @PostConstruct - public void function() { - KoTimeConfig config = new KoTimeConfig(); - config.setLogEnable(logEnable); - config.setLogLanguage(logLanguage); - config.setTimeThreshold(timeThreshold); - config.setExceptionEnable(exceptionEnable); - config.setDataSaver(saveSaver); - config.setKotimeEnable(kotimeEnable); - Context.setConfig(config); + public Boolean getEnable() { + return enable; } - @Bean - public AspectJExpressionPointcutAdvisor configurabledvisor() { - AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); - advisor.setExpression(pointcut); - advisor.setAdvice(new RunTimeHandler()); - return advisor; - } - - public Double getTimeThreshold() { - return timeThreshold; - } - - public void setTimeThreshold(Double timeThreshold) { - this.timeThreshold = timeThreshold; + public void setEnable(Boolean enable) { + this.enable = enable; } public String getLogLanguage() { @@ -77,5 +43,59 @@ public class DefaultConfig { this.logEnable = logEnable; } + public Double getThreshold() { + return threshold; + } + public void setThreshold(Double threshold) { + this.threshold = threshold; + } + + public String getPointcut() { + return pointcut; + } + + public void setPointcut(String pointcut) { + this.pointcut = pointcut; + } + + public Boolean getExceptionEnable() { + return exceptionEnable; + } + + public void setExceptionEnable(Boolean exceptionEnable) { + this.exceptionEnable = exceptionEnable; + } + + public String getSaveSaver() { + return saveSaver; + } + + public void setSaveSaver(String saveSaver) { + this.saveSaver = saveSaver; + } + + public Boolean getSaveAsync() { + return saveAsync; + } + + public void setSaveAsync(Boolean saveAsync) { + this.saveAsync = saveAsync; + } + + public Integer getThreadNum() { + return threadNum; + } + + public void setThreadNum(Integer threadNum) { + this.threadNum = threadNum; + } + + public String getUiTemplate() { + return uiTemplate; + } + + public void setUiTemplate(String uiTemplate) { + this.uiTemplate = uiTemplate; + } } diff --git a/src/main/java/cn/langpy/kotime/config/LoadConfig.java b/src/main/java/cn/langpy/kotime/config/LoadConfig.java index 1e25554..231a1f7 100644 --- a/src/main/java/cn/langpy/kotime/config/LoadConfig.java +++ b/src/main/java/cn/langpy/kotime/config/LoadConfig.java @@ -1,7 +1,82 @@ package cn.langpy.kotime.config; +import cn.langpy.kotime.handler.RunTimeHandler; +import cn.langpy.kotime.util.Common; +import cn.langpy.kotime.util.Context; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.logging.Logger; @ComponentScan("cn.langpy.kotime") +@Configuration public class LoadConfig { + public static Logger log = Logger.getLogger(Common.class.toString()); + @Value("${koTime.enable:true}") + private Boolean kotimeEnable; + @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; + @Value("${koTime.pointcut:execution(* cn.langpy.kotime.controller.KoTimeController.*(..))}") + private String pointcut; + @Value("${koTime.exception.enable:false}") + private Boolean exceptionEnable; + @Value("${koTime.ui.template}") + private String uiTemplate; + @Value("${koTime.save.saver:memory}") + private String saveSaver; + @Value("${koTime.save.async:false}") + private Boolean saveAsync; + @Value("${koTime.save.thread-num:4}") + private Integer threadNum; + + @Resource + private DefaultConfig defaultConfig; + + + @PostConstruct + public void function() { + getUiTemplate(); + DefaultConfig config = new DefaultConfig(); + config.setLogEnable(defaultConfig.getLogEnable()==null?logEnable:defaultConfig.getLogEnable()); + config.setLogLanguage(defaultConfig.getLogLanguage()==null?logLanguage:defaultConfig.getLogLanguage()); + config.setThreshold(defaultConfig.getThreshold()==null?timeThreshold:defaultConfig.getThreshold()); + config.setExceptionEnable(defaultConfig.getExceptionEnable()==null?exceptionEnable:defaultConfig.getExceptionEnable()); + config.setSaveSaver(defaultConfig.getSaveSaver()==null?saveSaver:defaultConfig.getSaveSaver()); + config.setEnable(defaultConfig.getEnable()==null?kotimeEnable:defaultConfig.getEnable()); + config.setUiTemplate(defaultConfig.getUiTemplate()==null?(uiTemplate==null?getUiTemplate():uiTemplate):defaultConfig.getUiTemplate()); + Context.setConfig(config); + } + + public String getUiTemplate() { + try { + LoadConfig.class.getClassLoader().loadClass("freemarker.template.Configuration"); + log.info("loaded freemarker"); + return "freemarker"; + } catch (ClassNotFoundException e) { + } + try { + LoadConfig.class.getClassLoader().loadClass("org.thymeleaf.Thymeleaf"); + log.info("loaded thymeleaf"); + } catch (ClassNotFoundException e) { + throw new RuntimeException("cannot find any ui-template,please add spring-boot-starter-freemarker(or thymeleaf) to pom.xml "); + } + return "thymeleaf"; + } + + @Bean + public AspectJExpressionPointcutAdvisor configurabledvisor() { + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(defaultConfig.getPointcut()==null?pointcut:defaultConfig.getPointcut()); + advisor.setAdvice(new RunTimeHandler()); + return advisor; + } } diff --git a/src/main/java/cn/langpy/kotime/controller/KoTimeController.java b/src/main/java/cn/langpy/kotime/controller/KoTimeController.java index d87b369..20b8f24 100644 --- a/src/main/java/cn/langpy/kotime/controller/KoTimeController.java +++ b/src/main/java/cn/langpy/kotime/controller/KoTimeController.java @@ -1,9 +1,9 @@ package cn.langpy.kotime.controller; +import cn.langpy.kotime.config.DefaultConfig; import cn.langpy.kotime.model.*; import cn.langpy.kotime.service.GraphService; import cn.langpy.kotime.util.Context; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -14,8 +14,7 @@ import java.util.List; @Controller @RequestMapping("/koTime") public class KoTimeController { - @Value("${koTime.ui.template:freemarker}") - private String showTemplate; + @GetMapping public String index(Model model) { @@ -29,7 +28,7 @@ public class KoTimeController { model.addAttribute("system", system); model.addAttribute("config", Context.getConfig()); String template = "index-freemarker"; - if ("thymeleaf".equals(showTemplate)) { + if ("thymeleaf".equals(Context.getConfig().getUiTemplate())) { template = "index-thymeleaf"; } return template; @@ -37,7 +36,7 @@ public class KoTimeController { @GetMapping("/getConfig") @ResponseBody - public KoTimeConfig getConfig() { + public DefaultConfig getConfig() { return Context.getConfig(); } @@ -84,9 +83,9 @@ public class KoTimeController { @PostMapping("/updateConfig") @ResponseBody public boolean updateConfig(@RequestBody KoTimeConfig config) { - KoTimeConfig koTimeConfig = Context.getConfig(); + DefaultConfig koTimeConfig = Context.getConfig(); if (config.getKotimeEnable()!=null) { - koTimeConfig.setKotimeEnable(config.getKotimeEnable()); + koTimeConfig.setEnable(config.getKotimeEnable()); } if (config.getExceptionEnable()!=null) { koTimeConfig.setExceptionEnable(config.getExceptionEnable()); @@ -95,7 +94,7 @@ public class KoTimeController { koTimeConfig.setLogEnable(config.getLogEnable()); } if (config.getTimeThreshold()!=null) { - koTimeConfig.setTimeThreshold(config.getTimeThreshold()); + koTimeConfig.setThreshold(config.getTimeThreshold()); } return true; } diff --git a/src/main/java/cn/langpy/kotime/data/MemoryBase.java b/src/main/java/cn/langpy/kotime/data/MemoryBase.java index 78862d9..de28925 100644 --- a/src/main/java/cn/langpy/kotime/data/MemoryBase.java +++ b/src/main/java/cn/langpy/kotime/data/MemoryBase.java @@ -31,6 +31,9 @@ public class MemoryBase implements GraphService { @Override public void addMethodNode(MethodNode methodNode) { + if (null==methodNode) { + return; + } if (!methodNodes.containsKey(methodNode.getId())) { methodNodes.put(methodNode.getId(), methodNode); } @@ -200,9 +203,9 @@ public class MemoryBase implements GraphService { if (null == controllerApis || controllerApis.size() == 0) { return systemStatistic; } - int delayNum = (int) controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() >= Context.getConfig().getTimeThreshold()).count(); + int delayNum = (int) controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() >= Context.getConfig().getThreshold()).count(); systemStatistic.setDelayNum(delayNum); - int normalNum = (int) controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() < Context.getConfig().getTimeThreshold()).count(); + int normalNum = (int) controllerApis.stream().filter(controllerApi -> controllerApi.getAvgRunTime() < Context.getConfig().getThreshold()).count(); systemStatistic.setNormalNum(normalNum); int totalNum = (int) controllerApis.stream().count(); systemStatistic.setTotalNum(totalNum); diff --git a/src/main/java/cn/langpy/kotime/handler/RunTimeHandler.java b/src/main/java/cn/langpy/kotime/handler/RunTimeHandler.java index 2ce8127..98e755f 100644 --- a/src/main/java/cn/langpy/kotime/handler/RunTimeHandler.java +++ b/src/main/java/cn/langpy/kotime/handler/RunTimeHandler.java @@ -5,6 +5,7 @@ import cn.langpy.kotime.service.GraphService; import cn.langpy.kotime.model.MethodNode; import cn.langpy.kotime.service.InvokeService; import cn.langpy.kotime.util.Context; +import cn.langpy.kotime.util.MethodStack; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -12,13 +13,15 @@ public class RunTimeHandler implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { - boolean kotimeEnable = Context.getConfig().getKotimeEnable(); + boolean kotimeEnable = Context.getConfig().getEnable(); if (!kotimeEnable) { return invocation.proceed(); } boolean exceptionEnable = Context.getConfig().getExceptionEnable(); long begin = System.nanoTime(); Object obj = null; + MethodNode parent = InvokeService.getParentMethodNode(); + MethodStack.record(invocation); if (exceptionEnable) { try { obj = invocation.proceed(); @@ -36,19 +39,19 @@ public class RunTimeHandler implements MethodInterceptor { graphService.addExceptionNode(exception); graphService.addExceptionRelation(current, exception); } + MethodStack.clear(); throw e; } } else { obj = invocation.proceed(); } long end = System.nanoTime(); - String packName = invocation.getMethod().getDeclaringClass().getPackage().getName(); - MethodNode parent = InvokeService.getParentMethodNode(packName); MethodNode current = InvokeService.getCurrentMethodNode(invocation, ((end - begin) / 1000000.0)); GraphService graphService = GraphService.getInstance(); graphService.addMethodNode(parent); graphService.addMethodNode(current); graphService.addMethodRelation(parent, current); + MethodStack.clear(); return obj; } } diff --git a/src/main/java/cn/langpy/kotime/service/InvokeService.java b/src/main/java/cn/langpy/kotime/service/InvokeService.java index 6f49f9b..91a004d 100644 --- a/src/main/java/cn/langpy/kotime/service/InvokeService.java +++ b/src/main/java/cn/langpy/kotime/service/InvokeService.java @@ -1,27 +1,34 @@ package cn.langpy.kotime.service; - import cn.langpy.kotime.model.MethodNode; import cn.langpy.kotime.util.Common; +import cn.langpy.kotime.util.MethodStack; +import cn.langpy.kotime.util.MethodType; import org.aopalliance.intercept.MethodInvocation; import java.math.BigDecimal; +import java.util.Stack; import java.util.logging.Logger; public class InvokeService { public static Logger log = Logger.getLogger(InvokeService.class.toString()); - - public static MethodNode getParentMethodNode(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(); + public static MethodNode getParentMethodNode() { + Stack stack = MethodStack.get(); + if (null==stack) { + MethodNode parent = new MethodNode(); + parent.setId("com.langpy.kotime.Cotroller.dispatch"); + parent.setClassName("Cotroller"); + parent.setMethodName("dispatch"); + parent.setName("Cotroller.dispatch"); + parent.setMethodType(MethodType.Others); + return parent; } + String classMethod = stack.peek(); + String[] classMethodSplit = classMethod.split("#"); + String parentClassName = classMethodSplit[0]; + String parentMothodName = classMethodSplit[1]; MethodNode parent = new MethodNode(); parent.setId(parentClassName + "." + parentMothodName); parent.setClassName(parentClassName); diff --git a/src/main/java/cn/langpy/kotime/util/Common.java b/src/main/java/cn/langpy/kotime/util/Common.java index 08a0b63..02c73d2 100644 --- a/src/main/java/cn/langpy/kotime/util/Common.java +++ b/src/main/java/cn/langpy/kotime/util/Common.java @@ -2,7 +2,6 @@ package cn.langpy.kotime.util; import cn.langpy.kotime.model.MethodRelation; import org.aopalliance.intercept.MethodInvocation; -import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; @@ -13,19 +12,6 @@ import java.util.logging.Logger; public class Common { public static Logger log = Logger.getLogger(Common.class.toString()); - public static StackTraceElement filter(StackTraceElement[] stacks, String packName) { - String[] packNameSplit = packName.split("\\."); - String filter = packNameSplit.length > 1 ? packNameSplit[0] + "." + packNameSplit[1] : packNameSplit[0]; - int stacksLength = stacks.length; - for (int i = 0; i < stacksLength; i++) { - StackTraceElement stack = stacks[i]; - if (stack.getClassName().startsWith(filter) && !stack.getClassName().contains("$")) { - return stack; - } - } - return null; - } - public static MethodType getMethodType(MethodInvocation pjp) { Class targetClass = pjp.getThis().getClass(); @@ -47,28 +33,6 @@ public class Common { return MethodType.Others; } } - - public static MethodType getMethodType(ProceedingJoinPoint pjp) { - Class targetClass = pjp.getTarget().getClass(); - if (targetClass.getAnnotation(Controller.class) != null || targetClass.getAnnotation(RestController.class) != null) { - return MethodType.Controller; - } else if (targetClass.getAnnotation(Service.class) != null) { - return MethodType.Service; - } else if (targetClass.getAnnotation(Repository.class) != null) { - return MethodType.Dao; - } - String className = pjp.getTarget().getClass().getName().toLowerCase(); - if (className.contains("controller")) { - return MethodType.Controller; - } else if (className.contains("service")) { - return MethodType.Service; - } else if (className.contains("dao") || className.contains("mapper") || className.contains("com.sun.proxy.$Proxy")) { - return MethodType.Dao; - } else { - return MethodType.Others; - } - } - public static MethodType getMethodType(String className) { className = className.toLowerCase(); if (className.contains("controller")) { diff --git a/src/main/java/cn/langpy/kotime/util/Context.java b/src/main/java/cn/langpy/kotime/util/Context.java index 0db325f..c396a04 100644 --- a/src/main/java/cn/langpy/kotime/util/Context.java +++ b/src/main/java/cn/langpy/kotime/util/Context.java @@ -1,23 +1,23 @@ package cn.langpy.kotime.util; -import cn.langpy.kotime.model.KoTimeConfig; +import cn.langpy.kotime.config.DefaultConfig; public class Context { - private static KoTimeConfig config; + private static DefaultConfig config; static { - config = new KoTimeConfig(); + config = new DefaultConfig(); config.setLogEnable(false); - config.setKotimeEnable(true); + config.setEnable(true); config.setLogLanguage("chinese"); } - public static void setConfig(KoTimeConfig koTimeConfig) { + public static void setConfig(DefaultConfig koTimeConfig) { config = koTimeConfig; } - public static KoTimeConfig getConfig() { + public static DefaultConfig getConfig() { return config; } diff --git a/src/main/java/cn/langpy/kotime/util/MethodStack.java b/src/main/java/cn/langpy/kotime/util/MethodStack.java new file mode 100644 index 0000000..105499e --- /dev/null +++ b/src/main/java/cn/langpy/kotime/util/MethodStack.java @@ -0,0 +1,39 @@ +package cn.langpy.kotime.util; + +import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack; +import org.aopalliance.intercept.MethodInvocation; + +import java.util.Stack; + +public class MethodStack { + + private static ThreadLocal threadMethods = new ThreadLocal<>(); + + public static void record(MethodInvocation pjp) { + String className = pjp.getMethod().getDeclaringClass().getName(); + String methodName = pjp.getMethod().getName(); + Stack queue = null; + if (null==threadMethods.get()) { + queue = new StringStack(); + }else { + queue = threadMethods.get(); + } + queue.add(className+"#"+methodName); + threadMethods.set(queue); + } + + public static Stack get() { + return threadMethods.get(); + } + + public static void clear() { + Stack queue = threadMethods.get(); + if (queue==null) { + return; + } + queue.pop(); + if (queue.isEmpty()) { + threadMethods.remove(); + } + } +}