资源情况监测方法
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
package org.jeecg.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
public class ContainerMetricsUtil {
|
||||
|
||||
// 缓存上一次 CPU 使用数据(用于计算使用率)
|
||||
private static final Map<String, Long> LAST_CPU_USAGE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Long> LAST_CPU_TIME = new ConcurrentHashMap<>();
|
||||
|
||||
// 判断是否为 cgroup v2
|
||||
private static boolean isCgroupV2() {
|
||||
return Files.exists(Paths.get("/sys/fs/cgroup/cgroup.controllers"));
|
||||
}
|
||||
|
||||
// 通用读取第一行
|
||||
private static String readFirstLine(String path) throws IOException {
|
||||
java.nio.file.Path p = Paths.get(path);
|
||||
if (!Files.exists(p)) {
|
||||
throw new IOException("文件不存在: " + path);
|
||||
}
|
||||
List<String> lines = Files.readAllLines(p);
|
||||
if (lines.isEmpty()) {
|
||||
throw new IOException("文件为空: " + path);
|
||||
}
|
||||
return lines.get(0).trim();
|
||||
}
|
||||
|
||||
// ========================
|
||||
// 内存指标
|
||||
// ========================
|
||||
|
||||
public static long getMemoryLimitBytes() {
|
||||
try {
|
||||
if (isCgroupV2()) {
|
||||
String max = readFirstLine("/sys/fs/cgroup/memory.max");
|
||||
if ("max".equals(max)) return -1; // 无限制
|
||||
return Long.parseLong(max);
|
||||
} else {
|
||||
long limit = Long.parseLong(readFirstLine("/sys/fs/cgroup/memory/memory.limit_in_bytes"));
|
||||
// 过滤“无限制”的极大值(如 9223372036854771712)
|
||||
return (limit > (1L << 40)) ? -1 : limit;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("无法读取内存限制", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getMemoryUsageBytes() {
|
||||
try {
|
||||
if (isCgroupV2()) {
|
||||
return Long.parseLong(readFirstLine("/sys/fs/cgroup/memory.current"));
|
||||
} else {
|
||||
return Long.parseLong(readFirstLine("/sys/fs/cgroup/memory/memory.usage_in_bytes"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("无法读取内存使用量", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// CPU 指标
|
||||
// ========================
|
||||
|
||||
/**
|
||||
* 获取 CPU 限制(核数),-1 表示无限制
|
||||
*/
|
||||
public static double getCpuLimitCores() {
|
||||
try {
|
||||
if (isCgroupV2()) {
|
||||
String cpuMax = readFirstLine("/sys/fs/cgroup/cpu.max");
|
||||
String[] parts = cpuMax.split(" ");
|
||||
if ("max".equals(parts[0])) return -1;
|
||||
long quota = Long.parseLong(parts[0]);
|
||||
long period = Long.parseLong(parts[1]);
|
||||
return (double) quota / period;
|
||||
} else {
|
||||
long quota = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"));
|
||||
if (quota == -1) return -1;
|
||||
long period = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpu.cfs_period_us"));
|
||||
return (double) quota / period;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("无法读取 CPU 限制", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CPU 使用率(百分比,单核基准)
|
||||
* 首次调用返回 0,后续调用返回基于时间差的使用率
|
||||
*/
|
||||
public static double getCpuUsagePercent() {
|
||||
try {
|
||||
long currentUsage;
|
||||
if (isCgroupV2()) {
|
||||
// 读取 /sys/fs/cgroup/cpu.stat
|
||||
String content = readFirstLine("/sys/fs/cgroup/cpu.stat");
|
||||
String[] lines = content.split("\n");
|
||||
long usageUsec = -1;
|
||||
for (String line : lines) {
|
||||
if (line.startsWith("usage_usec")) {
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
if (parts.length >= 2) {
|
||||
usageUsec = Long.parseLong(parts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (usageUsec == -1) {
|
||||
log.warn("cgroup v2: 未找到 usage_usec,无法计算 CPU 使用率");
|
||||
return -1;
|
||||
}
|
||||
currentUsage = usageUsec * 1000L; // 转为纳秒
|
||||
} else {
|
||||
// cgroup v1
|
||||
currentUsage = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpuacct.usage"));
|
||||
}
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
String key = isCgroupV2() ? "v2" : "v1";
|
||||
|
||||
Long lastUsage = LAST_CPU_USAGE.get(key);
|
||||
Long lastTime = LAST_CPU_TIME.get(key);
|
||||
|
||||
if (lastUsage == null || lastTime == null) {
|
||||
LAST_CPU_USAGE.put(key, currentUsage);
|
||||
LAST_CPU_TIME.put(key, currentTime);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
long usageDelta = currentUsage - lastUsage;
|
||||
long timeDeltaNs = currentTime - lastTime;
|
||||
|
||||
if (timeDeltaNs <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double cpuPercent = (usageDelta * 100.0) / timeDeltaNs;
|
||||
|
||||
// 归一化(可选)
|
||||
double limit = getCpuLimitCores();
|
||||
if (limit > 0 && limit < 1.0) {
|
||||
cpuPercent = Math.min(cpuPercent / limit, 100.0);
|
||||
} else {
|
||||
cpuPercent = Math.min(cpuPercent, 100.0);
|
||||
}
|
||||
|
||||
LAST_CPU_USAGE.put(key, currentUsage);
|
||||
LAST_CPU_TIME.put(key, currentTime);
|
||||
|
||||
return Math.max(0.0, cpuPercent);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.debug("计算 CPU 使用率失败", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// 综合方法:返回所有指标
|
||||
// ========================
|
||||
|
||||
public static Map<String, Object> getContainerMetrics() {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
|
||||
// 内存
|
||||
long memLimit = getMemoryLimitBytes();
|
||||
long memUsage = getMemoryUsageBytes();
|
||||
metrics.put("memoryLimitBytes", memLimit > 0 ? memLimit : null);
|
||||
metrics.put("memoryUsedBytes", memUsage > 0 ? memUsage : null);
|
||||
if (memLimit > 0 && memUsage > 0) {
|
||||
double memPercent = Math.round((memUsage * 100.0 / memLimit) * 100) / 100.0;
|
||||
metrics.put("memoryPercent", memPercent);
|
||||
}
|
||||
|
||||
// CPU
|
||||
double cpuLimit = getCpuLimitCores();
|
||||
double cpuUsage = getCpuUsagePercent();
|
||||
metrics.put("cpuLimitCores", cpuLimit > 0 ? cpuLimit : null);
|
||||
metrics.put("cpuUsagePercent", cpuUsage >= 0 ? cpuUsage : null);
|
||||
|
||||
// JVM 堆内存
|
||||
long heapUsed = java.lang.management.ManagementFactory.getMemoryMXBean()
|
||||
.getHeapMemoryUsage().getUsed();
|
||||
metrics.put("jvmHeapUsedBytes", heapUsed);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
// 辅助:格式化字节(仅用于日志,不放入返回值)
|
||||
private static String formatBytes(long bytes) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
if (bytes < 1024 * 1024) {
|
||||
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||
} else {
|
||||
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
// 便捷方法:记录日志 + 返回数据
|
||||
public static Map<String, Object> logAndCollectMetrics() {
|
||||
Map<String, Object> m = getContainerMetrics();
|
||||
log.info(
|
||||
"容器资源 | 内存: {} / {} ({}%) | CPU: {}% (限制: {}核) | JVM堆: {}",
|
||||
m.get("memoryUsedBytes") != null ? formatBytes((Long) m.get("memoryUsedBytes")) : "N/A",
|
||||
m.get("memoryLimitBytes") != null ? formatBytes((Long) m.get("memoryLimitBytes")) : "N/A",
|
||||
m.get("memoryPercent"),
|
||||
m.get("cpuUsagePercent"),
|
||||
m.get("cpuLimitCores"),
|
||||
formatBytes((Long) m.get("jvmHeapUsedBytes"))
|
||||
);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.jeecg.util;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class SystemInfoUtil {
|
||||
|
||||
/**
|
||||
* 打印当前 Java 进程在容器中的资源使用情况(CPU、内存等)
|
||||
* 适用于 Docker/Kubernetes 环境(需 JDK 8u191+ 或 JDK 10+)
|
||||
*/
|
||||
/**
|
||||
* 获取并可选打印当前 Java 进程在容器中的资源使用情况
|
||||
*
|
||||
* @param logEnabled 是否同时输出日志(true=打印日志,false=仅返回数据)
|
||||
* @return 包含资源指标的 Map
|
||||
*/
|
||||
public static Map<String, Object> getContainerMetrics(boolean logEnabled) {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
try {
|
||||
OperatingSystemMXBean osBean = (OperatingSystemMXBean)
|
||||
ManagementFactory.getOperatingSystemMXBean();
|
||||
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
|
||||
MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
|
||||
|
||||
// CPU 使用率(百分比)
|
||||
double processCpuLoad = osBean.getProcessCpuLoad();
|
||||
Double cpuPercent = (!Double.isNaN(processCpuLoad) && processCpuLoad >= 0)
|
||||
? Math.round(processCpuLoad * 100 * 100.0) / 100.0 // 保留两位小数
|
||||
: null;
|
||||
metrics.put("cpuPercent", cpuPercent);
|
||||
|
||||
// 容器内存(关键:受 Docker -m 限制)
|
||||
long totalMem = osBean.getTotalPhysicalMemorySize(); // 容器总内存限制
|
||||
long freeMem = osBean.getFreePhysicalMemorySize();
|
||||
long usedMem = totalMem - freeMem;
|
||||
metrics.put("memoryTotalBytes", totalMem > 0 ? totalMem : null);
|
||||
metrics.put("memoryUsedBytes", totalMem > 0 ? usedMem : null);
|
||||
Double memPercent = (totalMem > 0)
|
||||
? Math.round((usedMem * 100.0 / totalMem) * 100.0) / 100.0
|
||||
: null;
|
||||
metrics.put("memoryPercent", memPercent);
|
||||
|
||||
// JVM 内存
|
||||
metrics.put("jvmHeapUsedBytes", heap.getUsed());
|
||||
metrics.put("jvmNonHeapUsedBytes", nonHeap.getUsed());
|
||||
|
||||
// 进程运行时间(毫秒)
|
||||
long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||
metrics.put("processUptimeMs", uptime);
|
||||
|
||||
// 可选:格式化后的字符串(便于日志展示)
|
||||
String cpuStr = cpuPercent != null ? String.format("%.2f%%", cpuPercent) : "N/A";
|
||||
String memTotalStr = formatBytes(totalMem);
|
||||
String memUsedStr = formatBytes(usedMem);
|
||||
String memPercentStr = memPercent != null ? String.format("%.1f%%", memPercent) : "N/A";
|
||||
String heapStr = formatBytes(heap.getUsed());
|
||||
String nonHeapStr = formatBytes(nonHeap.getUsed());
|
||||
|
||||
if (logEnabled) {
|
||||
log.info(
|
||||
"容器资源 | CPU: {} | 内存: {} / {} ({}) | JVM堆: {} | JVM非堆: {}",
|
||||
cpuStr, memUsedStr, memTotalStr, memPercentStr, heapStr, nonHeapStr
|
||||
);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("获取容器指标失败", e);
|
||||
// 即使失败也返回空 map(或可根据需求抛异常)
|
||||
return new HashMap<>();
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷方法:获取指标并自动记录日志
|
||||
*/
|
||||
public static Map<String, Object> logContainerMetrics() {
|
||||
return getContainerMetrics(true);
|
||||
}
|
||||
|
||||
// 辅助方法:字节转易读格式(仅用于日志字符串,不放入返回 map)
|
||||
private static String formatBytes(long bytes) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
if (bytes < 1024 * 1024) {
|
||||
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||
} else {
|
||||
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.jeecg.modules.system.task;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.util.ContainerMetricsUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MonitorSystemJob {
|
||||
|
||||
@XxlJob(value = "systemInfoJob")
|
||||
public ReturnT<String> systemInfoJob(String time) {
|
||||
try {
|
||||
// Map<String, Object> metrics = SystemInfoUtil.logContainerMetrics();
|
||||
// // 使用 Spring 的 JSON 工具转为标准 JSON
|
||||
// String jsonResult = new ObjectMapper().writeValueAsString(metrics);
|
||||
//
|
||||
// return new ReturnT<>(jsonResult);
|
||||
// 第一次调用:初始化 CPU 计算(返回 0%)
|
||||
ContainerMetricsUtil.logAndCollectMetrics();
|
||||
|
||||
// 等待 1 秒以获取有效 CPU 使用率
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 第二次调用:获取真实 CPU 使用率
|
||||
Map<String, Object> metrics = ContainerMetricsUtil.logAndCollectMetrics();
|
||||
|
||||
// 转为 JSON 返回给 XXL-JOB
|
||||
String json = new ObjectMapper().writeValueAsString(metrics);
|
||||
return new ReturnT<>(ReturnT.SUCCESS_CODE, json);
|
||||
} catch (Exception e) {
|
||||
return ReturnT.FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user