| @@ -5,6 +5,7 @@ import com.ruoyi.common.core.domain.AjaxResult; | |||||
| import com.ruoyi.file.model.FileModel; | import com.ruoyi.file.model.FileModel; | ||||
| import com.ruoyi.file.model.FileSystemFilter; | import com.ruoyi.file.model.FileSystemFilter; | ||||
| import com.ruoyi.file.object.ProjectState; | import com.ruoyi.file.object.ProjectState; | ||||
| import com.ruoyi.file.object.RuntimeState; | |||||
| import com.ruoyi.file.object.StateMachine; | import com.ruoyi.file.object.StateMachine; | ||||
| import com.ruoyi.file.service.FileSystemService; | import com.ruoyi.file.service.FileSystemService; | ||||
| import com.ruoyi.file.service.StateService; | import com.ruoyi.file.service.StateService; | ||||
| @@ -94,4 +95,15 @@ public class StateController extends BaseController | |||||
| return AjaxResult.success(); | return AjaxResult.success(); | ||||
| } | } | ||||
| /** | |||||
| * 查询jvm信息 | |||||
| */ | |||||
| @ApiOperation("查询jvm信息") | |||||
| @PreAuthorize("@ss.hasPermi('admin:state:runtime')") | |||||
| @GetMapping("/runtime") | |||||
| public AjaxResult runtime() | |||||
| { | |||||
| RuntimeState state = stateService.runtimeState(); | |||||
| return AjaxResult.success(state); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,126 @@ | |||||
| package com.ruoyi.file.object; | |||||
| import com.fasterxml.jackson.annotation.JsonFormat; | |||||
| import lombok.Data; | |||||
| import java.lang.management.ManagementFactory; | |||||
| import java.lang.management.MemoryMXBean; | |||||
| import java.lang.management.MemoryPoolMXBean; | |||||
| import java.lang.management.MemoryUsage; | |||||
| import java.lang.management.RuntimeMXBean; | |||||
| import java.lang.management.ThreadInfo; | |||||
| import java.lang.management.ThreadMXBean; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| @Data | |||||
| public final class RuntimeState | |||||
| { | |||||
| @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") | |||||
| private Date startTime; | |||||
| @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") | |||||
| private Date uptime; | |||||
| private long runDuration; | |||||
| private String arguments; | |||||
| private long heapUsed; | |||||
| private long heapInit; | |||||
| private long heapMax; | |||||
| private long heapCommitted; | |||||
| private long nonHeapUsed; | |||||
| private long nonHeapInit; | |||||
| private long nonHeapMax; | |||||
| private long nonHeapCommitted; | |||||
| private long threadCount; | |||||
| private long daemonThreadCount; | |||||
| private long peakThreadCount; | |||||
| private long startedThreadCount; | |||||
| private List<ThreadState> threads; | |||||
| private List<MemoryPoolState> memoryPools; | |||||
| @Data | |||||
| public static class ThreadState | |||||
| { | |||||
| private long threadId; | |||||
| private String threadName; | |||||
| private String threadState; | |||||
| private Boolean isCurrent; | |||||
| } | |||||
| @Data | |||||
| public static class MemoryPoolState | |||||
| { | |||||
| private String name; | |||||
| private String type; | |||||
| private long memoryUsed = -1; | |||||
| private long memoryInit = -1; | |||||
| private long memoryMax = -1; | |||||
| private long memoryCommitted = -1; | |||||
| } | |||||
| public static RuntimeState capture() | |||||
| { | |||||
| RuntimeState state = new RuntimeState(); | |||||
| RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); | |||||
| long startTime = runtimeMXBean.getStartTime(); | |||||
| long uptime = runtimeMXBean.getUptime(); | |||||
| state.startTime = new Date(startTime); | |||||
| state.uptime = new Date(startTime + uptime); | |||||
| state.runDuration = uptime; | |||||
| state.arguments = String.join(" ", runtimeMXBean.getInputArguments()); | |||||
| MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); | |||||
| MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); | |||||
| state.heapUsed = heapMemoryUsage.getUsed(); | |||||
| state.heapMax = heapMemoryUsage.getMax(); | |||||
| state.heapInit = heapMemoryUsage.getInit(); | |||||
| state.heapCommitted = heapMemoryUsage.getCommitted(); | |||||
| MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); | |||||
| state.nonHeapUsed = nonHeapMemoryUsage.getUsed(); | |||||
| state.nonHeapMax = nonHeapMemoryUsage.getMax(); | |||||
| state.nonHeapInit = nonHeapMemoryUsage.getInit(); | |||||
| state.nonHeapCommitted = nonHeapMemoryUsage.getCommitted(); | |||||
| ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); | |||||
| state.threadCount = threadMXBean.getThreadCount(); | |||||
| state.daemonThreadCount = threadMXBean.getDaemonThreadCount(); | |||||
| state.peakThreadCount = threadMXBean.getPeakThreadCount(); | |||||
| state.startedThreadCount = threadMXBean.getTotalStartedThreadCount(); | |||||
| ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); | |||||
| state.threads = new ArrayList<>(); | |||||
| for(ThreadInfo threadInfo : threadInfos) | |||||
| { | |||||
| ThreadState t = new ThreadState(); | |||||
| t.threadId = threadInfo.getThreadId(); | |||||
| t.threadName = threadInfo.getThreadName(); | |||||
| t.threadState = threadInfo.getThreadState().name(); | |||||
| t.isCurrent = threadInfo.getThreadId() == Thread.currentThread().getId(); | |||||
| state.threads.add(t); | |||||
| } | |||||
| state.memoryPools = new ArrayList<>(); | |||||
| List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); | |||||
| for(MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) | |||||
| { | |||||
| MemoryPoolState m = new MemoryPoolState(); | |||||
| m.name = memoryPoolMXBean.getName(); | |||||
| m.type = memoryPoolMXBean.getType().name(); | |||||
| MemoryUsage usage = memoryPoolMXBean.getUsage(); | |||||
| if(null != usage) | |||||
| { | |||||
| m.memoryUsed = usage.getUsed(); | |||||
| m.memoryMax = usage.getMax(); | |||||
| m.memoryInit = usage.getInit(); | |||||
| m.memoryCommitted = usage.getCommitted(); | |||||
| } | |||||
| state.memoryPools.add(m); | |||||
| } | |||||
| return state; | |||||
| } | |||||
| } | |||||
| @@ -178,7 +178,7 @@ public final class StateMachine | |||||
| public void GC() | public void GC() | ||||
| { | { | ||||
| System.gc(); | |||||
| //System.gc(); | |||||
| gcCount++; | gcCount++; | ||||
| lastGCTime = new Date(); | lastGCTime = new Date(); | ||||
| } | } | ||||
| @@ -5,14 +5,13 @@ import com.ruoyi.common.core.redis.RedisCache; | |||||
| import com.ruoyi.common.utils.StringUtils; | import com.ruoyi.common.utils.StringUtils; | ||||
| import com.ruoyi.common.utils.spring.SpringUtils; | import com.ruoyi.common.utils.spring.SpringUtils; | ||||
| import com.ruoyi.file.object.ProjectState; | import com.ruoyi.file.object.ProjectState; | ||||
| import com.ruoyi.file.object.RuntimeState; | |||||
| import com.ruoyi.file.object.StateMachine; | import com.ruoyi.file.object.StateMachine; | ||||
| import com.ruoyi.file.utils.ProjectUtils; | |||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
| import javax.annotation.PostConstruct; | import javax.annotation.PostConstruct; | ||||
| import javax.annotation.PreDestroy; | import javax.annotation.PreDestroy; | ||||
| import java.util.Date; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| @@ -32,9 +31,17 @@ public class StateService | |||||
| return StateMachine.INSTANCE.GetStates(); | return StateMachine.INSTANCE.GetStates(); | ||||
| } | } | ||||
| public RuntimeState runtimeState() | |||||
| { | |||||
| return RuntimeState.capture(); | |||||
| } | |||||
| public long gc() | public long gc() | ||||
| { | { | ||||
| StateMachine.INSTANCE.GC(); | StateMachine.INSTANCE.GC(); | ||||
| dump(); | |||||
| System.gc(); | |||||
| init(); | |||||
| return StateMachine.INSTANCE.gcCount; | return StateMachine.INSTANCE.gcCount; | ||||
| } | } | ||||
| @@ -34,3 +34,10 @@ export function dump() { | |||||
| method: 'post', | method: 'post', | ||||
| }) | }) | ||||
| } | } | ||||
| export function runtimeState() { | |||||
| return request({ | |||||
| url: '/state/runtime', | |||||
| method: 'get', | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,170 @@ | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <el-row :gutter="10" class="mb8"> | |||||
| <el-col :span="1.5"> | |||||
| <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="gc" v-hasPermi="['admin:state:gc']">GC</el-button> | |||||
| </el-col> | |||||
| <right-toolbar :showSearch.sync="showSearch" @queryTable="refresh"></right-toolbar> | |||||
| </el-row> | |||||
| <el-divider content-position="left">JVM</el-divider> | |||||
| <Descriptions> | |||||
| <el-descriptions-item label="启动时间">{{state.startTime}}</el-descriptions-item> | |||||
| <el-descriptions-item label="当前时间">{{state.uptime}}</el-descriptions-item> | |||||
| <el-descriptions-item label="运行时长(s)">{{state.runDuration / 1000}}</el-descriptions-item> | |||||
| <el-descriptions-item label="启动参数">{{state.arguments}}</el-descriptions-item> | |||||
| </Descriptions> | |||||
| <el-divider content-position="left">堆内存</el-divider> | |||||
| <Descriptions> | |||||
| <el-descriptions-item label="初始分配">{{state.heapInit | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="最大">{{state.heapMax | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="已使用">{{state.heapUsed | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="已提交">{{state.heapCommitted | formatFileSize}}</el-descriptions-item> | |||||
| </Descriptions> | |||||
| <el-divider content-position="left">非堆内存</el-divider> | |||||
| <Descriptions> | |||||
| <el-descriptions-item label="初始分配">{{state.nonHeapInit | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="最大">{{state.nonHeapMax | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="已使用">{{state.nonHeapUsed | formatFileSize}}</el-descriptions-item> | |||||
| <el-descriptions-item label="已提交">{{state.nonHeapCommitted | formatFileSize}}</el-descriptions-item> | |||||
| </Descriptions> | |||||
| <el-divider content-position="left">内存池</el-divider> | |||||
| <el-table :data="state.memoryPools || []"> | |||||
| <el-table-column type="index" label="序号" width="50" align="center" /> | |||||
| <el-table-column label="名称" align="left" header-align="center" prop="name" /> | |||||
| <el-table-column label="类型" align="center" prop="type"/> | |||||
| <el-table-column label="初始分配" align="center" prop="memoryInit" :formatter="fileSizeFormatter"/> | |||||
| <el-table-column label="最大" align="center" prop="memoryMax" :formatter="fileSizeFormatter"/> | |||||
| <el-table-column label="已使用" align="center" prop="memoryUsed" :formatter="fileSizeFormatter"/> | |||||
| <el-table-column label="已提交" align="center" prop="memoryCommitted" :formatter="fileSizeFormatter"/> | |||||
| </el-table> | |||||
| <el-divider content-position="left">线程</el-divider> | |||||
| <Descriptions> | |||||
| <el-descriptions-item label="前后台活动线程数">{{state.threadCount}}</el-descriptions-item> | |||||
| <el-descriptions-item label="后台活动线程数">{{state.daemonThreadCount}}</el-descriptions-item> | |||||
| <el-descriptions-item label="实时线程数">{{state.peakThreadCount}}</el-descriptions-item> | |||||
| <el-descriptions-item label="最多线程数">{{state.startedThreadCount}}</el-descriptions-item> | |||||
| </Descriptions> | |||||
| <el-table :data="state.threads || []" :row-style="threadRowStyle"> | |||||
| <el-table-column type="index" label="序号" width="50" align="center" /> | |||||
| <el-table-column label="线程ID" align="center" prop="threadId" sortable/> | |||||
| <el-table-column label="线程名" align="left" header-align="center" prop="threadName" /> | |||||
| <el-table-column label="线程状态" align="center" prop="threadState" sortable/> | |||||
| </el-table> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import DeptSelect from "@/components/common/DeptSelect.vue"; | |||||
| import ServerFileChooser from "@/components/ServerFileChooser.vue"; | |||||
| import FullscreenDialog from "@/components/FullscreenDialog.vue"; | |||||
| import ProjectState from "@/views/file/state/ProjectState.vue"; | |||||
| import {stateList, gc, stateMachine, dump, runtimeState} from "@/api/file/state"; | |||||
| import Descriptions from "@/components/common/Descriptions.vue"; | |||||
| import {formatFileSize} from "@/utils/file"; | |||||
| export default { | |||||
| name: "State", | |||||
| components: { | |||||
| Descriptions, | |||||
| ProjectState, | |||||
| FullscreenDialog, | |||||
| ServerFileChooser, | |||||
| DeptSelect | |||||
| }, | |||||
| dicts: ['sys_bool'], | |||||
| data() { | |||||
| return { | |||||
| // 遮罩层 | |||||
| loading: false, | |||||
| // 遮罩按钮新增点击状态 | |||||
| diglogStatus: true, | |||||
| // 导出遮罩层 | |||||
| exportLoading: false, | |||||
| // 选中数组 | |||||
| ids: [], | |||||
| // 非单个禁用 | |||||
| single: true, | |||||
| // 非多个禁用 | |||||
| multiple: true, | |||||
| // 默认隐藏搜索条件 | |||||
| showSearch: false, | |||||
| // 总条数 | |||||
| total: 0, | |||||
| // 项目表格数据 | |||||
| stateList: [], | |||||
| // 弹出层标题 | |||||
| title: "", | |||||
| // 是否禁用 字典 sys_bool字典 | |||||
| disabledOptions: [], | |||||
| // 查询参数 | |||||
| queryParams: { | |||||
| // 分页 | |||||
| pageNum: 1, | |||||
| pageSize: 10, | |||||
| // 查询排序 | |||||
| //orderByColumn: "id", | |||||
| //isAsc: "desc", | |||||
| // 翻译字典 | |||||
| //translate_dict: "1", | |||||
| }, | |||||
| // 表单参数 | |||||
| form: {}, | |||||
| // 详情组件列数 | |||||
| descColumn: 2, | |||||
| // 详情组件Label所占百分比, 最大={100 / descColumn}. 内容所占百分比={100 / descColumn - descLabelWidth} | |||||
| descLabelWidth: 15, | |||||
| // 对话框显示只读的详情 | |||||
| projectToken: null, | |||||
| state: {}, | |||||
| }; | |||||
| }, | |||||
| created() { | |||||
| this.getRuntimeState(); | |||||
| }, | |||||
| methods: { | |||||
| // 取消按钮 | |||||
| cancel() { | |||||
| this.reset(); | |||||
| }, | |||||
| // 表单重置 | |||||
| reset() { | |||||
| this.form = { | |||||
| }; | |||||
| }, | |||||
| refresh() { | |||||
| this.getRuntimeState(); | |||||
| }, | |||||
| /** 重置按钮操作 */ | |||||
| resetQuery() { | |||||
| this.resetForm("queryForm"); | |||||
| this.handleQuery(); | |||||
| }, | |||||
| gc() { | |||||
| gc().then((resp) => { | |||||
| this.$message.success('成功'); | |||||
| this.getRuntimeState(); | |||||
| }); | |||||
| }, | |||||
| getRuntimeState() { | |||||
| this.state = {}; | |||||
| runtimeState().then((resp) => { | |||||
| this.state = resp.data; | |||||
| }); | |||||
| }, | |||||
| fileSizeFormatter(row, col, val) { | |||||
| return formatFileSize(val); | |||||
| }, | |||||
| threadRowStyle({row}) { | |||||
| let style = {}; | |||||
| if(row.isCurrent) | |||||
| style['background-color'] = '#67C23A'; | |||||
| return style; | |||||
| }, | |||||
| }, | |||||
| filters: { | |||||
| formatFileSize, | |||||
| }, | |||||
| }; | |||||
| </script> | |||||