Skip to content

Instantly share code, notes, and snippets.

@ivmm
Last active December 31, 2024 19:12
Show Gist options
  • Save ivmm/ec879710b14ab9f67148b2448ea8b5af to your computer and use it in GitHub Desktop.
Save ivmm/ec879710b14ab9f67148b2448ea8b5af to your computer and use it in GitHub Desktop.
disk_benchmark.sh
#!/bin/bash
# 默认参数
DEFAULT_DEVICE="/dev/sda"
TEST_SIZE="4G" # 测试文件大小
RUNTIME="60" # 每项测试运行时间(秒)
FIO_TESTFILE="/tmp/fio_testfile" # FIO测试文件路径
# 显示使用方法
usage() {
echo "用法: $0 [-d device] [-s size] [-t time]"
echo "选项:"
echo " -d 指定测试设备 (默认: $DEFAULT_DEVICE)"
echo " -s 指定测试大小 (默认: $TEST_SIZE)"
echo " -t 指定测试时长 (默认: ${RUNTIME}秒)"
echo " -h 显示此帮助信息"
echo
echo "示例:"
echo " $0 -d /dev/nvme0n1"
echo " $0 -d /dev/vdb -s 8G -t 120"
exit 1
}
# 解析命令行参数
while getopts "d:s:t:h" opt; do
case $opt in
d) DEVICE="$OPTARG";;
s) TEST_SIZE="$OPTARG";;
t) RUNTIME="$OPTARG";;
h) usage;;
?) usage;;
esac
done
# 如果没有指定设备,使用默认值
if [ -z "$DEVICE" ]; then
DEVICE=$DEFAULT_DEVICE
fi
# 验证设备是否存在
if [ ! -b "$DEVICE" ]; then
echo "错误: 设备 $DEVICE 不存在或不是块设备"
exit 1
fi
OUTPUT_DIR="disk_benchmark_$(date +%Y%m%d_%H%M%S)"
SUMMARY_FILE="$OUTPUT_DIR/summary.txt"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 清理函数
cleanup() {
echo "清理测试文件..."
rm -f $FIO_TESTFILE
}
# 在脚本退出时自动清理
trap cleanup EXIT
# 检测是否为虚拟设备
is_virtual_device() {
local device=$1
if [[ $device =~ ^/dev/vd || $device =~ ^/dev/xvd ]]; then
return 0 # 是虚拟设备
else
return 1 # 不是虚拟设备
fi
}
# 格式化数值输出
format_number() {
local number=$1
if [[ $number =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
printf "%.2f" "$number"
else
echo "N/A"
fi
}
# 输出系统信息
print_system_info() {
echo "系统信息" | tee -a $SUMMARY_FILE
echo "------------------------" | tee -a $SUMMARY_FILE
echo "测试时间: $(date)" | tee -a $SUMMARY_FILE
echo "设备: $DEVICE" | tee -a $SUMMARY_FILE
echo "测试大小: $TEST_SIZE" | tee -a $SUMMARY_FILE
echo "测试时长: $RUNTIME 秒" | tee -a $SUMMARY_FILE
echo "设备类型: $(is_virtual_device $DEVICE && echo "虚拟设备" || echo "物理设备")" | tee -a $SUMMARY_FILE
echo "设备信息:" | tee -a $SUMMARY_FILE
lsblk $DEVICE | tee -a $SUMMARY_FILE
echo "文件系统信息:" | tee -a $SUMMARY_FILE
df -h $(dirname $FIO_TESTFILE) | tee -a $SUMMARY_FILE
echo -e "\n" | tee -a $SUMMARY_FILE
}
# 运行单个 FIO 测试并解析结果
run_fio_test() {
local test_name=$1
local additional_params=$2
local output_file="$OUTPUT_DIR/${test_name}.json"
local error_file="$OUTPUT_DIR/${test_name}.error"
echo "运行测试: $test_name" | tee -a $SUMMARY_FILE
# 确保测试文件不存在
rm -f $FIO_TESTFILE
echo "执行命令: fio --directory=$(dirname $FIO_TESTFILE) --filename=$(basename $FIO_TESTFILE) --direct=1 --rw=$3 --bs=$4 --size=$TEST_SIZE --name=$test_name --group_reporting --time_based --runtime=$RUNTIME --output-format=json $additional_params" | tee -a $SUMMARY_FILE
# 运行 FIO 测试并同时捕获标准输出和错误输出
if ! fio --directory=$(dirname $FIO_TESTFILE) \
--filename=$(basename $FIO_TESTFILE) \
--direct=1 --rw=$3 --bs=$4 --size=$TEST_SIZE \
--name=$test_name --group_reporting --time_based \
--runtime=$RUNTIME --output-format=json \
--numjobs=1 --thread --group_reporting \
$additional_params > $output_file 2>$error_file; then
echo "FIO测试失败: $test_name" | tee -a $SUMMARY_FILE
echo "错误信息:" | tee -a $SUMMARY_FILE
cat $error_file | tee -a $SUMMARY_FILE
return 1
fi
# 检查输出文件是否为空或无效
if [[ ! -s $output_file ]] || ! jq empty $output_file >/dev/null 2>&1; then
echo "警告: FIO输出无效或为空" | tee -a $SUMMARY_FILE
echo "FIO输出内容:" | tee -a $SUMMARY_FILE
cat $output_file | tee -a $SUMMARY_FILE
echo "错误输出:" | tee -a $SUMMARY_FILE
cat $error_file | tee -a $SUMMARY_FILE
return 1
fi
# 测试完成后立即删除测试文件
rm -f $FIO_TESTFILE
# 解析JSON结果,使用临时变量存储解析结果
local read_bw write_bw read_iops write_iops read_lat write_lat
# 使用jq安全地解析JSON,添加错误检查
if [[ -s $output_file ]]; then
read_bw=$(jq -r '.jobs[0].read.bw // 0' $output_file)
write_bw=$(jq -r '.jobs[0].write.bw // 0' $output_file)
read_iops=$(jq -r '.jobs[0].read.iops // 0' $output_file)
write_iops=$(jq -r '.jobs[0].write.iops // 0' $output_file)
read_lat=$(jq -r '.jobs[0].read.lat_ns.mean // 0' $output_file)
write_lat=$(jq -r '.jobs[0].write.lat_ns.mean // 0' $output_file)
# 转换单位: ns -> μs (微秒)
read_lat=$(echo "scale=2; $read_lat/1000" | bc)
write_lat=$(echo "scale=2; $write_lat/1000" | bc)
# 带宽转换为MB/s
read_bw=$(echo "scale=2; $read_bw/1024" | bc)
write_bw=$(echo "scale=2; $write_bw/1024" | bc)
else
echo "警告: FIO输出文件为空" | tee -a $SUMMARY_FILE
return 1
fi
echo "结果:" | tee -a $SUMMARY_FILE
echo "读取带宽: $(format_number $read_bw) MB/s" | tee -a $SUMMARY_FILE
echo "写入带宽: $(format_number $write_bw) MB/s" | tee -a $SUMMARY_FILE
echo "读取IOPS: $(format_number $read_iops)" | tee -a $SUMMARY_FILE
echo "写入IOPS: $(format_number $write_iops)" | tee -a $SUMMARY_FILE
echo "读取延迟: $(format_number $read_lat) μs" | tee -a $SUMMARY_FILE
echo "写入延迟: $(format_number $write_lat) μs" | tee -a $SUMMARY_FILE
echo "------------------------" | tee -a $SUMMARY_FILE
# 显示剩余磁盘空间
echo "当前磁盘空间使用情况:" | tee -a $SUMMARY_FILE
df -h $(dirname $FIO_TESTFILE) | tee -a $SUMMARY_FILE
echo "------------------------" | tee -a $SUMMARY_FILE
}
# 检查错误率
check_disk_errors() {
echo "磁盘错误检查" | tee -a $SUMMARY_FILE
echo "------------------------" | tee -a $SUMMARY_FILE
# 检查系统日志中的I/O错误
echo "系统日志中的I/O错误:" | tee -a $SUMMARY_FILE
dmesg | grep -i "i/o error" | tail -n 5 | tee -a $SUMMARY_FILE
# 只在非虚拟设备上运行SMART检查
if ! is_virtual_device $DEVICE && command -v smartctl &> /dev/null; then
echo "SMART状态:" | tee -a $SUMMARY_FILE
if smartctl -a $DEVICE &> /dev/null; then
smartctl -H $DEVICE | tee -a $SUMMARY_FILE
echo "SMART错误日志:" | tee -a $SUMMARY_FILE
smartctl -l error $DEVICE | tee -a $SUMMARY_FILE
else
echo "此设备不支持SMART功能或无法访问" | tee -a $SUMMARY_FILE
fi
else
echo "虚拟设备或SMART工具未安装,跳过SMART检查" | tee -a $SUMMARY_FILE
fi
echo -e "\n" | tee -a $SUMMARY_FILE
}
# 主测试流程
main() {
# 检查是否为root用户
if [ "$EUID" -ne 0 ]; then
echo "请使用root权限运行此脚本"
exit 1
fi
# 检查必要工具
for cmd in fio jq bc; do
if ! command -v $cmd &> /dev/null; then
echo "请先安装 $cmd"
exit 1
fi
done
# 检查临时目录空间
local available_space=$(df -BG $(dirname $FIO_TESTFILE) | awk 'NR==2 {print $4}' | sed 's/G//')
local required_space=${TEST_SIZE%G}
if [ $available_space -lt $((required_space * 2)) ]; then
echo "警告: 临时目录空间可能不足,建议清理空间或减小测试文件大小"
echo "可用空间: ${available_space}G, 测试大小: ${TEST_SIZE}"
read -p "是否继续?(y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
print_system_info
# 顺序读写测试
run_fio_test "sequential_read" "--ioengine=libaio" "read" "1M"
run_fio_test "sequential_write" "--ioengine=libaio" "write" "1M"
# 随机读写测试
run_fio_test "random_read" "--ioengine=libaio --iodepth=32" "randread" "4k"
run_fio_test "random_write" "--ioengine=libaio --iodepth=32" "randwrite" "4k"
# 混合随机读写测试
run_fio_test "mixed_random" "--ioengine=libaio --iodepth=32 --rwmixread=70" "randrw" "4k"
# 检查错误率
check_disk_errors
echo "测试完成! 详细结果保存在: $OUTPUT_DIR"
}
# 运行主函数
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment