1. 目标 / 假设(短句)

  • 目标:用统一规则把“单盘基线(已取官方80%)”换算成阵列/具体磁盘的测试基线,便于自动化判定 PASS/FAIL(脚本变量:BASELINE_*)。

  • 关键假设

    • 单盘基线已经是“官方标称的 80%”(脚本常量如 HDD_7200_SEQ_READ 等)。

    • 顺序性能用大块(1MiB)测,随机性能以 4KiB 随机 IO(IOPS)测。

    • RAID 的并行增益与写惩罚通过经验系数折算(见下表)。


  1. 脚本内置的单盘基线(来自脚本常量)

(脚本把这些当作“单盘(已是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)


  1. 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)。


  1. 为什么这样设计(简要理由)

  • 读并行:阵列能并行读取多个盘的数据块 → 理论上 N(或 N-1)倍,但控制器/总线/调度/IO 大小会打折(因此乘 0.85~0.90)。

  • 写惩罚:RAID5 写需要读-修改-写 parity,随机写的放大最严重 → 脚本中用较低的系数(如 0.20)反映这一事实。

  • 镜像写:RAID1 必须把数据写到所有副本,吞吐受单盘限制 → 写近似单盘性能(系数 1.0)。

  • 工程保守性:脚本在多数系数上保守折算(不是理论极限),便于避免误判为通过。


  1. 演示示例(你的常见场景)

输入(单盘基线,已是 80%): single_seq_read = 100 MB/ssingle_rand_write = 55 IOPSN = 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)


  1. 脚本中额外的实现细节(关键点)

  • 磁盘检测:脚本优先用 storcli(若存在)判定 VD/PD 类型与 RAID 信息,回退到 smartctl/sys/block/*/queue/rotational 判断是否为 HDD/SSD,并尝试读 Rotation Rate(rpm)。

  • 若无法检测 rpm:对 RAID 磁盘默认假定 10000 RPM(脚本中有默认逻辑);非 RAID 默认 7200 RPM

  • 基线最终结果写入:脚本设置 BASELINE_SEQ_READ/WRITEBASELINE_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 "$@"

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐