增加采集内存详情、健康状态方法
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user