diff --git a/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java index f9f6098..fe3cf11 100644 --- a/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java +++ b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java @@ -77,6 +77,8 @@ public enum MsgEnum { tcpdump结果上报("TCPDUMP_RESULT"), + 内存详情上报("MEMORY_DETAILS"), + 多网IP探测上报("NETWORK_DETECT"); private String value; diff --git a/src/main/java/com/tongran/agent/client/core/vo/MemoryDetailsVO.java b/src/main/java/com/tongran/agent/client/core/vo/MemoryDetailsVO.java new file mode 100644 index 0000000..62dddd9 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/MemoryDetailsVO.java @@ -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配置错误) +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java index 9d5d09e..9f373fb 100644 --- a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java +++ b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java @@ -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 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); } /** diff --git a/src/main/java/com/tongran/agent/client/service/MemoryService.java b/src/main/java/com/tongran/agent/client/service/MemoryService.java index 72a8980..7f75271 100644 --- a/src/main/java/com/tongran/agent/client/service/MemoryService.java +++ b/src/main/java/com/tongran/agent/client/service/MemoryService.java @@ -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 getMemoryDetails(); + + Integer getMemorySlotsNum(); } diff --git a/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java index 12c5b03..e318f5f 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java @@ -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 diskList(long timestamp) { diff --git a/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java index 53a51a0..6e1f6ec 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java @@ -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 getMemoryDetails() { + List memoryList = new ArrayList<>(); + String command = "dmidecode -t memory"; + Process process = null; + BufferedReader reader = null; + // 获取 ipmitool sdr type "Memory" 的输出,用于后续匹配 + List 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 getIpmitoolSdrLines() { + List 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 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; + } + } }