do {…} while (0) 的用途汇总(欢迎补充)

在一些Linux内核和其它的开源代码中,我们经常看到像下面这样的代码:

do{

}while(0)

该代码片段并非循环,这样想想似乎使用do…while没有任何意义,那么为什么还要使用它呢?

实际上,do{…}while(0)的用途并不仅仅是优化你的代码。经过一系列的调研和探索,我们总结出它的一些用途如下。

1. 帮助定义复杂的宏以避免错误

详细解释请看“do {…} while (0) 在宏定义中的作用”,讲述了如何利用do {…} while (0) 解决常犯的宏定义错误,让大家不再惧怕宏。

2. 避免使用goto控制程序流

在一些函数中,我们在return语句之前可能需要做一些工作,比如释放在函数一开始由malloc函数申请的内存空间,使用goto总是一种简单的方法:

int foo()

{

somestruct* ptr = malloc(…);

dosomething…;

if(error)

{

goto END;

}

dosomething…;

if(error)

{

goto END;

}

dosomething…;

END:

free(ptr);

return 0;

}

但由于goto关键字可能会使代码不易读,因此许多人都不推荐使用它,那么我们可以使用do{…}while(0)来解决这一问题:

int foo()

{

somestruct* ptr = malloc(…);

do{

dosomething…;

if(error)

{

break;

}

dosomething…;

if(error)

{

break;

}

dosomething…;

}while(0);

free(ptr);

return 0;

}

这里,我们使用do{…}while(0)来包含函数的主要部分,同时使用break替换goto,代码的可读性增强了。

3. 避免由宏引起的警告

由于内核不同体系结构的限制,我们可能需要多次使用空宏。在编译的时候,这些空宏会产生警告,为了避免这种警告,我们可以使用do{…}while(0)来定义空宏:

#define EMPTYMICRO do{}while(0)

这样在编译的时候就不会产生警告。

4. 定义单一的函数块来完成复杂的操作

如果你有一个复杂的函数且你不想要创建新的函数,那么使用do{…}while(0),你可以将一些代码放在这里面并定义一些变量,这样你就不必担心do{…}while(0)外面的变量名是否与do{…}while(0)里面的变量名相同造成重复了。

如果你发现do{…}while(0)其它的好用途,请与我们分享哦!


转自http://www.yxkfw.com/?p=43525

#!/bin/bash #================================================== # 🔧 磁盘性能测试脚本(极简增强版 | 支持全模式轮循 + 全量预热 + NVMe/SATA通用) # # 🚀【一句话启动】 # ./disk_test.sh sda -warmup # → 进入交互菜单,选择预热方式和测试流程 # # 💡【支持设备类型】 # ✅ SATA/SAS 盘: /dev/sda, /dev/sdb, ... # ✅ NVMe 固态: /dev/nvme0n1, /dev/nvme1n1, ... # ✅ 多盘并发: sda,sdb,nvme0n1 # # 📌【基本语法】 # ./disk_test.sh <device_list> [options] [-warmup|-nowarmup] # # 🛠️【参数说明】 # rw= # 读写模式: read/write/randread/randwrite/randrw (默认 read) # bs= # 块大小: 4k, 8k, 512b, 1m 等 (默认 4k) # iodepth= # I/O 队列深度 (默认 8) # numjobs= # 并发线程数 (默认 1) # runtime= # 测试运行时间(秒)(默认 60) # ramp_time= # 预热时间(不计入结果统计)(默认 5) # size= # 测试范围: 100%, 10G, 500M 等 (默认 100%) # ioengine= # I/O 引擎: libaio, sync, psync, vsync (默认 libaio) # direct= # 是否绕过缓存: 1=是, 0=否 (默认 1) # jobname= # 自定义输出文件名前缀 # # 🔥【预热控制】 # -warmup # 启用交互式预热选择(推荐) # -nowarmup # 跳过预热,直接主测 # # 🎯【交互式预热菜单(选择 -warmup 后出现)】 # 1) 顺序读 (seqread) → 预热后执行主测试 # 2) 顺序写 (seqwrite) # 3) 随机读 (randread) # 4) 随机写 (randwrite) # 5) 混合随机读写 (70%读+30%写) # 6) 全模式轮循测试: # randread → randwrite → read → write → randrw # 每个模式独立预热 + 主测 # 7) 顺序流程测试: # 预热+主测 → 顺序读 # 预热+主测 → 顺序写 # 8) 随机流程测试: # 预热+主测 → 随机读 # 预热+主测 → 随机写 # # 📂【输出内容】 # fio_results/<timestamp>/ # ├── *.txt # FIO 原始结果报告 # ├── results.csv # 汇总表格(IOPS/BW/Latency) # ├── bw_timeline.<rw>.csv # 不同读写模式的带宽时间序列 # └── iops_timeline.<rw>.csv # 不同读写模式的 IOPS 时间序列 # # 🧪【典型使用示例】 # # 1. 【快速测试 NVMe 随机读】 # ./disk_test.sh nvme0n1 rw=randread bs=4k iodepth=128 numjobs=4 runtime=60 -warmup # # 2. 【SATA 盘顺序写性能】 # ./disk_test.sh sda rw=write bs=1m iodepth=16 numjobs=1 runtime=120 -warmup # # 3. 【多盘并发测试(RAID 场景)】 # ./disk_test.sh sda,sdb,nvme0n1 rw=randread bs=4k iodepth=64 numjobs=2 runtime=60 -warmup # # 4. 【跳过预热,快速跑一次】 # ./disk_test.sh nvme0n1 -nowarmup rw=read bs=4k # # 5. 【自定义命名 + 特定范围测试】 # ./disk_test.sh nvme0n1 jobname=mytest size=10G runtime=30 # # 6. 【高负载压测 NVMe(发挥极限性能)】 # ./disk_test.sh nvme0n1 rw=randread bs=4k iodepth=256 numjobs=8 ioengine=libaio direct=1 runtime=120 -warmup # # ⚠️ 注意事项: # • 建议使用 sudo 或确保对设备有读写权限 # • 避免对系统盘(如 /boot 或 /)执行写测试 # • 可通过 lsblk 查看设备用途:lsblk -f # # 📈 后续分析建议: # 使用 Python pandas 将多个 *_timeline.*.csv 合并为 Excel 多 sheet 文件 #================================================== # 必须使用 bash if [ -z "${BASH_VERSION:-}" ]; then echo "❌ 错误:此脚本必须使用 bash 运行,请使用: bash $0" exit 1 fi # 默认参数 DEFAULT_BS="4k" DEFAULT_IODEPTH=8 DEFAULT_NUMJOBS=1 DEFAULT_RW="read" DEFAULT_RUNTIME=60 DEFAULT_RAMP_TIME=5 DEFAULT_IOENGINE="libaio" DEFAULT_DIRECT=1 DEFAULT_SIZE="100%" # 👈 默认全盘范围预热 #-------------------------------------------------- # 显示帮助 show_help() { awk '/^#.*🚀.*一句话启动/,/^#.*📈.*后续分析建议/' "$0" | sed 's/^# //' exit 0 } for arg in "$@"; do [[ "$arg" == "-h" || "$arg" == "--help" ]] && show_help; done #-------------------------------------------------- # 参数解析 #-------------------------------------------------- WARMUP_MODE="" DEVICE_LIST_STR="" SIZE="$DEFAULT_SIZE" IOENGINE="$DEFAULT_IOENGINE" RUNTIME="$RUNTIME" RAMP_TIME="$RAMP_TIME" DIRECT="$DIRECT" CUSTOM_JOBNAME="" BS="" NUMJOBS="" IODEPTH="" RW="" while [ $# -gt 0 ]; do arg="$1"; shift case "$arg" in -warmup) WARMUP_MODE="force" ;; -nowarmup) WARMUP_MODE="skip" ;; bs=*) BS="${arg#*=}" ;; iodepth=*) IODEPTH="${arg#*=}" ;; numjobs=*) NUMJOBS="${arg#*=}" ;; rw=*) RW="${arg#*=}" ;; runtime=*) RUNTIME="${arg#*=}" ;; --time=*|time=*) RUNTIME="${arg#*=}" ;; ramp_time=*) RAMP_TIME="${arg#*=}" ;; --ioengine=*|ioengine=*) IOENGINE="${arg#*=}" ;; direct=*) DIRECT="${arg#*=}" ;; jobname=*) CUSTOM_JOBNAME="${arg#*=}" ;; size=*) SIZE="${arg#*=}" ;; *) if [ -z "$DEVICE_LIST_STR" ]; then DEVICE_LIST_STR="$arg" else echo "⚠️ 忽略未知参数: $arg" fi ;; esac done # 设置默认值 BS="${BS:-$DEFAULT_BS}" IODEPTH="${IODEPTH:-$DEFAULT_IODEPTH}" NUMJOBS="${NUMJOBS:-$DEFAULT_NUMJOBS}" RW="${RW:-$DEFAULT_RW}" RUNTIME="${RUNTIME:-$DEFAULT_RUNTIME}" RAMP_TIME="${RAMP_TIME:-$DEFAULT_RAMP_TIME}" IOENGINE="${IOENGINE:-$DEFAULT_IOENGINE}" DIRECT="${DIRECT:-$DEFAULT_DIRECT}" SIZE="${SIZE:-$DEFAULT_SIZE}" # 检查设备 if [ -z "$DEVICE_LIST_STR" ]; then echo "❌ 错误:未指定任何设备!" show_help fi is_valid_device_short() { [[ "$1" =~ ^sd[a-z]+$ ]] || [[ "$1" =~ ^nvme[0-9]+n[0-9]+$ ]] } IFS=',' read -ra DEVICES_INPUT <<< "$DEVICE_LIST_STR" DEVICE_ARRAY=() for dev in "${DEVICES_INPUT[@]}"; do dev=$(echo "$dev" | xargs) full_dev="${dev#/dev/}"; full_dev="/dev/$full_dev" [[ "$dev" == /dev/* ]] && full_dev="$dev" if ! is_valid_device_short "$(basename "$full_dev")" && [[ ! -b "$full_dev" ]]; then echo "❌ 设备无效或不存在: $full_dev" exit 1 fi DEVICE_ARRAY+=("$full_dev") done # 检查 fio if ! command -v fio &> /dev/null; then echo "❌ 错误:fio 未安装,请运行 sudo apt install fio" exit 1 fi # 创建输出目录 TIMESTAMP_FULL="$(date '+%Y%m%d_%H%M%S')" DATE_STAMP="$(date '+%Y-%m-%d %H:%M:%S')" OUTPUT_DIR="./fio_results/$TIMESTAMP_FULL" mkdir -p "$OUTPUT_DIR" JOBNAME_BASE="${CUSTOM_JOBNAME:-bs${BS}_iod${IODEPTH}_nj${NUMJOBS}_sz${SIZE}_eng_${IOENGINE##*/}_t${RUNTIME}s}_${TIMESTAMP_FULL}" TXT_OUT="$OUTPUT_DIR/${JOBNAME_BASE}.txt" CSV_FILE="$OUTPUT_DIR/results.csv" LOG_FILE="$OUTPUT_DIR/summary.log" ERROR_LOG="$OUTPUT_DIR/error.log" > "$LOG_FILE" echo "# FIO Error Log - $DATE_STAMP" > "$ERROR_LOG" TEST_TYPE="single_disk" (( ${#DEVICE_ARRAY[@]} > 1 )) && TEST_TYPE="concurrent_multi_disk" # 打印配置摘要 echo "🔧 测试配置摘要" printf " %-12s : %s\n" "Devices" "$(IFS=','; echo "${DEVICE_ARRAY[*]}")" printf " %-12s : %s\n" "Block Size" "$BS" printf " %-12s : %s\n" "I/O Depth" "$IODEPTH" printf " %-12s : %s\n" "Num Jobs" "$NUMJOBS" printf " %-12s : %ss\n" "Runtime" "$RUNTIME" printf " %-12s : %s\n" "Test Range" "$SIZE" printf " %-12s : %s\n" "I/O Engine" "$IOENGINE" printf " %-12s : %s\n" "Test Type" "$TEST_TYPE" #-------------------------------------------------- # ✅ 新增:执行单次预热 + 单次主测试流程 #-------------------------------------------------- run_single_pair() { local rw_type="$1" # randread, read 等 local phase="$2" # 描述阶段名称,如 "随机读" 或 "顺序写" local warmup_rw="$3" # 可选覆盖预热模式,默认同主测 warmup_rw="${warmup_rw:-$rw_type}" echo "" echo "🔄 正在为 [$phase] 执行全量预热..." local warmup_err="/tmp/fio_pair_warmup_$$.log" > "$warmup_err" local warmup_cmd=( fio --name="warmup_${rw_type}" --rw="$warmup_rw" --bs="4k" --iodepth=16 --runtime=30 --time_based --direct=1 --ioengine="$IOENGINE" --size="$SIZE" --output-format=normal ) for dev in "${DEVICE_ARRAY[@]}"; do warmup_cmd+=(--filename="$dev") done (( ${#DEVICE_ARRAY[@]} > 1 )) && warmup_cmd+=(--group_reporting) if "${warmup_cmd[@]}" > /dev/null 2>"$warmup_err"; then echo "✅ 全量预热完成 ($phase)" else echo "❌ 预热失败 ($phase),继续下一个..." cat "$warmup_err" >&2 fi rm -f "$warmup_err" # ---- 主测试 ---- local jobname="${JOBNAME_BASE}_auto_${rw_type}" local txt_out="$OUTPUT_DIR/${jobname}.txt" local bw_prefix="$OUTPUT_DIR/${jobname}" local iops_prefix="$OUTPUT_DIR/${jobname}" echo "🚀 执行主测试: $phase ..." local main_err="/tmp/fio_pair_main_$$.log" > "$main_err" local cmd=( fio --rw="$rw_type" --bs="$BS" --direct="$DIRECT" --ioengine="$IOENGINE" --iodepth="$IODEPTH" --numjobs="$NUMJOBS" --runtime="$RUNTIME" --ramp_time="$RAMP_TIME" --size="$SIZE" --time_based --output-format=normal --output="$txt_out" --write_bw_log="$bw_prefix" --write_iops_log="$iops_prefix" --log_avg_msec=1000 ) for dev in "${DEVICE_ARRAY[@]}"; do cmd+=(--filename="$dev") done (( ${#DEVICE_ARRAY[@]} > 1 )) && cmd+=(--group_reporting --name=multi_device_test) || cmd+=(--name="$jobname") if "${cmd[@]}" > /dev/null 2>"$main_err"; then echo "✅ 主测试完成: $jobname" else echo "❌ 主测试失败!查看日志: $main_err" cat "$main_err" >> "$ERROR_LOG" rm -f "$main_err" return 1 fi rm -f "$main_err" # ---- 写入结果 ---- local result=$(parse_fio_result_stable "$txt_out") IFS=',' read -r IOPS_RAW BW_RAW LAT_RAW <<< "$result" devices_quoted=$(printf '%s' "${DEVICE_ARRAY[*]}" | sed 's/,/","/g') echo "$DATE_STAMP,$TEST_TYPE,\"$devices_quoted\",$rw_type,$BS,$IODEPTH,$NUMJOBS,$SIZE,$IOENGINE,$RUNTIME,\"$IOPS_RAW\",\"$BW_RAW\",\"$LAT_RAW\"" >> "$CSV_FILE" printf "%s | %-50s -> IOPS=%-8s, BW=%-10s, Lat=%-8s\n" \ "$(date '+%Y-%m-%d %H:%M:%S')" "$jobname" "$IOPS_RAW" "$BW_RAW" "$LAT_RAW" >> "$LOG_FILE" extract_log_to_csv "bw" "$OUTPUT_DIR/${jobname}" "bw_kibys" "$rw_type" extract_log_to_csv "iops" "$OUTPUT_DIR/${jobname}" "iops" "$rw_type" } #-------------------------------------------------- # ✅ 新增:顺序读写流程(选项7) #-------------------------------------------------- run_sequential_flow() { echo "" echo "🔁【选项7】开始执行 顺序读写 流程..." run_single_pair "read" "顺序读" "read" run_single_pair "write" "顺序写" "write" echo "" echo "🎉 顺序读写流程已完成!" exit 0 } #-------------------------------------------------- # ✅ 新增:随机读写流程(选项8) #-------------------------------------------------- run_random_flow() { echo "" echo "🔁【选项8】开始执行 随机读写 流程..." run_single_pair "randread" "随机读" "randread" run_single_pair "randwrite" "随机写" "randwrite" echo "" echo "🎉 随机读写流程已完成!" exit 0 } #-------------------------------------------------- # 🔁 增强版预热逻辑:支持交互式选择预热模式(含全模式轮循) #-------------------------------------------------- prompt_warmup() { # 支持 -warmup 强制启用、-nowarmup 强制跳过 if [[ "$WARMUP_MODE" == "skip" ]]; then return 1 # 不预热 fi echo "" echo "🔥 是否执行预热?" echo " 1) 是(推荐)" echo " 2) 否" while true; do read -p "请选择 [1/2] > " choice case "$choice" in 1) echo "" echo "⚙️ 请选择预热模式:" echo " 1) 顺序读 (seqread)" echo " 2) 顺序写 (seqwrite)" echo " 3) 随机读 (randread)" echo " 4) 随机写 (randwrite)" echo " 5) 混合随机读写 (70% 读, 30% 写)" echo " 6) 全模式轮循测试(预热+主测 循环执行)" echo " 7) 顺序流程:预热+主测 → 读,再 → 写" echo " 8) 随机流程:预热+主测 → 随机读,再 → 随机写" while true; do read -p "请选择预热模式 [1-8] > " mode case "$mode" in 1) WARMUP_RW="read"; WARMUP_TYPE="seqread"; break ;; 2) WARMUP_RW="write"; WARMUP_TYPE="seqwrite"; break ;; 3) WARMUP_RW="randread"; WARMUP_TYPE="randread"; break ;; 4) WARMUP_RW="randwrite"; WARMUP_TYPE="randwrite"; break ;; 5) WARMUP_RW="randrw"; WARMUP_TYPE="mixrandrw_70r30w"; break ;; 6) WARMUP_RW="cycle_all"; WARMUP_TYPE="cycle_all"; break ;; 7) WARMUP_RW="seq_flow"; WARMUP_TYPE="seq_flow"; break ;; 8) WARMUP_RW="rand_flow"; WARMUP_TYPE="rand_flow"; break ;; *) echo "请输入 1 到 8 之间的数字";; esac done return 0 # 表示要执行预热或流程 ;; 2) return 1 # 不执行预热 ;; *) echo "请输入 1 或 2" ;; esac done } run_warmup() { local err="/tmp/fio_warmup_err_$$.log" > "$err" local warmup_bs="4k" local warmup_iodepth=16 local warmup_runtime=30 echo "🔄 开始全量预热: $WARMUP_TYPE (size=$SIZE) ..." local fio_args=( --name="warmup_${WARMUP_TYPE}" --bs="$warmup_bs" --iodepth="$warmup_iodepth" --runtime="$warmup_runtime" --time_based --direct=1 --ioengine="$IOENGINE" --size="$SIZE" --output-format=normal ) case "$WARMUP_RW" in read|write|randread|randwrite) fio_args+=(--rw="$WARMUP_RW") ;; randrw) fio_args+=(--rw=randrw --rwmixread=70 --rwmixwrite=30) ;; cycle_all) rm -f "$err" return 2 # 触发全模式轮循 ;; seq_flow) rm -f "$err" return 3 # 触发顺序流程 ;; rand_flow) rm -f "$err" return 4 # 触发随机流程 ;; *) echo "❌ 无效的预热模式: $WARMUP_RW" rm -f "$err" return 1 ;; esac # 添加所有设备 for dev in "${DEVICE_ARRAY[@]}"; do fio_args+=(--filename="$dev") done # 多盘时启用 group_reporting (( ${#DEVICE_ARRAY[@]} > 1 )) && fio_args+=(--group_reporting) # 执行 fio if fio "${fio_args[@]}" > /dev/null 2>"$err"; then echo "✅ 全量预热完成 [$WARMUP_TYPE]" else echo "⚠️ 预热失败!错误信息如下:" cat "$err" fi rm -f "$err" } #-------------------------------------------------- # ✅ 全模式轮循主函数(每个模式均执行全量预热 + 主测试) #-------------------------------------------------- run_cycle_all_tests() { local modes=("read" "write" "randread" "randwrite" "randrw") local mix_opts=("" "" "" "" "--rwmixread=70 --rwmixwrite=30") for i in "${!modes[@]}"; do local current_rw="${modes[i]}" local extra_args=() [[ -n "${mix_opts[i]}" ]] && extra_args=(${mix_opts[i]}) local jobname="${JOBNAME_BASE}_auto_${current_rw}" local txt_out="$OUTPUT_DIR/${jobname}.txt" local bw_prefix="$OUTPUT_DIR/${jobname}" local iops_prefix="$OUTPUT_DIR/${jobname}" echo "" echo "🔁【轮循模式】正在处理: $current_rw" # ---- 全量预热阶段 ---- echo "🔥 正在为 $current_rw 执行全量预热 (size=$SIZE)..." local warmup_err="/tmp/fio_cycle_warmup_$$.log" > "$warmup_err" local warmup_cmd=( fio --name="warmup_${current_rw}" --rw="$current_rw" --bs="4k" --iodepth=16 --runtime=30 --time_based --direct=1 --ioengine="$IOENGINE" --size="$SIZE" --output-format=normal ) [[ -n "${extra_args[0]}" ]] && warmup_cmd+=("${extra_args[@]}") for dev in "${DEVICE_ARRAY[@]}"; do warmup_cmd+=(--filename="$dev") done (( ${#DEVICE_ARRAY[@]} > 1 )) && warmup_cmd+=(--group_reporting) if "${warmup_cmd[@]}" > /dev/null 2>"$warmup_err"; then echo "✅ 全量预热完成 ($current_rw)" else echo "❌ 预热失败 ($current_rw),继续下一个..." cat "$warmup_err" >&2 fi rm -f "$warmup_err" # ---- 主测试 ---- echo "🚀 执行主测试: $current_rw ..." local main_err="/tmp/fio_cycle_main_$$.log" > "$main_err" local cmd=( fio --rw="$current_rw" --bs="$BS" --direct="$DIRECT" --ioengine="$IOENGINE" --iodepth="$IODEPTH" --numjobs="$NUMJOBS" --runtime="$RUNTIME" --ramp_time="$RAMP_TIME" --size="$SIZE" --time_based --output-format=normal --output="$txt_out" --write_bw_log="$bw_prefix" --write_iops_log="$iops_prefix" --log_avg_msec=1000 ) cmd+=("${extra_args[@]}") for dev in "${DEVICE_ARRAY[@]}"; do cmd+=(--filename="$dev") done (( ${#DEVICE_ARRAY[@]} > 1 )) && cmd+=(--group_reporting --name=multi_device_test) || cmd+=(--name="$jobname") if "${cmd[@]}" > /dev/null 2>"$main_err"; then echo "✅ 主测试完成: $jobname" else echo "❌ 主测试失败!查看日志: $main_err" cat "$main_err" >> "$ERROR_LOG" rm -f "$main_err" continue fi rm -f "$main_err" # ---- 提取结果并写入 CSV ---- local result=$(parse_fio_result_stable "$txt_out") IFS=',' read -r IOPS_RAW BW_RAW LAT_RAW <<< "$result" devices_quoted=$(printf '%s' "${DEVICE_ARRAY[*]}" | sed 's/,/","/g') echo "$DATE_STAMP,$TEST_TYPE,\"$devices_quoted\",$current_rw,$BS,$IODEPTH,$NUMJOBS,$SIZE,$IOENGINE,$RUNTIME,\"$IOPS_RAW\",\"$BW_RAW\",\"$LAT_RAW\"" >> "$CSV_FILE" printf "%s | %-50s -> IOPS=%-8s, BW=%-10s, Lat=%-8s\n" \ "$(date '+%Y-%m-%d %H:%M:%S')" "$jobname" "$IOPS_RAW" "$BW_RAW" "$LAT_RAW" >> "$LOG_FILE" extract_log_to_csv "bw" "$OUTPUT_DIR/${jobname}" "bw_kibys" "$current_rw" extract_log_to_csv "iops" "$OUTPUT_DIR/${jobname}" "iops" "$current_rw" done echo "" echo "🎉 全模式轮循测试已完成!结果保存在: $OUTPUT_DIR" exit 0 } #-------------------------------------------------- # ✅ 提取汇总指标(从 TXT 报告) #-------------------------------------------------- parse_fio_result_stable() { local file="$1" local throughput_line throughput_line=$(grep -i "\(iops\|bw\)" "$file" | grep -E "(^[[:space:]]*[rw].*:)|(group_report)" | head -1) local iops_raw=$(echo "$throughput_line" | grep -oiE "iops=[^,]*[^,[:space:]]" | cut -d= -f2 | xargs || echo "N/A") local bw_raw=$(echo "$throughput_line" | grep -oiE "bw=[^,]*[^,[:space:]]B.s" | cut -d= -f2 | xargs || echo "N/A") local lat_raw=$(grep -i "\(lat.*avg\|clat.*avg\)" "$file" | cut -d= -f4 | sed -n '3p' | cut -d, -f1 | xargs || echo "N/A") echo "$iops_raw,$bw_raw,$lat_raw" } #-------------------------------------------------- # ✅ ✅ 修改:提取日志 → 按 rw 类型分文件(模拟多 sheet) #-------------------------------------------------- extract_log_to_csv() { local type="$1" local prefix="$2" local unit_col="$3" local rw_mode="$4" # 新增参数:当前测试类型 # 如果没有传 rw_mode,则默认为 single local suffix="${rw_mode:-single}" local output_file="$OUTPUT_DIR/${type}_timeline.${suffix}.csv" local pattern="${prefix}_${type}.*.log" echo "🔍 查找 ${type} 日志文件模式: $pattern" shopt -s nullglob local log_files=($pattern) shopt -u nullglob if [ ${#log_files[@]} -eq 0 ]; then echo "❌ 未找到 ${type} 日志文件:$pattern" return 1 fi echo "✅ 发现 ${#log_files[@]} 个 ${type} 日志文件:" printf " %s\n" "${log_files[@]}" # 每次追加前判断是否首次创建该文件 if [ ! -f "$output_file" ]; then echo "time_sec,value" > "$output_file" fi for file in "${log_files[@]}"; do echo "📂 处理文件: $(basename "$file")" while IFS=',' read -r ms val rest; do [[ "$ms" =~ ^[0-9]+$ ]] || continue clean_val=$(echo "$val" | tr -d ',') sec=$((ms / 1000)) echo "$sec,$clean_val" >> "$output_file" done < "$file" done local count=$(($(wc -l < "$output_file") - 1)) echo "📈 成功生成 $type 时间序列 ($suffix): $output_file ($count 行)" } #-------------------------------------------------- # 🔁 执行预热判断 #-------------------------------------------------- if prompt_warmup; then run_warmup warmup_ret=$? case $warmup_ret in 2) run_cycle_all_tests ;; 3) run_sequential_flow ;; 4) run_random_flow ;; *) # 继续执行单次主测试 echo "⏭️ 已完成预热,即将开始主测试..." ;; esac else # 跳过预热 echo "⏭️ 跳过预热,直接开始主测试..." fi #-------------------------------------------------- # 执行单次主测试(非轮循模式) #-------------------------------------------------- ERROR_CAPTURE="/tmp/fio_err_$$.log" > "$ERROR_CAPTURE" # 构建主测试命令 FIO_CMD=( fio --rw="$RW" --bs="$BS" --direct="$DIRECT" --ioengine="$IOENGINE" --iodepth="$IODEPTH" --numjobs="$NUMJOBS" --runtime="$RUNTIME" --ramp_time="$RAMP_TIME" --size="$SIZE" --time_based --output-format=normal --output="$TXT_OUT" --write_bw_log="$OUTPUT_DIR/${JOBNAME_BASE}" --write_iops_log="$OUTPUT_DIR/${JOBNAME_BASE}" --log_avg_msec=1000 ) # 添加设备 for dev in "${DEVICE_ARRAY[@]}"; do FIO_CMD+=(--filename="$dev") done # 多设备设置 (( ${#DEVICE_ARRAY[@]} > 1 )) && FIO_CMD+=(--group_reporting --name=multi_device_test) || FIO_CMD+=(--name="$JOBNAME_BASE") echo "" echo "🚀 正在运行主测试: $JOBNAME_BASE ..." echo "⏱️ 开始时间: $DATE_STAMP" if "${FIO_CMD[@]}" > /dev/null 2>"$ERROR_CAPTURE"; then echo "✅ 主测试完成" else echo "❌ fio 测试失败!返回码: $?" cat "$ERROR_CAPTURE" >> "$ERROR_LOG" echo "📝 错误已记录到: $ERROR_LOG" rm -f "$ERROR_CAPTURE" exit 1 fi rm -f "$ERROR_CAPTURE" #-------------------------------------------------- # 写入 CSV & 提取数据 #-------------------------------------------------- RESULT=$(parse_fio_result_stable "$TXT_OUT") IFS=',' read -r IOPS_RAW BW_RAW LAT_RAW <<< "$RESULT" # 初始化总 CSV if [ ! -f "$CSV_FILE" ]; then echo "timestamp,test_type,devices,rw,bs,iodepth,numjobs,size,ioengine,runtime_sec,iops_str,bw_str,latency_str" > "$CSV_FILE" fi # 转义设备列表用于 CSV devices_quoted=$(printf '%s' "${DEVICE_ARRAY[*]}" | sed 's/,/","/g') echo "$DATE_STAMP,$TEST_TYPE,\"$devices_quoted\",$RW,$BS,$IODEPTH,$NUMJOBS,$SIZE,$IOENGINE,$RUNTIME,\"$IOPS_RAW\",\"$BW_RAW\",\"$LAT_RAW\"" >> "$CSV_FILE" # 记录日志 printf "%s | %-50s -> IOPS=%-8s, BW=%-10s, Lat=%-8s\n" \ "$(date '+%Y-%m-%d %H:%M:%S')" "$JOBNAME_BASE" "$IOPS_RAW" "$BW_RAW" "$LAT_RAW" >> "$LOG_FILE" echo "📊 提取结果: IOPS=$IOPS_RAW, BW=$BW_RAW, Lat=$LAT_RAW" # 提取时间线数据(单次测试统一写入 .single.csv) BASE_LOG_PREFIX="$OUTPUT_DIR/${JOBNAME_BASE}" extract_log_to_csv "bw" "$BASE_LOG_PREFIX" "bw_kibys" "$RW" extract_log_to_csv "iops" "$BASE_LOG_PREFIX" "iops" "$RW" # 完成提示 echo "" echo "🎉 测试成功完成!所有输出已保存至:" echo "📁 $OUTPUT_DIR" echo "📄 报告: $TXT_OUT" echo "📊 汇总: $CSV_FILE" for mode in read write randread randwrite randrw; do [ -f "$OUTPUT_DIR/bw_timeline.$mode.csv" ] && echo "📈 BW ($mode): $OUTPUT_DIR/bw_timeline.$mode.csv" [ -f "$OUTPUT_DIR/iops_timeline.$mode.csv" ] && echo "📈 IOPS ($mode): $OUTPUT_DIR/iops_timeline.$mode.csv" done 基本代码和前段的使用方法不变 使用awk或者sed修改上面代码,实现如下需求! time_sec,job1_bw,job2_bw,job3_bw,job4_bw,job5_bw,job6_bw,job7_bw,job8_bw 0,1200,1150,1180,1210,1190,1170,1160,1220 1,1250,1180,1200,1230,1210,1190,1180,1240
最新发布
11-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值