From 95922cac09707b3df2b3994c86c44386b6534922 Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Mon, 9 Feb 2026 18:05:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A3=81=E7=9B=98=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E4=BF=A1=E6=81=AF=E9=87=87=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/service/impl/DiskServiceImpl.java | 213 ++++++++++++++++-- src/main/resources/application.yml | 2 +- 2 files changed, 193 insertions(+), 22 deletions(-) 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 3e73d8a..12c5b03 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 @@ -15,6 +15,16 @@ import java.util.*; @Service public class DiskServiceImpl implements DiskService { + // 定义需要排除的虚拟设备前缀列表 + private static final String[] VIRTUAL_DISK_PREFIXES = { + "/dev/dm", // device-mapper (LVM/加密/RAID) + "/dev/nbd", // 网络块设备 + "/dev/loop", // 回环设备 + "/dev/ram", // RAM磁盘 + "/dev/drbd", // DRBD + "/dev/vd", // VirtIO (KVM) + "/dev/xvd" // Xen + }; @Override public List diskList(long timestamp) { List tempList = new ArrayList<>(); @@ -37,7 +47,16 @@ public class DiskServiceImpl implements DiskService { int score = getNvmeHealthScore(diskFullName); diskVO.setHealthScore(score); } - if (!diskFullName.startsWith("/dev/dm")) { + // 在判断中使用 + boolean isVirtualDisk = false; + for (String prefix : VIRTUAL_DISK_PREFIXES) { + if (diskFullName.startsWith(prefix)) { + isVirtualDisk = true; + break; + } + } + + if (!isVirtualDisk) { diskVO.setHealthStatus(isHealth); } String diskName = diskFullName.replace("/dev/", ""); @@ -252,6 +271,141 @@ public class DiskServiceImpl implements DiskService { } } public int getNvmeHealthScore(String diskDevice) { + int nvmeScore = getNvmeHealthScoreByNvmeCli(diskDevice); + if (nvmeScore >= 0) { + return nvmeScore; + } + + // 回退到 smartctl + return getNvmeHealthScoreBySmartctl(diskDevice); + } + + private int getNvmeHealthScoreByNvmeCli(String diskDevice) { + try { + Process process = new ProcessBuilder("nvme", "smart-log", diskDevice) + .redirectErrorStream(true) + .start(); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()))) { + + int exitCode = process.waitFor(); + if (exitCode != 0) { + return -1; // 命令执行失败 + } + + int percentageUsed = -1; + int availableSpare = -1; + String line; + + // 解析 nvme smart-log 输出 + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + // 处理下划线和空格两种格式 + if (isPercentageUsedLine(line)) { + percentageUsed = extractNvmeValueFromLine(line); + } + // 处理 available spare,排除 threshold + else if (isAvailableSpareLine(line)) { + availableSpare = extractNvmeValueFromLine(line); + } + } + + // 计算健康分数 + if (percentageUsed >= 0) { + // 健康分数 = 100 - 已用百分比 + return 100 - percentageUsed; + } else if (availableSpare >= 0) { + // 如果没有 Percentage Used,使用 Available Spare + return availableSpare; + } else { + return -1; // 无法获取健康信息 + } + } + + } catch (Exception e) { + return -1; + } + } + + private boolean isPercentageUsedLine(String line) { + String lowerLine = line.toLowerCase(); + + // 支持多种格式: + // 1. percentage_used : 0% + // 2. percentage used : 0% + // 3. Percentage Used: 0% + // 4. percentage used: 0% + + // 移除所有空格和下划线,统一比较 + String unified = lowerLine.replaceAll("[_\\s]", ""); + return unified.startsWith("percentageused"); + } + + private boolean isAvailableSpareLine(String line) { + String lowerLine = line.toLowerCase(); + + // 排除 threshold 行 + if (lowerLine.contains("threshold")) { + return false; + } + + // 支持多种格式: + // 1. available_spare : 100% + // 2. available spare : 100% + // 3. Available Spare: 100% + // 4. available spare: 100% + + // 移除所有空格和下划线,统一比较 + String unified = lowerLine.replaceAll("[_\\s]", ""); + return unified.startsWith("availablespare"); + } + + private int extractNvmeValueFromLine(String line) { + try { + // 按冒号分割 + String[] parts = line.split(":"); + if (parts.length > 1) { + String valuePart = parts[1].trim(); + + // 移除可能存在的逗号(如 1,234% -> 1234%) + valuePart = valuePart.replace(",", ""); + + // 查找百分比符号 + int percentIndex = valuePart.indexOf('%'); + if (percentIndex != -1) { + // 提取%前面的数字部分 + String numberStr = valuePart.substring(0, percentIndex).trim(); + // 移除所有非数字字符 + numberStr = numberStr.replaceAll("[^0-9]", "").trim(); + if (!numberStr.isEmpty()) { + return Integer.parseInt(numberStr); + } + } else { + // 如果没有百分号,尝试解析第一个数字 + String[] tokens = valuePart.split("\\s+"); + if (tokens.length > 0) { + String numberStr = tokens[0].trim(); + // 移除所有非数字字符 + numberStr = numberStr.replaceAll("[^0-9]", "").trim(); + if (!numberStr.isEmpty()) { + return Integer.parseInt(numberStr); + } + } + } + } + } catch (NumberFormatException e) { + // 解析失败 + } + return -1; + } + + + private int getNvmeHealthScoreBySmartctl(String diskDevice) { try { // 检查是否为 NVMe 设备 if (!diskDevice.startsWith("/dev/nvme")) { @@ -276,29 +430,18 @@ public class DiskServiceImpl implements DiskService { // 解析 smartctl 输出 while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + // 查找 Percentage Used(已用百分比) if (line.contains("Percentage Used:")) { - String[] parts = line.split(":"); - if (parts.length > 1) { - String value = parts[1].trim().split("\\s+")[0]; - try { - percentageUsed = Integer.parseInt(value.replace("%", "")); - } catch (NumberFormatException e) { - // 忽略解析错误 - } - } + percentageUsed = extractSmartctlValueFromLine(line); } // 查找 Available Spare(可用备用空间) - else if (line.contains("Available Spare:")) { - String[] parts = line.split(":"); - if (parts.length > 1) { - String value = parts[1].trim().split("\\s+")[0]; - try { - availableSpare = Integer.parseInt(value.replace("%", "")); - } catch (NumberFormatException e) { - // 忽略解析错误 - } - } + else if (line.contains("Available Spare:") && !line.contains("Available Spare Threshold")) { + availableSpare = extractSmartctlValueFromLine(line); } } @@ -312,7 +455,6 @@ public class DiskServiceImpl implements DiskService { } else { return -1; // 无法获取健康信息 } - } } catch (Exception e) { @@ -320,6 +462,35 @@ public class DiskServiceImpl implements DiskService { } } + private int extractSmartctlValueFromLine(String line) { + try { + // 按冒号分割 + String[] parts = line.split(":"); + if (parts.length > 1) { + String valuePart = parts[1].trim(); + + // 处理格式: "Percentage Used: 5%" + if (valuePart.contains("%")) { + String[] valueParts = valuePart.split("\\s+"); + for (String part : valueParts) { + if (part.endsWith("%")) { + return Integer.parseInt(part.substring(0, part.length() - 1).trim()); + } + } + } else { + // 处理格式: "Percentage Used: 5" + String[] valueParts = valuePart.split("\\s+"); + if (valueParts.length > 0) { + return Integer.parseInt(valueParts[0].trim()); + } + } + } + } catch (NumberFormatException e) { + // 解析失败 + } + return -1; + } + @Override public List pointList(long timestamp) { List list = new ArrayList<>(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 53534df..0079a48 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,7 +6,7 @@ spring: matching-strategy: ant_path_matcher application: name: tr-agent-client - version: 1.1.14 + version: 1.1.16 conf-path: /usr/local/tongran/conf script-path: /usr/local/tongran/sbin tmp-path: /usr/local/tongran/tmp