硬件压测指标和压测脚本
目标 / 假设(短句)BASELINE_*单盘基线已经是“官方标称的 80%”(脚本常量如等)。顺序性能用大块(1MiB)测,随机性能以 4KiB 随机 IO(IOPS)测。RAID 的并行增益与写惩罚通过经验系数折算(见下表)。脚本内置的单盘基线(来自脚本常量)(脚本把这些当作“单盘(已是80%)”基础值)脚本最终使用的四个BASELINE_*(MB/s)(MB/s)(IOPS)(IOPS)RA
-
目标 / 假设(短句)
-
目标:用统一规则把“单盘基线(已取官方80%)”换算成阵列/具体磁盘的测试基线,便于自动化判定 PASS/FAIL(脚本变量:
BASELINE_*)。 -
关键假设:
-
单盘基线已经是“官方标称的 80%”(脚本常量如
HDD_7200_SEQ_READ等)。 -
顺序性能用大块(1MiB)测,随机性能以 4KiB 随机 IO(IOPS)测。
-
RAID 的并行增益与写惩罚通过经验系数折算(见下表)。
-
-
脚本内置的单盘基线(来自脚本常量)
(脚本把这些当作“单盘(已是80%)”基础值)
| 介质 | 顺序读 (MB/s) | 顺序写 (MB/s) | 随机读 (IOPS) | 随机写 (IOPS) |
| HDD 7200 RPM | 120 | 100 | 75 | 70 |
| HDD 10000 RPM | 180 | 150 | 125 | 110 |
| HDD 15000 RPM | 200 | 180 | 175 | 160 |
| SSD (SATA/SAS) | 400 | 360 | 60,000 | 56,000 |
| NVMe | 2560 | 2240 | 320,000 | 270,000 |
脚本最终使用的四个 BASELINE_* 变量:
-
BASELINE_SEQ_READ(MB/s) -
BASELINE_SEQ_WRITE(MB/s) -
BASELINE_RAND_READ_IOPS(IOPS) -
BASELINE_RAND_WRITE_IOPS(IOPS)
-
RAID / 多盘场景的基线转换公式(脚本实现的具体算法)
记:
single_*表示 单盘基线值(脚本常量),N表示 阵列总盘数(脚本通过 storcli/mdadm 检测)。
RAID0(条带化)
BASELINE_SEQ_READ = single_seq_read * N * 0.90 BASELINE_SEQ_WRITE = single_seq_write * N * 0.85 BASELINE_RAND_READ_IOPS= single_rand_read_iops * N * 0.90 BASELINE_RAND_WRITE_IOPS=single_rand_write_iops * N * 0.85
RAID1(镜像,脚本按 2 盘典型处理)
BASELINE_SEQ_READ = single_seq_read * 2 * 0.90 BASELINE_SEQ_WRITE = single_seq_write * 1.0 BASELINE_RAND_READ_IOPS= single_rand_read_iops * 2 * 0.75 BASELINE_RAND_WRITE_IOPS=single_rand_write_iops * 1.0
说明:脚本把 RAID1 的读并行按“2 副本 × 0.90/0.75”折算(适用于常见 2-way 镜像)。
RAID5(带奇偶校验)
BASELINE_SEQ_READ = single_seq_read * (N - 1) * 0.85 BASELINE_SEQ_WRITE = single_seq_write * (N - 1) * 0.85 BASELINE_RAND_READ_IOPS= single_rand_read_iops * (N - 1) * 0.80 BASELINE_RAND_WRITE_IOPS=single_rand_write_iops * (N - 1) * 0.20
说明:读按
N-1倍并行并乘 0.85;随机写严重受写惩罚,脚本用了 20% 的工程因子。
RAID10(镜像+条带)
G = N / 2 # 镜像组数(假设 N 为偶数) BASELINE_SEQ_READ = single_seq_read * G * 0.85 BASELINE_SEQ_WRITE = single_seq_write * G * 0.45 BASELINE_RAND_READ_IOPS= single_rand_read_iops * G * 0.90 BASELINE_RAND_WRITE_IOPS=single_rand_write_iops * G * 0.50
说明:RAID10 写系数脚本用 0.45(保守);随机读按组并行效果较好(0.90)。
-
为什么这样设计(简要理由)
-
读并行:阵列能并行读取多个盘的数据块 → 理论上
N(或N-1)倍,但控制器/总线/调度/IO 大小会打折(因此乘 0.85~0.90)。 -
写惩罚:RAID5 写需要读-修改-写 parity,随机写的放大最严重 → 脚本中用较低的系数(如 0.20)反映这一事实。
-
镜像写:RAID1 必须把数据写到所有副本,吞吐受单盘限制 → 写近似单盘性能(系数 1.0)。
-
工程保守性:脚本在多数系数上保守折算(不是理论极限),便于避免误判为通过。
-
演示示例(你的常见场景)
输入(单盘基线,已是 80%):
single_seq_read = 100 MB/s,single_rand_write = 55 IOPS,N = 3(RAID5)
脚本计算:
SEQ_READ_RAID5 = 100 * (3 - 1) * 0.85 = 170 MB/s RAND_WRITE_RAID5 = 55 * (3 - 1) * 0.20 = 22 IOPS
(即脚本会把 RAID5 的顺序读基线定为 170 MB/s,随机写基线定为 22 IOPS)
-
脚本中额外的实现细节(关键点)
-
磁盘检测:脚本优先用 storcli(若存在)判定 VD/PD 类型与 RAID 信息,回退到
smartctl或/sys/block/*/queue/rotational判断是否为 HDD/SSD,并尝试读 Rotation Rate(rpm)。 -
若无法检测 rpm:对 RAID 磁盘默认假定
10000 RPM(脚本中有默认逻辑);非 RAID 默认7200 RPM。 -
基线最终结果写入:脚本设置
BASELINE_SEQ_READ/WRITE与BASELINE_RAND_READ_IOPS/WRITE_IOPS,后续 fio 测试结果会与这些基线比较并判定 PASS/FAIL。
========================================
💡 简短结论
一块 7500 转机械盘的性能 ≠ 3 块盘做 RAID5 的性能。 RAID5 的读性能一般比单盘强(可达 2 倍左右), 但写性能反而更差(特别是随机写场景)。 所以程序的实际性能,要看它是「读多还是写多」。
⚙️ RAID5 的机制(核心差异来源)
RAID5 把数据和「奇偶校验块(Parity)」分布写在多块盘上。
举例:3 块盘做 RAID5。
| 写入操作 | 实际磁盘动作 |
| 写入一个数据块 | 读取旧数据 + 读取旧校验 + 写新数据 + 写新校验 → 共 4 次 I/O |
所以 RAID5 每次写入 要多干两件事:读旧块和写校验块,这叫 “写惩罚(Write Penalty)”。
这就是为什么 RAID5 写入性能通常比单盘还差的根本原因。
🚀 性能对比表(典型情况)
| 场景类型 | 单块 7500 RPM | 3块 RAID5 | 说明 |
| 顺序读 | 约 180 MB/s | ≈ 360 MB/s(提升 2 倍) | 可并行读取多盘 |
| 顺序写 | 约 170 MB/s | ≈ 100–130 MB/s(下降) | 因计算和写入校验 |
| 随机读 (4K) | ≈ 90 IOPS | ≈ 180 IOPS(提升 2 倍) | 读可并行 |
| 随机写 (4K) | ≈ 90 IOPS | ≈ 60 IOPS(下降约 30–40%) | 写惩罚显著 |
所以:RAID5 适合读多写少的场景,不适合写多的程序(例如数据库、监控时序库等)。
#!/bin/bash
# Filename: disk_stress_test.sh
# Version: 1.0
# Date: 2024/10/27
# Description: 磁盘性能专项压测脚本,用于服务器磁盘性能验证
# 功能:自动识别磁盘类型(机械盘/SSD/NVMe),根据磁盘类型和转速设置性能基线
# 测试项目:顺序读写、随机读写、IOPS、延迟等综合性能指标
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 全局变量
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="${SCRIPT_DIR}/disk_test_logs"
RESULT_DIR="${SCRIPT_DIR}/disk_test_results"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
LOG_FILE="${LOG_DIR}/disk_test_${TIMESTAMP}.log"
RESULT_FILE="${RESULT_DIR}/disk_result_${TIMESTAMP}.json"
# ===========================================
# 测试配置参数
# ===========================================
# 测试时长配置(秒)
SEQ_TEST_DURATION=30 # 顺序读写测试时长
RAND_TEST_DURATION=30 # 随机读写测试时长
# 测试文件大小
DEFAULT_TEST_FILE_SIZE="1G" # 默认测试文件大小
# 磁盘性能基线配置(根据磁盘类型动态设置)
# 基线值参考官方数据的80%阈值
# 机械硬盘 - 7200 RPM
HDD_7200_SEQ_READ=120 # MB/s (官方约120-150)
HDD_7200_SEQ_WRITE=100 # MB/s (官方约100-120)
HDD_7200_RAND_READ_IOPS=75 # IOPS (官方约75-100)
HDD_7200_RAND_WRITE_IOPS=70 # IOPS (官方约70-90)
HDD_7200_RAND_READ_BW=1 # MB/s
HDD_7200_RAND_WRITE_BW=1 # MB/s
# 机械硬盘 - 10000 RPM
HDD_10000_SEQ_READ=180 # MB/s (官方约180-200)
HDD_10000_SEQ_WRITE=150 # MB/s (官方约150-180)
HDD_10000_RAND_READ_IOPS=125 # IOPS (官方约125-150)
HDD_10000_RAND_WRITE_IOPS=110 # IOPS (官方约110-130)
HDD_10000_RAND_READ_BW=2 # MB/s
HDD_10000_RAND_WRITE_BW=2 # MB/s
# 机械硬盘 - 15000 RPM
HDD_15000_SEQ_READ=200 # MB/s (官方约200-250)
HDD_15000_SEQ_WRITE=180 # MB/s (官方约180-210)
HDD_15000_RAND_READ_IOPS=175 # IOPS (官方约175-200)
HDD_15000_RAND_WRITE_IOPS=160 # IOPS (官方约160-185)
HDD_15000_RAND_READ_BW=3 # MB/s
HDD_15000_RAND_WRITE_BW=3 # MB/s
# SSD(统一类型,不区分SATA/SAS)
SSD_SEQ_READ=400 # MB/s (官方约500-550)
SSD_SEQ_WRITE=360 # MB/s (官方约450-500)
SSD_RAND_READ_IOPS=60000 # IOPS (官方约75k-90k)
SSD_RAND_WRITE_IOPS=56000 # IOPS (官方约70k-85k)
SSD_RAND_READ_BW=240 # MB/s
SSD_RAND_WRITE_BW=220 # MB/s
# NVMe SSD(统一类型,不区分Gen3/Gen4)
NVME_SEQ_READ=2560 # MB/s (官方约3200-3500)
NVME_SEQ_WRITE=2240 # MB/s (官方约2800-3200)
NVME_RAND_READ_IOPS=320000 # IOPS (官方约400k-600k)
NVME_RAND_WRITE_IOPS=270000 # IOPS (官方约350k-550k)
NVME_RAND_READ_BW=1250 # MB/s
NVME_RAND_WRITE_BW=1100 # MB/s
# 当前磁盘的基线值(会根据检测结果动态设置)
BASELINE_SEQ_READ=0
BASELINE_SEQ_WRITE=0
BASELINE_RAND_READ_IOPS=0
BASELINE_RAND_WRITE_IOPS=0
BASELINE_RAND_READ_BW=0
BASELINE_RAND_WRITE_BW=0
# 测试结果存储
declare -A TEST_RESULTS
declare -A DISK_INFO
# 测试配置
TEST_ALL_DISKS=true # 默认测试所有磁盘
SKIP_SYSTEM_DISK=false # 默认包含系统盘
VERBOSE=false # 默认不显示详细输出
# 帮助信息
# @AIGenerated
function show_help() {
echo -e "${BLUE}==================================================${NC}"
echo -e "${BLUE} 磁盘性能专项压测脚本${NC}"
echo -e "${BLUE}==================================================${NC}"
echo -e ""
echo -e "用法: sh $0 [--disk_stress_test=yes|true|no|false]"
echo -e ""
echo -e "${CYAN}参数说明:${NC}"
echo -e " --disk_stress_test=VALUE 是否进行磁盘压测(不区分大小写)"
echo -e " yes/true: 进行压测(默认)"
echo -e " no/false: 跳过压测"
echo -e " --help 显示此帮助信息"
echo -e ""
echo -e "${YELLOW}示例:${NC}"
echo -e " sh $0 # 默认进行磁盘压测"
echo -e " sh $0 --disk_stress_test=yes # 进行磁盘压测"
echo -e " sh $0 --disk_stress_test=True # 进行磁盘压测(不区分大小写)"
echo -e " sh $0 --disk_stress_test=no # 跳过磁盘压测"
echo -e " sh $0 --disk_stress_test=FALSE # 跳过磁盘压测(不区分大小写)"
echo -e ""
echo -e "${YELLOW}功能说明:${NC}"
echo -e " - 自动测试所有磁盘(包括系统盘)"
echo -e " - 自动识别磁盘类型: HDD(7200/10000/15000 RPM)、SSD、NVMe"
echo -e " - 根据磁盘类型设置性能基线(官方标称性能的80%)"
echo -e " - 虚拟机环境自动跳过测试"
echo -e ""
echo -e "${YELLOW}测试内容:${NC}"
echo -e " - 顺序读写性能测试"
echo -e " - 随机读写性能测试"
echo -e " - 混合读写性能测试"
echo -e " - 延迟测试"
echo -e ""
}
# 日志函数 - 信息级别
# @AIGenerated
function log_info() {
local message="$1"
[[ ! -d "$LOG_DIR" ]] && mkdir -p "$LOG_DIR"
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $message" | tee -a "$LOG_FILE" >&2
}
# 日志函数 - 警告级别
# @AIGenerated
function log_warn() {
local message="$1"
[[ ! -d "$LOG_DIR" ]] && mkdir -p "$LOG_DIR"
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $message" | tee -a "$LOG_FILE" >&2
}
# 日志函数 - 错误级别
# @AIGenerated
function log_error() {
local message="$1"
[[ ! -d "$LOG_DIR" ]] && mkdir -p "$LOG_DIR"
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $message" | tee -a "$LOG_FILE" >&2
}
# 日志函数 - 调试级别
# @AIGenerated
function log_debug() {
local message="$1"
[[ ! -d "$LOG_DIR" ]] && mkdir -p "$LOG_DIR"
[[ "$VERBOSE" == "true" ]] && echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $message" | tee -a "$LOG_FILE" >&2
}
# 日志函数 - 成功级别
# @AIGenerated
function log_success() {
local message="$1"
[[ ! -d "$LOG_DIR" ]] && mkdir -p "$LOG_DIR"
echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $message" | tee -a "$LOG_FILE" >&2
}
# 初始化环境
# @AIGenerated
function init_environment() {
mkdir -p "$LOG_DIR" "$RESULT_DIR"
log_info "======================================================"
log_info " 磁盘性能专项压测脚本 v1.0"
log_info "======================================================"
log_info "初始化测试环境..."
# 系统信息
log_info "系统信息:"
log_info " 主机名: $(hostname)"
log_info " 操作系统: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"' || echo 'Unknown')"
log_info " 内核版本: $(uname -r)"
# 检查必要工具
check_required_tools
}
# 检查必要工具
# @AIGenerated
function check_required_tools() {
log_info "检查必要工具..."
local tools_needed=("fio" "smartctl" "lsblk" "df" "dd" "bc" "jq")
local tools_missing=()
for tool in "${tools_needed[@]}"; do
if ! command -v "$tool" >/dev/null 2>&1; then
tools_missing+=("$tool")
fi
done
if [[ ${#tools_missing[@]} -gt 0 ]]; then
log_warn "缺失工具: ${tools_missing[*]}"
install_missing_tools "${tools_missing[@]}"
else
log_info "所有必要工具已安装"
fi
}
# 安装缺失工具
# @AIGenerated
function install_missing_tools() {
local tools=("$@")
log_info "尝试安装缺失的工具..."
if command -v yum >/dev/null 2>&1; then
for tool in "${tools[@]}"; do
case "$tool" in
"fio")
sudo yum install -y fio ;;
"smartctl")
sudo yum install -y smartmontools ;;
"bc")
sudo yum install -y bc ;;
"jq")
sudo yum install -y jq ;;
"lsblk"|"df"|"dd")
log_warn "$tool 是基础系统工具,应该已经安装" ;;
esac
done
elif command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
for tool in "${tools[@]}"; do
case "$tool" in
"fio")
sudo apt-get install -y fio ;;
"smartctl")
sudo apt-get install -y smartmontools ;;
"bc")
sudo apt-get install -y bc ;;
"jq")
sudo apt-get install -y jq ;;
"lsblk"|"df"|"dd")
log_warn "$tool 是基础系统工具,应该已经安装" ;;
esac
done
else
log_error "未找到支持的包管理器,请手动安装: ${tools[*]}"
return 1
fi
}
# 列出所有可用磁盘
# @AIGenerated
function list_disks() {
echo -e "${CYAN}==================================================${NC}"
echo -e "${CYAN} 系统中的磁盘列表${NC}"
echo -e "${CYAN}==================================================${NC}"
echo ""
# 使用 lsblk 列出所有磁盘
if command -v lsblk >/dev/null 2>&1; then
lsblk -d -o NAME,SIZE,TYPE,ROTA,MODEL | grep -E "disk"
else
ls -l /dev/sd* /dev/nvme* 2>/dev/null | grep -v "partition"
fi
echo ""
echo -e "${YELLOW}说明:${NC}"
echo -e " NAME - 设备名称"
echo -e " SIZE - 磁盘大小"
echo -e " TYPE - 设备类型"
echo -e " ROTA - 是否旋转介质 (1=机械盘, 0=固态盘)"
echo -e " MODEL - 磁盘型号"
echo ""
}
# 检测磁盘类型和特性
# @AIGenerated
function detect_disk_type() {
local device="$1"
local device_path="/dev/${device}"
log_info "正在检测磁盘类型: $device"
# 初始化磁盘信息
DISK_INFO["device"]="$device"
DISK_INFO["device_path"]="$device_path"
# 检测是否为 NVMe 设备
if [[ "$device" =~ ^nvme ]]; then
DISK_INFO["interface"]="NVMe"
detect_nvme_details "$device"
else
# 检测 SATA/SAS 设备
detect_sata_sas_details "$device"
fi
# 获取磁盘容量
local size=$(lsblk -d -n -o SIZE "/dev/$device" 2>/dev/null | xargs)
DISK_INFO["size"]="$size"
# 获取磁盘型号
local model=$(lsblk -d -n -o MODEL "/dev/$device" 2>/dev/null | xargs)
[[ -z "$model" ]] && model=$(smartctl -i "$device_path" 2>/dev/null | grep "Device Model" | cut -d':' -f2 | xargs)
DISK_INFO["model"]="${model:-Unknown}"
# 显示检测结果
log_info "磁盘检测结果:"
log_info " 设备: ${DISK_INFO[device]}"
log_info " 型号: ${DISK_INFO[model]}"
log_info " 容量: ${DISK_INFO[size]}"
log_info " 接口: ${DISK_INFO[interface]}"
log_info " 类型: ${DISK_INFO[type]}"
[[ -n "${DISK_INFO[rpm]}" ]] && log_info " 转速: ${DISK_INFO[rpm]} RPM"
[[ -n "${DISK_INFO[raid_level]}" ]] && log_info " RAID: ${DISK_INFO[raid_level]} (${DISK_INFO[raid_disk_count]} 块盘)"
[[ -n "${DISK_INFO[pcie_gen]}" ]] && log_info " PCIe: ${DISK_INFO[pcie_gen]}"
# 设置性能基线
set_performance_baseline
}
# 检测 NVMe 设备详细信息
# @AIGenerated
function detect_nvme_details() {
local device="$1"
DISK_INFO["type"]="NVMe"
DISK_INFO["detailed_type"]="NVMe SSD"
DISK_INFO["interface"]="NVMe"
}
# 使用 storcli64 判断指定 OS 设备对应虚拟盘的介质类型(SSD/HDD)
# 返回值:"ssd" 或 "sas"(HDD),失败返回空
# @AIGenerated
function get_disk_type_via_storcli() {
local os_base_dev="$1" # 形如 /dev/sda 或 /dev/nvme0n1
local storcli="/opt/MegaRAID/storcli/storcli64"
if [[ ! -x "$storcli" ]] || [[ -z "$os_base_dev" ]]; then
echo ""
return 1
fi
# 遍历可能的控制器编号,找到与 OS 设备匹配的 VD
for ctrl in 0 1 2 3 4 5 6 7; do
local vd_json
vd_json=$("$storcli" /c${ctrl} /vall show all J 2>/dev/null) || true
if [[ -z "$vd_json" ]]; then
continue
fi
# 从 VD* Properties 中按 OS Drive Name 定位 VD 索引
local vd_index
vd_index=$(echo "$vd_json" | jq -r --arg dev "$os_base_dev" '
.Controllers[]?["Response Data"] | to_entries[]
| select(.key | test("^VD[0-9]+ Properties$"))
| select(.value["OS Drive Name"] == $dev)
| (.key | capture("^VD(?<n>[0-9]+) Properties$").n)
' 2>/dev/null | head -1)
if [[ -z "$vd_index" ]]; then
continue
fi
# 直接在同一份 JSON 中读取该 VD 的物理盘介质
local media_list
media_list=$(echo "$vd_json" | jq -r --arg idx "$vd_index" '
.Controllers[]?["Response Data"]
| .["PDs for VD " + $idx][]?
| (.Med // .["Media Type"]) // empty
' 2>/dev/null | tr '[:upper:]' '[:lower:]' | sort -u)
if echo "$media_list" | grep -q "ssd"; then
echo "ssd"
return 0
fi
if echo "$media_list" | grep -Eq "hdd|hard"; then
echo "sas"
return 0
fi
done
echo ""
return 1
}
# 获取 RAID 配置信息(RAID级别、磁盘数量)
# 返回格式:raid_level|disk_count 例如:RAID1|2 或 RAID5|3
# @AIGenerated
function get_raid_info() {
local os_base_dev="$1" # 形如 /dev/sda
local storcli="/opt/MegaRAID/storcli/storcli64"
if [[ ! -x "$storcli" ]] || [[ -z "$os_base_dev" ]]; then
echo ""
return 1
fi
# 遍历可能的控制器编号,找到与 OS 设备匹配的 VD
for ctrl in 0 1 2 3 4 5 6 7; do
local vd_json
vd_json=$("$storcli" /c${ctrl} /vall show all J 2>/dev/null) || true
if [[ -z "$vd_json" ]]; then
continue
fi
# 查找匹配的 VD(通过 OS Drive Name)
local vd_index
vd_index=$(echo "$vd_json" | jq -r --arg dev "$os_base_dev" '
.Controllers[]?["Response Data"] | to_entries[]
| select(.key | test("^VD[0-9]+ Properties$"))
| select(.value["OS Drive Name"] == $dev)
| (.key | capture("^VD(?<n>[0-9]+) Properties$").n)
' 2>/dev/null | head -1)
if [[ -z "$vd_index" ]]; then
continue
fi
# 从 VD Properties 中获取磁盘数量
local disk_count
disk_count=$(echo "$vd_json" | jq -r --arg idx "$vd_index" '
.Controllers[]?["Response Data"]["VD" + $idx + " Properties"]["Number of Drives Per Span"] // empty
' 2>/dev/null)
# 从 /c0/vX 数组中获取 RAID 类型
local raid_level
raid_level=$(echo "$vd_json" | jq -r '
.Controllers[]?["Response Data"] | to_entries[]
| select(.key | test("^/c[0-9]+/v[0-9]+$"))
| .value[] | select(.["DG/VD"]) | .TYPE
' 2>/dev/null | head -1)
if [[ -n "$raid_level" ]] && [[ -n "$disk_count" ]]; then
echo "${raid_level}|${disk_count}"
return 0
fi
done
echo ""
return 1
}
# 检测 SATA/SAS 设备详细信息
# @AIGenerated
function detect_sata_sas_details() {
local device="$1"
local device_path="/dev/${device}"
# 解析底层基础块设备名
local base_kname=$(lsblk -no PKNAME "$device_path" 2>/dev/null)
if [[ -z "$base_kname" ]]; then
base_kname=$(lsblk -no KNAME "$device_path" 2>/dev/null)
fi
# 检查是否为 RAID 磁盘
local is_raid=false
if command -v smartctl >/dev/null 2>&1; then
local smart_info=$(smartctl -i "$device_path" 2>/dev/null)
if echo "$smart_info" | grep -qi "PERC\|MegaRAID\|LSI\|RAID"; then
is_raid=true
fi
fi
# 优先通过 storcli 判断介质;失败则回退 rotational
local detected_media=$(get_disk_type_via_storcli "/dev/$base_kname")
if [[ -z "$detected_media" ]]; then
local disk_base=$base_kname
if [[ -z "$disk_base" ]]; then
disk_base=$(echo "$device_path" | sed 's|/dev/||' | sed 's/p[0-9]\+$//' | sed 's/[0-9]\+$//')
fi
# 使用 rotational 判断
local disk_type_val=$(cat "/sys/block/$disk_base/queue/rotational" 2>/dev/null || echo 1)
if [[ "$disk_type_val" -eq 0 ]]; then
detected_media="ssd"
else
detected_media="sas"
fi
fi
# 检测接口类型 (SATA/SAS)
local interface="SATA"
if command -v smartctl >/dev/null 2>&1; then
local smart_info=$(smartctl -i "$device_path" 2>/dev/null)
if echo "$smart_info" | grep -qi "sas\|scsi"; then
interface="SAS"
fi
fi
DISK_INFO["interface"]="$interface"
# 判断是机械盘还是固态盘
if [[ "$detected_media" == "ssd" ]]; then
# 固态硬盘 - 进一步判断是否为 NVMe
local disk_base=$base_kname
if [[ -z "$disk_base" ]]; then
disk_base=$(echo "$device_path" | sed 's|/dev/||' | sed 's/p[0-9]\+$//' | sed 's/[0-9]\+$//')
fi
# 如果设备名以 nvme 开头,则判定为 NVMe PCIe SSD
if [[ "$disk_base" =~ ^nvme ]]; then
DISK_INFO["type"]="NVMe"
DISK_INFO["detailed_type"]="NVMe SSD"
DISK_INFO["interface"]="NVMe"
else
# 普通 SATA/SAS SSD
DISK_INFO["type"]="SSD"
DISK_INFO["detailed_type"]="SSD"
fi
else
# 机械硬盘
DISK_INFO["type"]="HDD"
# 检测转速
local rpm=""
if command -v smartctl >/dev/null 2>&1; then
local smart_info=$(smartctl -i "$device_path" 2>/dev/null)
rpm=$(echo "$smart_info" | grep -i "Rotation Rate" | grep -o '[0-9]\+' | head -1)
# 如果是 RAID 磁盘且无法获取转速,尝试使用 megaraid 接口
if [[ -z "$rpm" ]] && [[ "$is_raid" == "true" ]]; then
# 尝试 DG 0-7 (Disk Group)
for dg in {0..7}; do
local raid_info=$(smartctl -a -d megaraid,${dg} "$device_path" 2>/dev/null)
if [[ $? -eq 0 ]]; then
rpm=$(echo "$raid_info" | grep -i "Rotation Rate" | grep -o '[0-9]\+' | head -1)
if [[ -n "$rpm" ]]; then
break
fi
fi
done
fi
if [[ -z "$rpm" ]]; then
# 尝试从型号名称推断
local model=$(echo "$smart_info" | grep -E "Device Model|Product" | cut -d':' -f2 | xargs)
if echo "$model" | grep -qi "15k\|15000"; then
rpm="15000"
elif echo "$model" | grep -qi "10k\|10000"; then
rpm="10000"
else
# RAID 磁盘默认使用 10000 RPM
if [[ "$is_raid" == "true" ]]; then
rpm="10000"
else
rpm="7200"
fi
fi
fi
else
rpm="7200" # 默认7200
fi
DISK_INFO["rpm"]="$rpm"
DISK_INFO["detailed_type"]="HDD ${rpm}RPM"
# 如果是 RAID 磁盘,获取 RAID 配置信息
if [[ "$is_raid" == "true" ]]; then
local raid_info=$(get_raid_info "$device_path")
if [[ -n "$raid_info" ]]; then
local raid_level=$(echo "$raid_info" | cut -d'|' -f1)
local raid_disk_count=$(echo "$raid_info" | cut -d'|' -f2)
DISK_INFO["raid_level"]="$raid_level"
DISK_INFO["raid_disk_count"]="$raid_disk_count"
DISK_INFO["detailed_type"]="HDD ${rpm}RPM ${raid_level} (${raid_disk_count}盘)"
fi
fi
fi
}
# 设置性能基线
# @AIGenerated
function set_performance_baseline() {
local disk_type="${DISK_INFO[type]}"
local rpm="${DISK_INFO[rpm]}"
log_info "设置性能基线: ${DISK_INFO[detailed_type]}"
# 第一步:获取单盘基线值
local single_seq_read=0
local single_seq_write=0
local single_rand_read_iops=0
local single_rand_write_iops=0
if [[ "$disk_type" == "HDD" ]]; then
# 根据转速获取单盘基线
case "$rpm" in
"7200")
single_seq_read=$HDD_7200_SEQ_READ
single_seq_write=$HDD_7200_SEQ_WRITE
single_rand_read_iops=$HDD_7200_RAND_READ_IOPS
single_rand_write_iops=$HDD_7200_RAND_WRITE_IOPS
;;
"10000")
single_seq_read=$HDD_10000_SEQ_READ
single_seq_write=$HDD_10000_SEQ_WRITE
single_rand_read_iops=$HDD_10000_RAND_READ_IOPS
single_rand_write_iops=$HDD_10000_RAND_WRITE_IOPS
;;
"15000")
single_seq_read=$HDD_15000_SEQ_READ
single_seq_write=$HDD_15000_SEQ_WRITE
single_rand_read_iops=$HDD_15000_RAND_READ_IOPS
single_rand_write_iops=$HDD_15000_RAND_WRITE_IOPS
;;
*)
single_seq_read=100
single_seq_write=80
single_rand_read_iops=60
single_rand_write_iops=55
;;
esac
# 第二步:如果是 RAID,应用 RAID 系数
if [[ -n "${DISK_INFO[raid_level]}" ]] && [[ -n "${DISK_INFO[raid_disk_count]}" ]]; then
local raid_level="${DISK_INFO[raid_level]}"
local N=${DISK_INFO[raid_disk_count]}
log_info "检测到 RAID 配置: $raid_level ($N 块盘)"
case "$raid_level" in
"RAID0")
BASELINE_SEQ_READ=$(echo "scale=0; $single_seq_read * $N * 0.90 / 1" | bc)
BASELINE_SEQ_WRITE=$(echo "scale=0; $single_seq_write * $N * 0.85 / 1" | bc)
BASELINE_RAND_READ_IOPS=$(echo "scale=0; $single_rand_read_iops * $N * 0.90 / 1" | bc)
BASELINE_RAND_WRITE_IOPS=$(echo "scale=0; $single_rand_write_iops * $N * 0.85 / 1" | bc)
;;
"RAID1")
BASELINE_SEQ_READ=$(echo "scale=0; $single_seq_read * 2 * 0.90 / 1" | bc)
BASELINE_SEQ_WRITE=$(echo "scale=0; $single_seq_write * 1.0 / 1" | bc)
BASELINE_RAND_READ_IOPS=$(echo "scale=0; $single_rand_read_iops * 2 * 0.75 / 1" | bc)
BASELINE_RAND_WRITE_IOPS=$(echo "scale=0; $single_rand_write_iops * 1.0 / 1" | bc)
;;
"RAID5")
BASELINE_SEQ_READ=$(echo "scale=0; $single_seq_read * ($N - 1) * 0.85 / 1" | bc)
BASELINE_SEQ_WRITE=$(echo "scale=0; $single_seq_write * ($N - 1) * 0.85 / 1" | bc)
BASELINE_RAND_READ_IOPS=$(echo "scale=0; $single_rand_read_iops * ($N - 1) * 0.80 / 1" | bc)
BASELINE_RAND_WRITE_IOPS=$(echo "scale=0; $single_rand_write_iops * ($N - 1) * 0.20 / 1" | bc)
;;
"RAID10")
BASELINE_SEQ_READ=$(echo "scale=0; $single_seq_read * ($N / 2) * 0.85 / 1" | bc)
BASELINE_SEQ_WRITE=$(echo "scale=0; $single_seq_write * ($N / 2) * 0.45 / 1" | bc)
BASELINE_RAND_READ_IOPS=$(echo "scale=0; $single_rand_read_iops * ($N / 2) * 0.90 / 1" | bc)
BASELINE_RAND_WRITE_IOPS=$(echo "scale=0; $single_rand_write_iops * ($N / 2) * 0.50 / 1" | bc)
;;
*)
log_warn "未知 RAID 级别: $raid_level,使用单盘基线值"
BASELINE_SEQ_READ=$single_seq_read
BASELINE_SEQ_WRITE=$single_seq_write
BASELINE_RAND_READ_IOPS=$single_rand_read_iops
BASELINE_RAND_WRITE_IOPS=$single_rand_write_iops
;;
esac
else
# 非 RAID,使用单盘基线值
BASELINE_SEQ_READ=$single_seq_read
BASELINE_SEQ_WRITE=$single_seq_write
BASELINE_RAND_READ_IOPS=$single_rand_read_iops
BASELINE_RAND_WRITE_IOPS=$single_rand_write_iops
fi
elif [[ "$disk_type" == "SSD" ]]; then
BASELINE_SEQ_READ=$SSD_SEQ_READ
BASELINE_SEQ_WRITE=$SSD_SEQ_WRITE
BASELINE_RAND_READ_IOPS=$SSD_RAND_READ_IOPS
BASELINE_RAND_WRITE_IOPS=$SSD_RAND_WRITE_IOPS
elif [[ "$disk_type" == "NVMe" ]]; then
BASELINE_SEQ_READ=$NVME_SEQ_READ
BASELINE_SEQ_WRITE=$NVME_SEQ_WRITE
BASELINE_RAND_READ_IOPS=$NVME_RAND_READ_IOPS
BASELINE_RAND_WRITE_IOPS=$NVME_RAND_WRITE_IOPS
else
log_warn "未知磁盘类型,使用默认基线值"
BASELINE_SEQ_READ=100
BASELINE_SEQ_WRITE=80
BASELINE_RAND_READ_IOPS=100
BASELINE_RAND_WRITE_IOPS=80
fi
log_info "性能基线值:"
log_info " 顺序读: ${BASELINE_SEQ_READ} MB/s"
log_info " 顺序写: ${BASELINE_SEQ_WRITE} MB/s"
log_info " 随机读IOPS: ${BASELINE_RAND_READ_IOPS}"
log_info " 随机写IOPS: ${BASELINE_RAND_WRITE_IOPS}"
}
# 顺序读写测试
# @AIGenerated
function test_sequential_performance() {
local test_path="$1"
local test_file="${test_path}/fio_seq_test_${TIMESTAMP}"
log_info "======================================================"
log_info "开始顺序读写性能测试..."
log_info "======================================================"
local test_size="${TEST_FILE_SIZE:-$DEFAULT_TEST_FILE_SIZE}"
local duration="${SEQ_TEST_DURATION}"
# 顺序读测试
log_info "执行顺序读测试 (块大小: 1M, 时长: ${duration}s)..."
local seq_read_result=$(fio --name=seq_read \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=1M \
--iodepth=32 \
--rw=read \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting \
--numjobs=1 2>&1)
# 提取带宽和IOPS(注意:fio输出格式是 "read: IOPS=660, BW=661MiB/s")
local seq_read_bw=$(echo "$seq_read_result" | grep "read:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local seq_read_iops=$(echo "$seq_read_result" | grep "read:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
# 转换带宽单位到 MB/s
seq_read_bw=$(convert_to_mbps "$seq_read_bw")
# 如果还是没有提取到 IOPS,设置为 N/A
[[ -z "$seq_read_iops" ]] && seq_read_iops="N/A"
TEST_RESULTS["seq_read_bw"]="$seq_read_bw"
TEST_RESULTS["seq_read_iops"]="$seq_read_iops"
log_info "顺序读测试结果: ${seq_read_bw} MB/s, IOPS: ${seq_read_iops}"
# 清除缓存
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
sleep 2
# 顺序写测试
log_info "执行顺序写测试 (块大小: 1M, 时长: ${duration}s)..."
local seq_write_result=$(fio --name=seq_write \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=1M \
--iodepth=32 \
--rw=write \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting \
--numjobs=1 2>&1)
# 提取带宽和IOPS(注意:fio输出格式是 "write: IOPS=xxx, BW=xxxMiB/s")
local seq_write_bw=$(echo "$seq_write_result" | grep "write:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local seq_write_iops=$(echo "$seq_write_result" | grep "write:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
# 转换带宽单位到 MB/s
seq_write_bw=$(convert_to_mbps "$seq_write_bw")
# 如果还是没有提取到 IOPS,设置为 N/A
[[ -z "$seq_write_iops" ]] && seq_write_iops="N/A"
TEST_RESULTS["seq_write_bw"]="$seq_write_bw"
TEST_RESULTS["seq_write_iops"]="$seq_write_iops"
log_info "顺序写测试结果: ${seq_write_bw} MB/s, IOPS: ${seq_write_iops}"
# 评估顺序读写性能
evaluate_performance "seq_read" "$seq_read_bw" "$BASELINE_SEQ_READ"
evaluate_performance "seq_write" "$seq_write_bw" "$BASELINE_SEQ_WRITE"
# 清理测试文件
rm -f "$test_file"
log_info "顺序读写测试完成"
}
# 随机读写测试
# @AIGenerated
function test_random_performance() {
local test_path="$1"
local test_file="${test_path}/fio_rand_test_${TIMESTAMP}"
log_info "======================================================"
log_info "开始随机读写性能测试..."
log_info "======================================================"
local test_size="${TEST_FILE_SIZE:-$DEFAULT_TEST_FILE_SIZE}"
local duration="${RAND_TEST_DURATION}"
# 随机读测试 (4K)
log_info "执行随机读测试 (块大小: 4K, 时长: ${duration}s)..."
local rand_read_result=$(fio --name=rand_read \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=64 \
--rw=randread \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting \
--numjobs=4 2>&1)
# 提取带宽和IOPS(注意:fio输出格式是 "read: IOPS=10.5k, BW=41.0MiB/s")
local rand_read_bw=$(echo "$rand_read_result" | grep "read:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local rand_read_iops=$(echo "$rand_read_result" | grep "read:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
# 转换单位
rand_read_bw=$(convert_to_mbps "$rand_read_bw")
rand_read_iops=$(convert_iops "$rand_read_iops")
# 如果转换后还是0或空,设置为N/A
[[ -z "$rand_read_bw" ]] && rand_read_bw="0"
if [[ -z "$rand_read_iops" ]] || [[ "$rand_read_iops" == "0" ]]; then
rand_read_iops="N/A"
fi
TEST_RESULTS["rand_read_bw"]="$rand_read_bw"
TEST_RESULTS["rand_read_iops"]="$rand_read_iops"
log_info "随机读测试结果: ${rand_read_bw} MB/s, IOPS: ${rand_read_iops}"
# 清除缓存
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
sleep 2
# 随机写测试 (4K)
log_info "执行随机写测试 (块大小: 4K, 时长: ${duration}s)..."
local rand_write_result=$(fio --name=rand_write \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=64 \
--rw=randwrite \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting \
--numjobs=4 2>&1)
# 提取带宽和IOPS(注意:fio输出格式是 "write: IOPS=xxx, BW=xxxKiB/s")
local rand_write_bw=$(echo "$rand_write_result" | grep "write:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local rand_write_iops=$(echo "$rand_write_result" | grep "write:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
# 转换单位
rand_write_bw=$(convert_to_mbps "$rand_write_bw")
rand_write_iops=$(convert_iops "$rand_write_iops")
# 如果转换后还是0或空,设置为N/A
[[ -z "$rand_write_bw" ]] && rand_write_bw="0"
if [[ -z "$rand_write_iops" ]] || [[ "$rand_write_iops" == "0" ]]; then
rand_write_iops="N/A"
fi
TEST_RESULTS["rand_write_bw"]="$rand_write_bw"
TEST_RESULTS["rand_write_iops"]="$rand_write_iops"
log_info "随机写测试结果: ${rand_write_bw} MB/s, IOPS: ${rand_write_iops}"
# 评估随机读写性能
evaluate_performance "rand_read_iops" "$rand_read_iops" "$BASELINE_RAND_READ_IOPS"
evaluate_performance "rand_write_iops" "$rand_write_iops" "$BASELINE_RAND_WRITE_IOPS"
# 清理测试文件
rm -f "$test_file"
log_info "随机读写测试完成"
}
# 混合读写测试
# @AIGenerated
function test_mixed_performance() {
local test_path="$1"
local test_file="${test_path}/fio_mixed_test_${TIMESTAMP}"
log_info "======================================================"
log_info "开始混合读写性能测试..."
log_info "======================================================"
local test_size="${TEST_FILE_SIZE:-$DEFAULT_TEST_FILE_SIZE}"
local duration="120"
# 70% 读 30% 写
log_info "执行混合读写测试 (70%读/30%写, 块大小: 4K, 时长: ${duration}s)..."
local mixed_result=$(fio --name=mixed_rw \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=64 \
--rw=randrw \
--rwmixread=70 \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting \
--numjobs=4 2>&1)
# 提取带宽和IOPS(注意:混合测试有两行 "read:" 和 "write:")
local mixed_read_bw=$(echo "$mixed_result" | grep "read:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local mixed_read_iops=$(echo "$mixed_result" | grep "read:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
local mixed_write_bw=$(echo "$mixed_result" | grep "write:" | grep -oP 'BW=\K[0-9.]+[A-Za-z/]+' | head -1)
local mixed_write_iops=$(echo "$mixed_result" | grep "write:" | grep -oP 'IOPS=\K[0-9.]+k?' | head -1)
# 转换单位
mixed_read_bw=$(convert_to_mbps "$mixed_read_bw")
mixed_read_iops=$(convert_iops "$mixed_read_iops")
mixed_write_bw=$(convert_to_mbps "$mixed_write_bw")
mixed_write_iops=$(convert_iops "$mixed_write_iops")
# 如果没有提取到值,设置为 N/A
[[ -z "$mixed_read_bw" ]] && mixed_read_bw="N/A"
[[ -z "$mixed_read_iops" ]] && mixed_read_iops="N/A"
[[ -z "$mixed_write_bw" ]] && mixed_write_bw="N/A"
[[ -z "$mixed_write_iops" ]] && mixed_write_iops="N/A"
TEST_RESULTS["mixed_read_bw"]="$mixed_read_bw"
TEST_RESULTS["mixed_read_iops"]="$mixed_read_iops"
TEST_RESULTS["mixed_write_bw"]="$mixed_write_bw"
TEST_RESULTS["mixed_write_iops"]="$mixed_write_iops"
log_info "混合读写测试结果:"
log_info " 读: ${mixed_read_bw} MB/s, IOPS: ${mixed_read_iops}"
log_info " 写: ${mixed_write_bw} MB/s, IOPS: ${mixed_write_iops}"
# 清理测试文件
rm -f "$test_file"
log_info "混合读写测试完成"
}
# 延迟测试
# @AIGenerated
function test_latency() {
local test_path="$1"
local test_file="${test_path}/fio_latency_test_${TIMESTAMP}"
log_info "======================================================"
log_info "开始延迟测试..."
log_info "======================================================"
local test_size="512M"
local duration="60"
# 测试随机读延迟
log_info "执行随机读延迟测试..."
local latency_result=$(fio --name=latency_test \
--filename="$test_file" \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=1 \
--rw=randread \
--size="$test_size" \
--runtime="$duration" \
--time_based \
--group_reporting 2>&1)
local avg_lat=$(echo "$latency_result" | grep "lat (usec):" -A 1 | grep "avg=" | grep -oP 'avg=\K[0-9.]+' | head -1)
local lat_99=$(echo "$latency_result" | grep "99.00th" | grep -oP '\[\s*[0-9]+' | grep -oP '[0-9]+' | head -1)
if [[ -z "$avg_lat" ]]; then
avg_lat=$(echo "$latency_result" | grep "clat (usec):" -A 1 | grep "avg=" | grep -oP 'avg=\K[0-9.]+' | head -1)
fi
TEST_RESULTS["avg_latency_usec"]="$avg_lat"
TEST_RESULTS["lat_99th_usec"]="$lat_99"
log_info "延迟测试结果:"
log_info " 平均延迟: ${avg_lat} μs"
log_info " 99th 百分位延迟: ${lat_99} μs"
# 清理测试文件
rm -f "$test_file"
log_info "延迟测试完成"
}
# 单位转换:转换为 MB/s
# @AIGenerated
function convert_to_mbps() {
local value="$1"
if [[ -z "$value" ]]; then
echo "0"
return
fi
# 提取数字和单位
local num=$(echo "$value" | grep -oP '^[0-9.]+')
local unit=$(echo "$value" | grep -oP '[A-Za-z/]+$')
case "$unit" in
"KiB/s"|"KB/s"|"kB/s")
echo "scale=2; $num / 1024" | bc 2>/dev/null || echo "0"
;;
"MiB/s"|"MB/s"|"mB/s")
echo "$num"
;;
"GiB/s"|"GB/s"|"gB/s")
echo "scale=2; $num * 1024" | bc 2>/dev/null || echo "0"
;;
*)
echo "$num"
;;
esac
}
# 单位转换:转换 IOPS
# @AIGenerated
function convert_iops() {
local value="$1"
if [[ -z "$value" ]] || [[ "$value" == "0" ]]; then
echo "0"
return
fi
# 提取数字部分
local num=$(echo "$value" | grep -oP '^[0-9.]+' | head -1)
if [[ -z "$num" ]]; then
echo "0"
return
fi
# 检查是否有 k 单位
if [[ "$value" =~ k$|K$ ]]; then
# 乘以 1000
echo "scale=0; $num * 1000 / 1" | bc 2>/dev/null || echo "$num"
else
# 直接返回数字(可能已经是完整的IOPS值)
echo "scale=0; $num / 1" | bc 2>/dev/null || echo "$num"
fi
}
# 性能评估
# @AIGenerated
function evaluate_performance() {
local test_name="$1"
local actual_value="$2"
local baseline_value="$3"
# 提取数字部分
actual_value=$(echo "$actual_value" | grep -oP '^[0-9.]+' | head -1)
if [[ -z "$actual_value" ]] || [[ "$actual_value" == "0" ]]; then
TEST_RESULTS["${test_name}_status"]="ERROR"
log_error " ${test_name}: 无法获取测试结果"
return
fi
# 比较实际值和基线值
local comparison=$(echo "$actual_value >= $baseline_value" | bc 2>/dev/null || echo "0")
if [[ "$comparison" -eq 1 ]]; then
TEST_RESULTS["${test_name}_status"]="PASS"
log_success " ${test_name}: ${GREEN}通过${NC} (实际: $actual_value, 基线: $baseline_value)"
else
TEST_RESULTS["${test_name}_status"]="FAIL"
log_error " ${test_name}: ${RED}未达标${NC} (实际: $actual_value, 基线: $baseline_value)"
fi
}
# 生成测试报告
# @AIGenerated
function generate_report() {
log_info "======================================================"
log_info "生成测试报告..."
log_info "======================================================"
mkdir -p "$RESULT_DIR"
# 生成 JSON 报告
local json_report="${RESULT_DIR}/disk_result_${TIMESTAMP}.json"
{
echo "{"
echo " \"timestamp\": \"$TIMESTAMP\","
echo " \"hostname\": \"$(hostname)\","
echo " \"disk_info\": {"
echo " \"device\": \"${DISK_INFO[device]}\","
echo " \"model\": \"${DISK_INFO[model]}\","
echo " \"size\": \"${DISK_INFO[size]}\","
echo " \"type\": \"${DISK_INFO[type]}\","
echo " \"detailed_type\": \"${DISK_INFO[detailed_type]}\","
echo " \"interface\": \"${DISK_INFO[interface]}\""
[[ -n "${DISK_INFO[rpm]}" ]] && echo " ,\"rpm\": \"${DISK_INFO[rpm]}\""
[[ -n "${DISK_INFO[pcie_gen]}" ]] && echo " ,\"pcie_gen\": \"${DISK_INFO[pcie_gen]}\""
echo " },"
echo " \"baseline\": {"
echo " \"seq_read\": $BASELINE_SEQ_READ,"
echo " \"seq_write\": $BASELINE_SEQ_WRITE,"
echo " \"rand_read_iops\": $BASELINE_RAND_READ_IOPS,"
echo " \"rand_write_iops\": $BASELINE_RAND_WRITE_IOPS"
echo " },"
echo " \"test_results\": {"
local first=true
for key in "${!TEST_RESULTS[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
echo -n " \"$key\": \"${TEST_RESULTS[$key]}\""
done
echo ""
echo " }"
echo "}"
} > "$json_report"
# 生成文本报告
local text_report="${RESULT_DIR}/disk_report_${TIMESTAMP}.txt"
{
echo "======================================================"
echo " 磁盘性能压测报告"
echo "======================================================"
echo ""
echo "测试时间: $(date)"
echo "主机名: $(hostname)"
echo ""
echo "【磁盘信息】"
echo " 设备: ${DISK_INFO[device]}"
echo " 型号: ${DISK_INFO[model]}"
echo " 容量: ${DISK_INFO[size]}"
echo " 接口: ${DISK_INFO[interface]}"
echo " 类型: ${DISK_INFO[detailed_type]}"
[[ -n "${DISK_INFO[rpm]}" ]] && echo " 转速: ${DISK_INFO[rpm]} RPM"
[[ -n "${DISK_INFO[pcie_gen]}" ]] && echo " PCIe: ${DISK_INFO[pcie_gen]}"
echo ""
echo "【性能基线】"
echo " 顺序读: ${BASELINE_SEQ_READ} MB/s"
echo " 顺序写: ${BASELINE_SEQ_WRITE} MB/s"
echo " 随机读IOPS: ${BASELINE_RAND_READ_IOPS}"
echo " 随机写IOPS: ${BASELINE_RAND_WRITE_IOPS}"
echo ""
echo "【顺序读写测试】"
echo " 顺序读: ${TEST_RESULTS[seq_read_bw]} MB/s - ${TEST_RESULTS[seq_read_status]}"
echo " 顺序写: ${TEST_RESULTS[seq_write_bw]} MB/s - ${TEST_RESULTS[seq_write_status]}"
echo ""
echo "【随机读写测试】"
echo " 随机读: ${TEST_RESULTS[rand_read_bw]} MB/s, IOPS: ${TEST_RESULTS[rand_read_iops]} - ${TEST_RESULTS[rand_read_iops_status]}"
echo " 随机写: ${TEST_RESULTS[rand_write_bw]} MB/s, IOPS: ${TEST_RESULTS[rand_write_iops]} - ${TEST_RESULTS[rand_write_iops_status]}"
echo ""
if [[ -n "${TEST_RESULTS[mixed_read_bw]}" ]]; then
echo "【混合读写测试】"
echo " 读: ${TEST_RESULTS[mixed_read_bw]} MB/s, IOPS: ${TEST_RESULTS[mixed_read_iops]}"
echo " 写: ${TEST_RESULTS[mixed_write_bw]} MB/s, IOPS: ${TEST_RESULTS[mixed_write_iops]}"
echo ""
fi
if [[ -n "${TEST_RESULTS[avg_latency_usec]}" ]]; then
echo "【延迟测试】"
echo " 平均延迟: ${TEST_RESULTS[avg_latency_usec]} μs"
echo " 99th延迟: ${TEST_RESULTS[lat_99th_usec]} μs"
echo ""
fi
echo "【综合评估】"
local overall_status="PASS"
local failed_tests=()
for key in "${!TEST_RESULTS[@]}"; do
if [[ "$key" == *"_status" ]] && [[ "${TEST_RESULTS[$key]}" == "FAIL" ]]; then
overall_status="FAIL"
failed_tests+=("$key")
fi
done
if [[ "$overall_status" == "PASS" ]]; then
echo "整体评估: ${GREEN}通过${NC} - 磁盘性能符合要求"
else
echo "整体评估: ${RED}未通过${NC} - 磁盘性能存在问题"
echo ""
echo "未达标项目:"
for test in "${failed_tests[@]}"; do
echo " - $test: ${TEST_RESULTS[$test]}"
done
fi
echo ""
echo "详细日志: $LOG_FILE"
echo "JSON结果: $json_report"
echo "======================================================"
} > "$text_report"
# 显示报告
cat "$text_report"
log_info "报告已生成:"
log_info " 文本报告: $text_report"
log_info " JSON报告: $json_report"
log_info " 详细日志: $LOG_FILE"
}
# 获取系统盘设备名
# @AIGenerated
function get_system_disk() {
# 获取根分区所在的磁盘
local root_device=$(df / | tail -1 | awk '{print $1}')
# 去除分区号,只保留磁盘名
local system_disk=$(echo "$root_device" | sed 's/[0-9]*$//' | sed 's|/dev/||' | sed 's/p$//')
echo "$system_disk"
}
# 获取所有可测试的磁盘
# @AIGenerated
function get_all_testable_disks() {
local system_disk=$(get_system_disk)
local disks=()
# 获取所有块设备(只要磁盘,不要分区)
while IFS= read -r disk; do
# 跳过回环设备、rom设备等
if [[ "$disk" =~ ^loop ]] || [[ "$disk" =~ ^sr ]] || [[ "$disk" =~ ^ram ]]; then
continue
fi
# 如果设置了跳过系统盘,则跳过
if [[ "$SKIP_SYSTEM_DISK" == "true" ]] && [[ "$disk" == "$system_disk" ]]; then
log_info "跳过系统盘: $disk"
continue
fi
disks+=("$disk")
done < <(lsblk -d -n -o NAME | grep -E "^sd|^nvme|^vd")
echo "${disks[@]}"
}
# 查找磁盘的测试路径(优先选择空间最大的分区)
# @AIGenerated
function find_disk_test_path() {
local device="$1"
local test_path=""
local max_space=0
# 获取该磁盘的所有分区(包括磁盘本身)
while IFS= read -r dev; do
# 跳过空行
[[ -z "$dev" ]] && continue
# 获取该设备的挂载点(只取第一行,避免多行输出)
local mount_point=$(lsblk -n -o MOUNTPOINT "/dev/$dev" 2>/dev/null | head -1)
# 跳过空挂载点和 SWAP
if [[ -z "$mount_point" ]] || [[ "$mount_point" == "[SWAP]" ]]; then
continue
fi
# 获取该挂载点的可用空间(KB)
local avail_space=$(df "$mount_point" 2>/dev/null | tail -1 | awk '{print $4}')
# 如果空间更大,更新测试路径
if [[ -n "$avail_space" ]] && [[ "$avail_space" -gt "$max_space" ]]; then
max_space=$avail_space
test_path="$mount_point"
fi
done < <(lsblk -n -o NAME "/dev/$device" 2>/dev/null)
# 返回找到的路径(可能为空)
echo "$test_path"
}
# 执行单个磁盘测试
# @AIGenerated
function run_single_disk_test() {
local device="$1"
local test_path="$2"
log_info "======================================================"
log_info "开始测试磁盘: $device"
log_info "======================================================"
# 如果没有指定测试路径,自动查找
if [[ -z "$test_path" ]]; then
# 检查是否为系统盘
local system_disk=$(get_system_disk)
if [[ "$device" == "$system_disk" ]]; then
# 系统盘直接使用 /tmp
test_path="/tmp"
log_info "系统盘 $device,使用 /tmp 进行测试"
else
# 非系统盘,查找空间最大的分区
test_path=$(find_disk_test_path "$device")
# 如果非系统盘没有找到挂载点,跳过该磁盘
if [[ -z "$test_path" ]]; then
log_warn "磁盘 $device 没有找到可用的挂载点,跳过测试"
log_warn "请确保磁盘已分区并挂载后再进行测试"
return 2 # 返回特殊错误码,表示跳过
fi
fi
fi
log_info "测试路径: $test_path"
# 检查测试路径
if [[ ! -d "$test_path" ]] || [[ ! -w "$test_path" ]]; then
log_error "测试路径不存在或不可写: $test_path"
return 1
fi
# 检查可用空间(使用 MB 为单位避免整数除法精度问题)
local available_space_kb=$(df "$test_path" | tail -1 | awk '{print $4}')
local available_space_mb=$((available_space_kb / 1024))
local available_space_gb=$(echo "scale=2; $available_space_mb / 1024" | bc 2>/dev/null || echo "0")
log_info "可用空间: ${available_space_gb}GB (${available_space_mb}MB)"
# 检查是否至少有 5GB (5120MB)
if [[ "$available_space_mb" -lt 5120 ]]; then
log_error "磁盘可用空间不足5GB: ${available_space_gb}GB"
return 1
fi
# 清空上次的测试结果
declare -gA TEST_RESULTS
declare -gA DISK_INFO
# 检测磁盘类型
detect_disk_type "$device"
# 执行各项测试(全面测试)
test_sequential_performance "$test_path"
test_random_performance "$test_path"
test_mixed_performance "$test_path"
test_latency "$test_path"
# 生成报告
generate_report
# 判断测试结果
local exit_code=0
for key in "${!TEST_RESULTS[@]}"; do
if [[ "$key" == *"_status" ]] && [[ "${TEST_RESULTS[$key]}" == "FAIL" ]]; then
exit_code=1
break
fi
done
return $exit_code
}
# 执行所有磁盘测试
# @AIGenerated
function run_all_disks_test() {
log_info "======================================================"
log_info "开始测试所有可用磁盘"
log_info "======================================================"
local disks=($(get_all_testable_disks))
if [[ ${#disks[@]} -eq 0 ]]; then
log_error "没有找到可测试的磁盘"
return 1
fi
log_info "找到 ${#disks[@]} 个可测试磁盘: ${disks[*]}"
echo ""
local failed_disks=()
local passed_disks=()
local skipped_disks=()
local total_disks=${#disks[@]}
local current=0
for disk in "${disks[@]}"; do
((current++))
echo ""
log_info "======================================================"
log_info "进度: [$current/$total_disks] 测试磁盘: $disk"
log_info "======================================================"
run_single_disk_test "$disk"
local result=$?
if [[ $result -eq 0 ]]; then
passed_disks+=("$disk")
log_success "磁盘 $disk 测试通过"
elif [[ $result -eq 2 ]]; then
skipped_disks+=("$disk")
log_warn "磁盘 $disk 已跳过"
else
failed_disks+=("$disk")
log_error "磁盘 $disk 测试失败"
fi
echo ""
sleep 2
done
# 生成汇总报告
echo ""
log_info "======================================================"
log_info " 所有磁盘测试完成 - 汇总报告"
log_info "======================================================"
log_info "总计磁盘数: $total_disks"
log_info "通过: ${#passed_disks[@]}"
log_info "失败: ${#failed_disks[@]}"
log_info "跳过: ${#skipped_disks[@]}"
echo ""
if [[ ${#passed_disks[@]} -gt 0 ]]; then
log_success "通过的磁盘:"
for disk in "${passed_disks[@]}"; do
log_success " ✓ $disk"
done
echo ""
fi
if [[ ${#skipped_disks[@]} -gt 0 ]]; then
log_warn "跳过的磁盘(未挂载):"
for disk in "${skipped_disks[@]}"; do
log_warn " ⊘ $disk"
done
echo ""
fi
if [[ ${#failed_disks[@]} -gt 0 ]]; then
log_error "失败的磁盘:"
for disk in "${failed_disks[@]}"; do
log_error " ✗ $disk"
done
echo ""
return 1
fi
return 0
}
# 主函数
# @AIGenerated
function main() {
local disk_stress_test="yes"
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
--disk_stress_test=*)
disk_stress_test="${1#*=}"
shift
;;
--help)
show_help
exit 0
;;
*)
log_error "未知参数: $1"
show_help
exit 1
;;
esac
done
# 将参数转换为小写,以便不区分大小写
disk_stress_test_lower=$(echo "$disk_stress_test" | tr '[:upper:]' '[:lower:]')
# 判断参数值
if [[ "$disk_stress_test_lower" == "no" ]] || [[ "$disk_stress_test_lower" == "false" ]]; then
log_info "disk_stress_test=${disk_stress_test},跳过磁盘压测"
exit 0
elif [[ "$disk_stress_test_lower" == "yes" ]] || [[ "$disk_stress_test_lower" == "true" ]]; then
log_info "disk_stress_test=${disk_stress_test},开始磁盘压测"
else
log_error "无效的参数值: disk_stress_test=${disk_stress_test}"
log_error "有效值为: yes, true, no, false (不区分大小写)"
show_help
exit 1
fi
# 检查机器类型,虚拟机不进行硬件压测
if command -v systemd-detect-virt >/dev/null 2>&1; then
local virt_type=$(systemd-detect-virt)
if [[ "$virt_type" != "none" ]]; then
log_info "检测到虚拟机环境: $virt_type"
log_info "虚拟机不进行硬件压测,跳过测试"
exit 0
fi
fi
# 检查root权限
if [[ $EUID -ne 0 ]]; then
log_warn "建议使用root用户运行此脚本以获得更准确的测试结果"
fi
# 初始化环境
init_environment
# 执行所有磁盘测试(包括系统盘)
log_info "======================================================"
log_info "开始测试所有磁盘(包括系统盘)"
log_info "======================================================"
run_all_disks_test
local result=$?
if [[ $result -eq 0 ]]; then
log_success "======================================================"
log_success "所有磁盘测试通过,性能符合要求"
log_success "======================================================"
else
log_error "======================================================"
log_error "部分磁盘测试存在问题,请查看上方详细信息"
log_error "======================================================"
fi
exit $result
}
# 信号处理
trap 'log_error "测试被中断"; exit 1' INT TERM
# 运行主函数
main "$@"
更多推荐


所有评论(0)