mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-27 19:30:24 +08:00
✨ feat(jvm): 新增持续监控与采样链路
- 后端新增监控会话管理,支持启动、停止和历史查询 - JMX、Endpoint、Agent Provider 补齐监控快照采集能力 - JMX helper 增加内存、GC、线程、类加载采样并更新内嵌运行时 - 生成 Wails 监控接口绑定并补充后端回归测试
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
package com.gonavi.jmxhelper;
|
||||
|
||||
import java.lang.management.ClassLoadingMXBean;
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -46,6 +53,8 @@ final class JmxRuntime {
|
||||
return listResources(server, connection, target);
|
||||
case "get":
|
||||
return singleton("snapshot", getValue(server, target));
|
||||
case "monitor":
|
||||
return singleton("monitoringSnapshot", getMonitoringSnapshot(server));
|
||||
case "preview":
|
||||
return singleton("preview", previewChange(server, target, change));
|
||||
case "apply":
|
||||
@@ -210,6 +219,208 @@ final class JmxRuntime {
|
||||
throw new IllegalArgumentException("unsupported target kind: " + target.kind);
|
||||
}
|
||||
|
||||
private static Map<String, Object> getMonitoringSnapshot(MBeanServerConnection server) throws Exception {
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
LinkedHashMap<String, Object> point = new LinkedHashMap<>();
|
||||
List<String> availableMetrics = new ArrayList<>();
|
||||
List<String> missingMetrics = new ArrayList<>();
|
||||
List<String> providerWarnings = new ArrayList<>();
|
||||
|
||||
long sampleTimestamp = System.currentTimeMillis();
|
||||
point.put("timestamp", sampleTimestamp);
|
||||
|
||||
try {
|
||||
ThreadMXBean threadBean = ManagementFactory.newPlatformMXBeanProxy(
|
||||
server,
|
||||
ManagementFactory.THREAD_MXBEAN_NAME,
|
||||
ThreadMXBean.class
|
||||
);
|
||||
point.put("threadCount", threadBean.getThreadCount());
|
||||
point.put("daemonThreadCount", threadBean.getDaemonThreadCount());
|
||||
point.put("peakThreadCount", threadBean.getPeakThreadCount());
|
||||
addUnique(availableMetrics, "thread.count");
|
||||
|
||||
long[] threadIds = threadBean.getAllThreadIds();
|
||||
ThreadInfo[] infos = threadBean.getThreadInfo(threadIds, 0);
|
||||
Map<String, Object> stateCounts = new LinkedHashMap<>();
|
||||
for (ThreadInfo info : infos) {
|
||||
if (info == null || info.getThreadState() == null) {
|
||||
continue;
|
||||
}
|
||||
String state = info.getThreadState().name();
|
||||
int current = stateCounts.get(state) instanceof Number
|
||||
? ((Number) stateCounts.get(state)).intValue()
|
||||
: 0;
|
||||
stateCounts.put(state, current + 1);
|
||||
}
|
||||
if (!stateCounts.isEmpty()) {
|
||||
point.put("threadStateCounts", stateCounts);
|
||||
addUnique(availableMetrics, "thread.states");
|
||||
}
|
||||
} catch (Exception error) {
|
||||
addUnique(missingMetrics, "thread.count");
|
||||
addUnique(providerWarnings, "thread metrics unavailable: " + error.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
MemoryMXBean memoryBean = ManagementFactory.newPlatformMXBeanProxy(
|
||||
server,
|
||||
ManagementFactory.MEMORY_MXBEAN_NAME,
|
||||
MemoryMXBean.class
|
||||
);
|
||||
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
|
||||
if (heap != null) {
|
||||
point.put("heapUsedBytes", heap.getUsed());
|
||||
point.put("heapCommittedBytes", heap.getCommitted());
|
||||
point.put("heapMaxBytes", heap.getMax());
|
||||
addUnique(availableMetrics, "heap.used");
|
||||
} else {
|
||||
addUnique(missingMetrics, "heap.used");
|
||||
}
|
||||
|
||||
MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
|
||||
if (nonHeap != null) {
|
||||
point.put("nonHeapUsedBytes", nonHeap.getUsed());
|
||||
point.put("nonHeapCommittedBytes", nonHeap.getCommitted());
|
||||
addUnique(availableMetrics, "heap.non_heap");
|
||||
}
|
||||
} catch (Exception error) {
|
||||
addUnique(missingMetrics, "heap.used");
|
||||
addUnique(providerWarnings, "heap metrics unavailable: " + error.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
ClassLoadingMXBean classLoadingBean = ManagementFactory.newPlatformMXBeanProxy(
|
||||
server,
|
||||
ManagementFactory.CLASS_LOADING_MXBEAN_NAME,
|
||||
ClassLoadingMXBean.class
|
||||
);
|
||||
point.put("loadedClassCount", classLoadingBean.getLoadedClassCount());
|
||||
point.put("unloadedClassCount", classLoadingBean.getUnloadedClassCount());
|
||||
addUnique(availableMetrics, "class.loading");
|
||||
} catch (Exception error) {
|
||||
addUnique(missingMetrics, "class.loading");
|
||||
addUnique(providerWarnings, "class loading metrics unavailable: " + error.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
List<Map<String, Object>> recentGcEvents = new ArrayList<>();
|
||||
long totalCount = 0L;
|
||||
long totalTime = 0L;
|
||||
Set<ObjectName> names = server.queryNames(
|
||||
new ObjectName(ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*"),
|
||||
null
|
||||
);
|
||||
for (ObjectName name : names) {
|
||||
Long collectionCount = safeLongAttribute(server, name, "CollectionCount");
|
||||
if (collectionCount != null && collectionCount >= 0L) {
|
||||
totalCount += collectionCount.longValue();
|
||||
}
|
||||
Long collectionTime = safeLongAttribute(server, name, "CollectionTime");
|
||||
if (collectionTime != null && collectionTime >= 0L) {
|
||||
totalTime += collectionTime.longValue();
|
||||
}
|
||||
|
||||
Object lastGcInfo = safeAttribute(server, name, "LastGcInfo");
|
||||
if (lastGcInfo instanceof CompositeData) {
|
||||
CompositeData data = (CompositeData) lastGcInfo;
|
||||
LinkedHashMap<String, Object> event = new LinkedHashMap<>();
|
||||
event.put("timestamp", sampleTimestamp);
|
||||
event.put("name", name.getKeyProperty("name"));
|
||||
Object gcCause = compositeValue(data, "GcCause");
|
||||
if (gcCause != null) {
|
||||
event.put("cause", String.valueOf(gcCause));
|
||||
}
|
||||
Object gcAction = compositeValue(data, "GcAction");
|
||||
if (gcAction != null) {
|
||||
event.put("action", String.valueOf(gcAction));
|
||||
}
|
||||
Object duration = compositeValue(data, "duration");
|
||||
if (duration instanceof Number) {
|
||||
event.put("durationMs", ((Number) duration).longValue());
|
||||
}
|
||||
long beforeUsedBytes = sumMemoryUsage(compositeValue(data, "memoryUsageBeforeGc"));
|
||||
if (beforeUsedBytes > 0L) {
|
||||
event.put("beforeUsedBytes", beforeUsedBytes);
|
||||
}
|
||||
long afterUsedBytes = sumMemoryUsage(compositeValue(data, "memoryUsageAfterGc"));
|
||||
if (afterUsedBytes > 0L) {
|
||||
event.put("afterUsedBytes", afterUsedBytes);
|
||||
}
|
||||
recentGcEvents.add(event);
|
||||
}
|
||||
}
|
||||
point.put("gcCollectionCount", totalCount);
|
||||
point.put("gcCollectionTimeMs", totalTime);
|
||||
result.put("recentGcEvents", recentGcEvents);
|
||||
addUnique(availableMetrics, "gc.count");
|
||||
addUnique(availableMetrics, "gc.time");
|
||||
if (!recentGcEvents.isEmpty()) {
|
||||
addUnique(availableMetrics, "gc.events");
|
||||
} else {
|
||||
addUnique(missingMetrics, "gc.events");
|
||||
}
|
||||
} catch (Exception error) {
|
||||
addUnique(missingMetrics, "gc.count");
|
||||
addUnique(missingMetrics, "gc.time");
|
||||
addUnique(missingMetrics, "gc.events");
|
||||
addUnique(providerWarnings, "gc metrics unavailable: " + error.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectName osName = new ObjectName("java.lang:type=OperatingSystem");
|
||||
Double processCpuLoad = safeDoubleAttribute(server, osName, "ProcessCpuLoad");
|
||||
if (processCpuLoad != null && processCpuLoad >= 0d) {
|
||||
point.put("processCpuLoad", processCpuLoad.doubleValue());
|
||||
addUnique(availableMetrics, "cpu.process");
|
||||
} else {
|
||||
addUnique(missingMetrics, "cpu.process");
|
||||
}
|
||||
|
||||
Double systemCpuLoad = safeDoubleAttribute(server, osName, "SystemCpuLoad");
|
||||
if (systemCpuLoad != null && systemCpuLoad >= 0d) {
|
||||
point.put("systemCpuLoad", systemCpuLoad.doubleValue());
|
||||
addUnique(availableMetrics, "cpu.system");
|
||||
} else {
|
||||
addUnique(missingMetrics, "cpu.system");
|
||||
}
|
||||
|
||||
Long processRssBytes = firstNumericAttribute(
|
||||
server,
|
||||
osName,
|
||||
"ProcessResidentMemorySize",
|
||||
"ResidentSetSize",
|
||||
"ResidentMemorySize"
|
||||
);
|
||||
if (processRssBytes != null && processRssBytes >= 0L) {
|
||||
point.put("processRssBytes", processRssBytes.longValue());
|
||||
addUnique(availableMetrics, "memory.rss");
|
||||
} else {
|
||||
addUnique(missingMetrics, "memory.rss");
|
||||
}
|
||||
|
||||
Long committedVirtualMemoryBytes = safeLongAttribute(server, osName, "CommittedVirtualMemorySize");
|
||||
if (committedVirtualMemoryBytes != null && committedVirtualMemoryBytes >= 0L) {
|
||||
point.put("committedVirtualMemoryBytes", committedVirtualMemoryBytes.longValue());
|
||||
addUnique(availableMetrics, "memory.virtual");
|
||||
} else {
|
||||
addUnique(missingMetrics, "memory.virtual");
|
||||
}
|
||||
} catch (Exception error) {
|
||||
addUnique(missingMetrics, "cpu.process");
|
||||
addUnique(missingMetrics, "cpu.system");
|
||||
addUnique(missingMetrics, "memory.rss");
|
||||
addUnique(missingMetrics, "memory.virtual");
|
||||
addUnique(providerWarnings, "process/system metrics unavailable: " + error.getMessage());
|
||||
}
|
||||
|
||||
result.put("point", point);
|
||||
result.put("availableMetrics", availableMetrics);
|
||||
result.put("missingMetrics", missingMetrics);
|
||||
result.put("providerWarnings", providerWarnings);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Object> previewChange(
|
||||
MBeanServerConnection server,
|
||||
TargetSpec target,
|
||||
@@ -858,6 +1069,82 @@ final class JmxRuntime {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addUnique(List<String> items, String value) {
|
||||
if (value == null || value.isEmpty() || items.contains(value)) {
|
||||
return;
|
||||
}
|
||||
items.add(value);
|
||||
}
|
||||
|
||||
private static Object safeAttribute(MBeanServerConnection server, ObjectName objectName, String attribute) {
|
||||
try {
|
||||
return server.getAttribute(objectName, attribute);
|
||||
} catch (Exception error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Long safeLongAttribute(MBeanServerConnection server, ObjectName objectName, String attribute) {
|
||||
Object value = safeAttribute(server, objectName, attribute);
|
||||
return value instanceof Number ? ((Number) value).longValue() : null;
|
||||
}
|
||||
|
||||
private static Double safeDoubleAttribute(MBeanServerConnection server, ObjectName objectName, String attribute) {
|
||||
Object value = safeAttribute(server, objectName, attribute);
|
||||
return value instanceof Number ? ((Number) value).doubleValue() : null;
|
||||
}
|
||||
|
||||
private static Long firstNumericAttribute(
|
||||
MBeanServerConnection server,
|
||||
ObjectName objectName,
|
||||
String... attributeNames
|
||||
) {
|
||||
for (String attributeName : attributeNames) {
|
||||
Long value = safeLongAttribute(server, objectName, attributeName);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Object compositeValue(CompositeData data, String key) {
|
||||
if (data == null || key == null || !data.getCompositeType().containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
return data.get(key);
|
||||
}
|
||||
|
||||
private static long sumMemoryUsage(Object value) {
|
||||
long total = 0L;
|
||||
if (value instanceof TabularData) {
|
||||
for (Object item : ((TabularData) value).values()) {
|
||||
total += usedFromMemoryUsage(item);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
if (value instanceof Map<?, ?>) {
|
||||
for (Object item : ((Map<?, ?>) value).values()) {
|
||||
total += usedFromMemoryUsage(item);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
return usedFromMemoryUsage(value);
|
||||
}
|
||||
|
||||
private static long usedFromMemoryUsage(Object value) {
|
||||
if (!(value instanceof CompositeData)) {
|
||||
return 0L;
|
||||
}
|
||||
CompositeData data = (CompositeData) value;
|
||||
Object used = compositeValue(data, "used");
|
||||
if (used instanceof Number) {
|
||||
return ((Number) used).longValue();
|
||||
}
|
||||
Object nestedValue = compositeValue(data, "value");
|
||||
return usedFromMemoryUsage(nestedValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> requiredObject(Object value, String label) {
|
||||
if (value instanceof Map<?, ?>) {
|
||||
|
||||
Reference in New Issue
Block a user