diff --git a/README.md b/README.md index 8b0b0c6..5e4b8e5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
- +
@@ -22,6 +22,7 @@ - ✅ Find exceptions occurred in methods - ✅ Email you after finding an overtime method - ✅ Hot update online:you needn't restart it +- ✅ Thread manage:show threads information - ✅ Easy to use:you needn't additional learning costs - ✅ Enough to add a pom dependency:you needn't additional deployment costs @@ -35,6 +36,7 @@ - ✅ 追踪系统异常,精确定位到方法 - ✅ 接口超时邮件通知,无需实时查看 - ✅ 线上热更新:无需重启更新代码 +- ✅ 线程管理:线程实时统计与状态查看 - ✅ 使用简单,无技术学习成本 - ✅ pom依赖即可,无代码侵入,无多余部署成本 @@ -87,6 +89,13 @@ v2.2.5开始加入了邮件通知功能,当方法耗时超过阈值之后, ![输入图片说明](docs/v200/image.png) +5.线程管理 + +v2.3.9开始加入了线程管理功能,可以统计线程状态和查看线程堆栈信息 + +![输入图片说明](docs/v220/xcgl.png) +![输入图片说明](docs/v220/xcgl2.png) + ## 重要版本说明 > V1.0:基本功能 diff --git a/docs/v220/xcgl.png b/docs/v220/xcgl.png new file mode 100644 index 0000000..9c5e3ba Binary files /dev/null and b/docs/v220/xcgl.png differ diff --git a/docs/v220/xcgl2.png b/docs/v220/xcgl2.png new file mode 100644 index 0000000..066b488 Binary files /dev/null and b/docs/v220/xcgl2.png differ diff --git a/pom.xml b/pom.xml index f05f144..be3fb99 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ cn.langpy ko-time - 2.3.8 + 2.3.9 KoTime A springboot tool for tracking the paths of the methods,which can help you find method's performances easily. diff --git a/src/main/java/cn/langpy/kotime/controller/KoTimeController.java b/src/main/java/cn/langpy/kotime/controller/KoTimeController.java index 78f1a1e..f5d6c77 100644 --- a/src/main/java/cn/langpy/kotime/controller/KoTimeController.java +++ b/src/main/java/cn/langpy/kotime/controller/KoTimeController.java @@ -21,11 +21,9 @@ import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * zhangchang @@ -71,7 +69,7 @@ public class KoTimeController { if (StringUtils.hasText(kotoken)) { if (kotoken.equals(Context.getConfig().getStaticToken())) { checkLogin = true; - }else { + } else { checkLogin = KoUtil.isLogin(kotoken); } } @@ -81,7 +79,7 @@ public class KoTimeController { @GetMapping - public void index(String kotoken,String test, HttpServletResponse response, HttpServletRequest request) { + public void index(String kotoken, String test, HttpServletResponse response, HttpServletRequest request) { if (!Context.getConfig().getEnable()) { return; } @@ -121,14 +119,14 @@ public class KoTimeController { line = line.replace("UIKitJs", uiKitJsText); } else if (line.indexOf("MetricFlowJs") > -1) { line = line.replace("MetricFlowJs", metricFlowJsText); - }else if (line.indexOf("jQueryJs") > -1) { + } else if (line.indexOf("jQueryJs") > -1) { line = line.replace("jQueryJs", jQueryJsText); - }else if (line.indexOf("uiKitIconsJs") > -1) { + } else if (line.indexOf("uiKitIconsJs") > -1) { line = line.replace("uiKitIconsJs", uiKitIconsJs); - }else if (line.indexOf("staticTokenVisitValue") > -1) { - line = line.replace("staticTokenVisitValue", staticTokenVisit+""); - }else if (line.indexOf("staticTokenValue") > -1) { - line = line.replace("staticTokenValue", "'"+kotoken+"'"); + } else if (line.indexOf("staticTokenVisitValue") > -1) { + line = line.replace("staticTokenVisitValue", staticTokenVisit + ""); + } else if (line.indexOf("staticTokenValue") > -1) { + line = line.replace("staticTokenValue", "'" + kotoken + "'"); } stringBuilder.append(line + "\n"); } @@ -139,6 +137,7 @@ public class KoTimeController { e.printStackTrace(); } } + private String getResourceText(String fileName) { ClassPathResource classPathResource = new ClassPathResource(fileName); try (InputStream inputStream = classPathResource.getInputStream(); @@ -229,9 +228,9 @@ public class KoTimeController { @GetMapping("/getMethodsByExceptionId") @ResponseBody @Auth - public List getMethodsByExceptionId(String exceptionId,String message) { + public List getMethodsByExceptionId(String exceptionId, String message) { GraphService graphService = GraphService.getInstance(); - List exceptionInfos = graphService.getExceptionInfos(exceptionId,message); + List exceptionInfos = graphService.getExceptionInfos(exceptionId, message); return exceptionInfos; } @@ -257,12 +256,13 @@ public class KoTimeController { } return true; } + @PostMapping("/updateClass") @ResponseBody @Auth - public Map updateClass(@RequestParam("classFile") MultipartFile classFile,String className) { + public Map updateClass(@RequestParam("classFile") MultipartFile classFile, String className) { Map map = new HashMap(); - if (classFile==null || classFile.isEmpty()) { + if (classFile == null || classFile.isEmpty()) { map.put("state", 0); map.put("message", "文件不能为空"); return map; @@ -288,7 +288,7 @@ public class KoTimeController { map.put("message", "请确认类名是否正确"); return map; } - file = uploadFile(classFile.getBytes(),filename[0]); + file = uploadFile(classFile.getBytes(), filename[0]); } catch (IOException e) { log.severe("Error class file!"); map.put("state", 0); @@ -296,7 +296,7 @@ public class KoTimeController { return map; } final ClassService classService = ClassService.getInstance(); - classService.updateClass(className,file.getAbsolutePath()); + classService.updateClass(className, file.getAbsolutePath()); file.deleteOnExit(); map.put("state", 1); @@ -305,19 +305,18 @@ public class KoTimeController { } - - private static File uploadFile(byte[] file,String fileName) throws IOException { + private static File uploadFile(byte[] file, String fileName) throws IOException { FileOutputStream out = null; try { - File targetFile = File.createTempFile(fileName, ".class", new File(System.getProperty("java.io.tmpdir"))); + File targetFile = File.createTempFile(fileName, ".class", new File(System.getProperty("java.io.tmpdir"))); out = new FileOutputStream(targetFile.getAbsolutePath()); out.write(file); out.flush(); return targetFile; } catch (Exception e) { log.severe("" + e); - }finally { - if(out !=null){ + } finally { + if (out != null) { out.flush(); out.close(); } @@ -342,6 +341,7 @@ public class KoTimeController { HeapMemoryInfo heapMemoryInfo = usageService.getHeapMemoryInfo(); return heapMemoryInfo; } + @GetMapping("/getPhysicalMemoryInfo") @ResponseBody @Auth @@ -363,9 +363,20 @@ public class KoTimeController { @GetMapping("/getThreadsInfo") @ResponseBody @Auth - public List getThreadsInfo() { + public Map getThreadsInfo(String state) { ThreadUsageService usageService = ThreadUsageService.newInstance(); List threads = usageService.getThreads(); - return threads; + threads = threads.stream().sorted(Comparator.comparing(ThreadInfo::getState)).collect(Collectors.toList()); + + Map stateCounting = threads.stream().collect(Collectors.groupingBy(ThreadInfo::getState, Collectors.counting())); + stateCounting.put("all",(long)threads.size()); + + Map map = new HashMap(); + map.put("statistics", stateCounting); + if (StringUtils.hasText(state)) { + threads = threads.stream().filter(a -> a.getState().equals(state)).collect(Collectors.toList()); + } + map.put("threads", threads); + return map; } } diff --git a/src/main/java/cn/langpy/kotime/service/ThreadUsageService.java b/src/main/java/cn/langpy/kotime/service/ThreadUsageService.java index df3e3bb..04ae1f6 100644 --- a/src/main/java/cn/langpy/kotime/service/ThreadUsageService.java +++ b/src/main/java/cn/langpy/kotime/service/ThreadUsageService.java @@ -37,4 +37,9 @@ public class ThreadUsageService { } return list; } + + public List getThreads(String state) { + List threads = getThreads(); + return threads.stream().filter(a -> a.getState().equals(state)).collect(Collectors.toList()); + } } diff --git a/src/main/resources/kotime-en.html b/src/main/resources/kotime-en.html index 856cfdf..83bc80b 100644 --- a/src/main/resources/kotime-en.html +++ b/src/main/resources/kotime-en.html @@ -505,9 +505,68 @@ loadCpuInfo(); loadHeapMemoryInfo(); loadPhysicalMemoryInfo(); + loadThreadsInfo(); } }); } + let threadMap = new Map(); + function loadThreadsInfo(queryState) { + queryState = queryState || ''; + $.get(concatToken('contextPath/koTime/getThreadsInfo?state='+queryState), function (data) { + let statistics = data['statistics']; + let all = statistics['all']; + let RUNNABLE = statistics['RUNNABLE'] || 0; + let BLOCKED = statistics['BLOCKED'] || 0; + let WAITING = statistics['WAITING'] || 0; + let TIMED_WAITING = statistics['TIMED_WAITING'] || 0; + + document.querySelector("#threadNum").innerHTML = all; + document.querySelector("#runnableNum").innerHTML = RUNNABLE; + document.querySelector("#blockedNum").innerHTML = BLOCKED; + document.querySelector("#waitingNum").innerHTML = WAITING; + document.querySelector("#timedWaitingNum").innerHTML = TIMED_WAITING; + + let element = document.getElementById('threadList'); + let html = ''; + let threads = data['threads']; + let colors = { + 'RUNNABLE':'#32d296', + 'BLOCKED':'#cc0c0c', + 'WAITING':'#ad7070', + 'TIMED_WAITING':'#ad7070' + } + for (let i = 0; i < threads.length; i++) { + let thread = threads[i]; + let id = thread['id']; + let name = thread['name']; + let classType = thread['classType']; + let state = thread['state']; + let stacks = thread['stacks']; + threadMap[id+''] = stacks; + html+=`
  • id=${id}   name=${name}   class=${classType}   ${state}
  • `; + } + element.innerHTML = html; + }); + } + + function showThreadInfo(id) { + let stacks = threadMap[id]; + let html = ''; + for (let i = 0; i < stacks.length; i++) { + let stack = stacks[i]; + let className = stack['className'] + let methodName = stack['methodName'] + let fileName = stack['fileName'] + let lineNumber = stack['lineNumber'] + html+=`
  • ${className}.${methodName}   ${fileName}:${lineNumber}
  • `; + + } + let threadDetailDom = document.getElementById('thread-detail'); + threadDetailDom.innerHTML = html; + UIkit.notification.closeAll(); + UIkit.modal(document.getElementById("modal-thread")).show(); + } + $(document).ready(function () { refreshData(); }); @@ -529,6 +588,7 @@
  • Summary
  • Interfaces
  • Exceptions
  • +
  • Threads
  • Hot update
  • Configurations
  • Contact me
  • @@ -648,6 +708,50 @@
  • exception 1 1 closed
  • +
  • + +
    +
    +
    + ALL
    + 0 +
    +
    +
    +
    + RUNNABLE +
    + 0 +
    +
    +
    +
    + BLOCKED +
    + 0
    +
    +
    +
    + WAITING +
    + 0
    +
    +
    +
    + TIMED_WAITING +
    + 0
    +
    +
    + +
      +
    • thread 1 1 0
    • +
    +
  • @@ -709,7 +813,19 @@ Local plugin
  • +