资源情况监测方法

This commit is contained in:
2026-02-12 11:29:36 +08:00
parent 175238e291
commit e9784f7957
3 changed files with 369 additions and 0 deletions
@@ -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));
}
}
}
@@ -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;
}
}
}