优化磁盘健康信息采集
This commit is contained in:
@@ -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<>();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user