增加采集内存详情、健康状态方法

This commit is contained in:
gaoyutao
2026-02-25 17:52:50 +08:00
parent 95922cac09
commit 3ff73519f9
6 changed files with 381 additions and 2 deletions
@@ -77,6 +77,8 @@ public enum MsgEnum {
tcpdump结果上报("TCPDUMP_RESULT"),
内存详情上报("MEMORY_DETAILS"),
多网IP探测上报("NETWORK_DETECT");
private String value;
@@ -0,0 +1,20 @@
package com.tongran.agent.client.core.vo;
import lombok.Data;
@Data
public class MemoryDetailsVO {
private String name; // 名称(如 DIMM000
private String location; // 位置(mainboard
private String manufacturer; // 厂商(Samsung
private Long capacity; // 容量(MB,如 32768
private Integer frequency; // 主频(MHz,如 2133
private String serialNumber; // 序列号(如 0x32A1EA07
private String type; // 类型(DDR4
private Integer minVoltage; // 最小电压(mV,如 1200
private String rank; // RANK(列)(如 4 rank
private String bitWidth; // 位宽(如 72 bit
private String technology; // 技术(Synchronous|Registered (B N/A
private String partNumber; // 部件编码(如 M386A4G40DM0-CPB
private String healthStatus; // 健康状态(1正常/0配置错误)
}
@@ -378,8 +378,22 @@ public class BusinessTasks {
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.系统其他上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送系统其他信息-内存利用率信息包={}",JSON.toJSONString(message));
// 执行内存详情包
List<MemoryDetailsVO> memDetailsList = memoryService.getMemoryDetails();
Integer total = memoryService.getMemorySlotsNum();
JSONObject object = new JSONObject();
if(memDetailsList != null && !memDetailsList.isEmpty()){
object.put("details", memDetailsList);
if(total != null){
object.put("total", total);
}
}
object.put("timestamp", timestamp);
Message memDetails = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.内存详情上报.getValue()).data(object.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), memDetails);
AssertLog.info("发送内存详情信息包={}",JSON.toJSONString(memDetails));
}
AssertLog.info("系统其他信息-内存利用率采集定时任务执行 - task #{} completed", count);
AssertLog.info("系统其他信息-内存利用率采集/内存详情定时任务执行 - task #{} completed", count);
}
/**
@@ -1,7 +1,15 @@
package com.tongran.agent.client.service;
import com.tongran.agent.client.core.vo.MemoryDetailsVO;
import com.tongran.agent.client.core.vo.MemoryVO;
import java.io.IOException;
import java.util.List;
public interface MemoryService {
MemoryVO get();
List<MemoryDetailsVO> getMemoryDetails();
Integer getMemorySlotsNum();
}
@@ -23,7 +23,8 @@ public class DiskServiceImpl implements DiskService {
"/dev/ram", // RAM磁盘
"/dev/drbd", // DRBD
"/dev/vd", // VirtIO (KVM)
"/dev/xvd" // Xen
"/dev/xvd", // Xen
"/dev/bcache"
};
@Override
public List<DiskVO> diskList(long timestamp) {
@@ -1,13 +1,20 @@
package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.vo.MemoryDetailsVO;
import com.tongran.agent.client.core.vo.MemoryVO;
import com.tongran.agent.client.service.MemoryService;
import com.tongran.agent.client.utils.AgentDataUtil;
import com.tongran.agent.client.utils.AssertLog;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class MemoryServiceImpl implements MemoryService {
@@ -72,5 +79,332 @@ public class MemoryServiceImpl implements MemoryService {
return memoryVO;
}
@Override
public List<MemoryDetailsVO> getMemoryDetails() {
List<MemoryDetailsVO> memoryList = new ArrayList<>();
String command = "dmidecode -t memory";
Process process = null;
BufferedReader reader = null;
// 获取 ipmitool sdr type "Memory" 的输出,用于后续匹配
List<String> sdrLines = getIpmitoolSdrLines();
try {
// 执行过滤命令
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c", command + " | awk '/^Handle 0x/ {if (buffer && buffer ~ /Size: [0-9]/) print buffer; buffer=$0; next} {buffer=buffer ORS $0} END {if (buffer && buffer ~ /Size: [0-9]/) print buffer}'"
);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder block = new StringBuilder();
String line;
int totalMemoryCount = 0;
while ((line = reader.readLine()) != null) {
block.append(line).append("\n");
if (line.trim().isEmpty() && block.length() > 0) {
MemoryDetailsVO vo = parseMemoryBlock(block.toString(), sdrLines);
if (vo != null) {
memoryList.add(vo);
totalMemoryCount++;
}
block.setLength(0);
}
}
// 处理最后一个块
if (block.length() > 0) {
MemoryDetailsVO vo = parseMemoryBlock(block.toString(), sdrLines);
if (vo != null) {
memoryList.add(vo);
totalMemoryCount++;
}
}
// 等待命令完成
int exitCode = process.waitFor();
if (exitCode != 0) {
AssertLog.warn("dmidecode命令执行异常, 退出码: {}", exitCode);
}
// 记录统计日志
if (totalMemoryCount > 0) {
AssertLog.info("成功获取内存信息, 共发现 {} 根内存条", totalMemoryCount);
// 计算总容量
long totalCapacityGB = memoryList.stream()
.mapToLong(MemoryDetailsVO::getCapacity)
.sum() / 1024L;
AssertLog.info("总内存容量: {} GB", totalCapacityGB);
} else {
AssertLog.warn("未检测到已安装的内存条");
}
} catch (IOException e) {
AssertLog.error("执行内存信息获取命令失败: {}", e.getMessage(), e);
} catch (InterruptedException e) {
AssertLog.error("命令执行被中断: {}", e.getMessage(), e);
Thread.currentThread().interrupt();
} catch (Exception e) {
AssertLog.error("解析内存信息时发生未知错误: {}", e.getMessage(), e);
} finally {
// 清理资源
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
AssertLog.warn("关闭流时发生错误: {}", e.getMessage());
}
}
if (process != null) {
process.destroy();
}
}
// 处理 dmidecode 未监测到但 ipmitool 检测到异常的插槽
if (!sdrLines.isEmpty()) {
for (String sdrLine : sdrLines) {
// 提取 DIMM 名称,如 "DIMM110"
String dimmName = sdrLine.split("\\|")[0].trim();
boolean alreadyExists = memoryList.stream()
.anyMatch(mem -> dimmName.equals(mem.getName()));
// 如果 dmidecode 中没有这个插槽,但 ipmitool 检测到异常
if (!alreadyExists) {
MemoryDetailsVO errorSlot = new MemoryDetailsVO();
errorSlot.setName(dimmName);
errorSlot.setLocation("Unknown");
errorSlot.setManufacturer("Unknown");
errorSlot.setCapacity(0L); // 没有安装内存,容量为0
errorSlot.setFrequency(0);
errorSlot.setSerialNumber("Unknown");
errorSlot.setType("Unknown");
errorSlot.setMinVoltage(0);
errorSlot.setRank("Unknown");
errorSlot.setBitWidth("Unknown");
errorSlot.setTechnology("Unknown");
errorSlot.setPartNumber("Unknown");
errorSlot.setHealthStatus("0"); // 异常状态
memoryList.add(errorSlot);
AssertLog.debug("添加未在dmidecode中监测到的异常插槽: {}", dimmName);
}
}
}
return memoryList;
}
@Override
public Integer getMemorySlotsNum() {
ProcessBuilder processBuilder = null;
Process process = null;
BufferedReader reader = null;
try {
// 使用bash执行管道命令
processBuilder = new ProcessBuilder(
"bash", "-c",
"dmidecode -t memory | grep 'Number Of Devices:' | awk '{print $4}'"
);
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
try {
Integer slots = Integer.parseInt(line.trim());
AssertLog.info("成功获取内存插槽数量: " + slots);
return slots;
} catch (NumberFormatException e) {
AssertLog.info("解析内存插槽数量失败,输出内容: " + line);
}
}
}
AssertLog.info("未找到内存插槽数量信息");
return null;
} catch (IOException e) {
AssertLog.info("执行命令时发生IO异常: " + e.getMessage());
return null;
} catch (Exception e) {
AssertLog.info("获取内存插槽数量时发生未知异常: " + e.getMessage());
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
if (process != null) {
process.destroy();
}
} catch (IOException e) {
AssertLog.info("关闭资源时发生异常: " + e.getMessage());
}
}
}
// 获取 ipmitool sdr type "Memory" 的输出行列表
private List<String> getIpmitoolSdrLines() {
List<String> lines = new ArrayList<>();
try {
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c", "ipmitool sdr type \"Memory\" | grep -E \"DIMM.*Configuration Error\""
);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
lines.add(line.trim());
}
int exitCode = process.waitFor();
if (exitCode != 0) {
AssertLog.warn("ipmitool命令执行异常, 退出码: {}", exitCode);
}
} catch (Exception e) {
AssertLog.error("获取ipmitool sdr信息失败: {}", e.getMessage(), e);
}
return lines;
}
// 解析内存块,并注入健康状态
private MemoryDetailsVO parseMemoryBlock(String block, List<String> sdrLines) {
try {
// 跳过空块
if (block.trim().isEmpty()) {
return null;
}
MemoryDetailsVO vo = new MemoryDetailsVO();
// 正则提取字段
Pattern namePattern = Pattern.compile("Locator:\\s*(\\w+)"); // 提取 DIMM000
Pattern locationPattern = Pattern.compile("Bank Locator:\\s*(.+)");
Pattern manufacturerPattern = Pattern.compile("Manufacturer:\\s*(.+)");
Pattern capacityPattern = Pattern.compile("Size:\\s*(\\d+)\\s*GB");
Pattern frequencyPattern = Pattern.compile("Speed:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
Pattern serialNumberPattern = Pattern.compile("Serial Number:\\s*(.+)");
Pattern typePattern = Pattern.compile("Type:\\s*(.+)");
Pattern minVoltagePattern = Pattern.compile("Minimum Voltage:\\s*(\\d+\\.?\\d*)\\s*V");
Pattern rankPattern = Pattern.compile("Rank:\\s*(.+)");
Pattern bitWidthPattern = Pattern.compile("Total Width:\\s*(.+) bit");
Pattern technologyPattern = Pattern.compile("Type Detail:\\s*(.+)");
Pattern partNumberPattern = Pattern.compile("Part Number:\\s*(.+)");
Matcher nameMatcher = namePattern.matcher(block);
if (nameMatcher.find()) {
vo.setName(nameMatcher.group(1).trim());
} else {
AssertLog.debug("未找到内存名称字段: {}", block);
return null;
}
// 位置固定为 mainboard
vo.setLocation("mainboard");
Matcher manufacturerMatcher = manufacturerPattern.matcher(block);
if (manufacturerMatcher.find()) {
vo.setManufacturer(manufacturerMatcher.group(1).trim());
} else {
vo.setManufacturer("Unknown");
}
Matcher capacityMatcher = capacityPattern.matcher(block);
if (capacityMatcher.find()) {
long gb = Long.parseLong(capacityMatcher.group(1));
vo.setCapacity(gb * 1024L); // GB 转 MB
} else {
AssertLog.debug("未找到内存容量字段: {}", block);
return null;
}
Matcher frequencyMatcher = frequencyPattern.matcher(block);
if (frequencyMatcher.find()) {
vo.setFrequency(Integer.parseInt(frequencyMatcher.group(1)));
} else {
vo.setFrequency(0);
}
Matcher serialNumberMatcher = serialNumberPattern.matcher(block);
if (serialNumberMatcher.find()) {
vo.setSerialNumber(serialNumberMatcher.group(1).trim());
} else {
vo.setSerialNumber("Unknown");
}
Matcher typeMatcher = typePattern.matcher(block);
if (typeMatcher.find()) {
vo.setType(typeMatcher.group(1).trim());
} else {
vo.setType("Unknown");
}
Matcher minVoltageMatcher = minVoltagePattern.matcher(block);
if (minVoltageMatcher.find()) {
String voltageStr = minVoltageMatcher.group(1);
// 处理 1.2 V -> 1200 mV
double voltage = Double.parseDouble(voltageStr);
vo.setMinVoltage((int)(voltage * 1000));
} else {
vo.setMinVoltage(0);
}
Matcher rankMatcher = rankPattern.matcher(block);
if (rankMatcher.find()) {
vo.setRank(rankMatcher.group(1).trim() + " rank");
} else {
vo.setRank("Unknown");
}
Matcher bitWidthMatcher = bitWidthPattern.matcher(block);
if (bitWidthMatcher.find()) {
vo.setBitWidth(bitWidthMatcher.group(1).trim() + " bit");
} else {
vo.setBitWidth("Unknown");
}
Matcher technologyMatcher = technologyPattern.matcher(block);
if (technologyMatcher.find()) {
vo.setTechnology(technologyMatcher.group(1).trim());
} else {
vo.setTechnology("Unknown");
}
Matcher partNumberMatcher = partNumberPattern.matcher(block);
if (partNumberMatcher.find()) {
vo.setPartNumber(partNumberMatcher.group(1).trim());
} else {
vo.setPartNumber("Unknown");
}
// 检查健康状态
String memoryName = vo.getName(); // 例如 "DIMM000"
boolean hasConfigError = sdrLines.stream()
.anyMatch(line -> line.startsWith(memoryName));
if (hasConfigError) {
vo.setHealthStatus("0");
} else {
vo.setHealthStatus("1");
}
// 调试日志
AssertLog.debug("解析内存信息: {} - {}GB - {}MHz - 健康状态: {}",
vo.getName(), vo.getCapacity()/1024L, vo.getFrequency(), vo.getHealthStatus());
return vo;
} catch (Exception e) {
AssertLog.error("解析内存块时发生错误: {}", e.getMessage(), e);
return null;
}
}
}