优化磁盘健康信息采集

This commit is contained in:
gaoyutao
2026-02-09 18:05:02 +08:00
parent 01fd405e0b
commit 95922cac09
2 changed files with 193 additions and 22 deletions
@@ -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<DiskVO> diskList(long timestamp) {
List<DiskVO> 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<PointVO> pointList(long timestamp) {
List<PointVO> list = new ArrayList<>();
+1 -1
View File
@@ -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