Last active
December 31, 2024 19:12
-
-
Save ivmm/ec879710b14ab9f67148b2448ea8b5af to your computer and use it in GitHub Desktop.
disk_benchmark.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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