#!/bin/sh # ============================================================ # bench.sh - 内存 & 磁盘 IO 速度检测 # 兼容 BusyBox / POSIX sh,无第三方依赖 # ============================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # ── 自动寻找可写磁盘目录 ───────────────────────────────────── # 排除 tmpfs/ramfs(内存文件系统),找真实存储挂载点 find_disk_dir() { # 遍历常见路径,找非 tmpfs 的可写目录 for dir in /overlay /mnt /data /storage /usb /opt /root /home; do [ -d "$dir" ] || continue [ -w "$dir" ] || continue fstype=$(df -T "$dir" 2>/dev/null | awk 'NR==2{print $2}') # 跳过内存文件系统 case "$fstype" in tmpfs|ramfs|devtmpfs|none|"") continue ;; esac echo "$dir" return done # 再扫一遍 /proc/mounts 找非内存挂载点 while read -r dev mnt fstype rest; do case "$fstype" in tmpfs|ramfs|devtmpfs|sysfs|proc|cgroup*|devpts|overlay) continue ;; esac [ "$mnt" = "/" ] && continue [ -w "$mnt" ] || continue echo "$mnt" return done < /proc/mounts # 实在没有就用根目录 echo "/" } DISK_DIR="${1:-$(find_disk_dir)}" MEM_DIR="/dev/shm" [ ! -d "$MEM_DIR" ] && MEM_DIR="/tmp" DISK_FILE="$DISK_DIR/bench_$$" MEM_FILE="$MEM_DIR/bench_mem_$$" # ── 工具检测 ───────────────────────────────────────────────── check_tools() { for tool in dd awk free df; do command -v "$tool" > /dev/null 2>&1 || { printf "${RED}缺少工具: %s${NC}\n" "$tool"; exit 1; } done } # ── 输出格式 ───────────────────────────────────────────────── print_header() { printf "\n" printf "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}\n" printf "${BOLD}${CYAN}║ 系统性能基准测试 ║${NC}\n" printf "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}\n" printf " 测试时间: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" printf " 主机名: %s\n" "$(cat /proc/sys/kernel/hostname 2>/dev/null || echo unknown)" printf " 内核: %s\n" "$(uname -r)" printf "\n" } print_section() { printf "\n${BOLD}${YELLOW}▶ %s${NC}\n" "$1" printf "${YELLOW}──────────────────────────────────────────────────${NC}\n" } print_result() { printf " %-18s : ${GREEN}${BOLD}%s${NC} %s\n" "$1" "$2" "$3" } # ── 安全水位计算(最多写到 80%)──────────────────────────── # 返回允许写入的 KB 数,0 表示不可用 safe_limit_kb() { local dir="$1" local total_kb used_kb safe_kb pct_used total_kb=$(df -k "$dir" 2>/dev/null | awk 'NR==2{print $2}') used_kb=$(df -k "$dir" 2>/dev/null | awk 'NR==2{print $3}') [ -z "$total_kb" ] || [ "$total_kb" -eq 0 ] && { echo 0; return; } pct_used=$(( used_kb * 100 / total_kb )) safe_kb=$(( total_kb * 80 / 100 - used_kb )) if [ "$safe_kb" -le 0 ]; then printf " ${RED}警告: %s 已用 %d%%,超过 80%% 安全水位,跳过!${NC}\n" "$dir" "$pct_used" >&2 echo 0; return fi printf " ${CYAN}%s: 总 %dMB / 已用 %d%% / 本次最多写入 %dMB${NC}\n" \ "$dir" "$(( total_kb/1024 ))" "$pct_used" "$(( safe_kb/1024 ))" >&2 echo "$safe_kb" } # ── 核心测速函数 ───────────────────────────────────────────── # 优先用 dd 自身输出的速度;dd 无输出则手动计时 # BusyBox date 只有秒精度 → 强制写入足够大数据保证耗时 >= 2s timed_dd() { local src="$1" dst="$2" bs_kb="$3" count="$4" flags="$5" local t0 t1 elapsed dd_out dd_speed total_kb speed_mbs total_kb=$(( bs_kb * count )) # 判断 date 是否支持纳秒 local use_ns=0 local ts ts=$(date +%s%N 2>/dev/null) echo "$ts" | grep -vq 'N' && use_ns=1 if [ "$use_ns" = "1" ]; then t0=$(date +%s%N) dd_out=$(dd if="$src" of="$dst" bs="${bs_kb}k" count="$count" $flags 2>&1) sync t1=$(date +%s%N) elapsed=$(( (t1 - t0) / 1000000 )) # ms else t0=$(date +%s) dd_out=$(dd if="$src" of="$dst" bs="${bs_kb}k" count="$count" $flags 2>&1) sync t1=$(date +%s) elapsed=$(( (t1 - t0) * 1000 )) # ms fi # 尝试从 dd 输出提取速度(GNU dd / 部分 BusyBox dd) dd_speed=$(echo "$dd_out" | awk '{ for(i=1;i<=NF;i++){ if($i~/^[0-9.]+$/ && $(i+1)~/^[MmGgKk][Ii]?B\/s$/){ spd=$i; unit=$(i+1) } } } END { if(spd) printf "%s %s", spd, unit }') if [ -n "$dd_speed" ]; then printf "%s ${YELLOW}(dd)${NC}" "$dd_speed" return fi # 手动计时兜底 if [ "$elapsed" -le 0 ]; then # 耗时为 0,说明操作极快(内存缓存),给个提示 printf "${YELLOW}< 1s,数据可能来自缓存,仅供参考${NC}" return fi # total_kb * 1000 / elapsed = KB/s,再转 MB/s speed_mbs=$(awk "BEGIN{printf \"%.1f\", $total_kb / $elapsed }") printf "%s MB/s ${YELLOW}(计时 %dms)${NC}" "$speed_mbs" "$elapsed" } # ── 系统信息 ───────────────────────────────────────────────── show_sysinfo() { print_section "系统信息" local cpu cores mem_gb fstype cpu=$(grep -m1 'model name\|Hardware\|cpu model' /proc/cpuinfo 2>/dev/null | awk -F': ' '{print $2}') [ -z "$cpu" ] && cpu=$(uname -m) cores=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo "?") mem_gb=$(awk '/MemTotal/{printf "%.1f GB", $2/1024/1024}' /proc/meminfo) fstype=$(df -T "$DISK_DIR" 2>/dev/null | awk 'NR==2{print $2}') print_result "CPU" "$cpu" "($cores 核)" print_result "内存" "$mem_gb" "" print_result "内存测试目录" "$MEM_DIR" "($(df -T "$MEM_DIR" 2>/dev/null | awk 'NR==2{print $2}'))" print_result "磁盘测试目录" "$DISK_DIR" "($fstype)" } # ── 内存使用情况 ───────────────────────────────────────────── show_memory_info() { print_section "内存使用情况" free | awk ' NR==1 {printf " %-10s %10s %10s %10s\n", "", $1, $2, $3} NR==2 {printf " %-10s %10s %10s %10s\n", "内存(KB):", $2, $3, $4} NR==3 {printf " %-10s %10s %10s %10s\n", "Swap(KB):", $2, $3, $4} ' } # ── 内存读写测试(写到 /dev/shm 或 /tmp)──────────────────── test_memory() { print_section "内存读写速度 ($MEM_DIR)" local safe_kb safe_kb=$(safe_limit_kb "$MEM_DIR") [ "$safe_kb" -le 0 ] && { printf " ${RED}跳过:空间不足${NC}\n"; return; } # 限制最大 256MB,最小 16MB local max_kb=$(( 256 * 1024 )) local test_kb=$(( safe_kb < max_kb ? safe_kb : max_kb )) [ "$test_kb" -lt $(( 16 * 1024 )) ] && { printf " ${RED}跳过:安全可用空间不足 16MB${NC}\n"; return; } # BusyBox date 秒精度:确保数据量足够大让耗时 >= 2s # 假设内存 >= 500MB/s,2s 需要 >= 1000MB,但内存有限所以取实际值 local count=$(( test_kb / 1024 )) # 单位: 1MB块 [ "$count" -lt 1 ] && count=1 printf " ${CYAN}写入测试 (%dMB)...${NC}\n" "$count" local w w=$(timed_dd /dev/zero "$MEM_FILE" 1024 "$count") print_result "内存写入" "$w" "" printf " ${CYAN}读取测试 (%dMB)...${NC}\n" "$count" local r r=$(timed_dd "$MEM_FILE" /dev/null 1024 "$count") print_result "内存读取" "$r" "" rm -f "$MEM_FILE" } # ── 磁盘读写测试 ───────────────────────────────────────────── test_disk() { print_section "磁盘 IO 速度 ($DISK_DIR)" local safe_kb safe_kb=$(safe_limit_kb "$DISK_DIR") [ "$safe_kb" -le 0 ] && { printf " ${RED}跳过:空间不足${NC}\n"; return; } local max_kb=$(( 512 * 1024 )) local test_kb=$(( safe_kb < max_kb ? safe_kb : max_kb )) [ "$test_kb" -lt $(( 16 * 1024 )) ] && { printf " ${RED}跳过:安全可用空间不足 16MB${NC}\n"; return; } local count=$(( test_kb / 1024 )) [ "$count" -lt 1 ] && count=1 # ── 顺序写入 printf " ${CYAN}顺序写入 (%dMB, 含 sync)...${NC}\n" "$count" local sw sw=$(timed_dd /dev/zero "$DISK_FILE" 1024 "$count" "conv=fsync") print_result "顺序写入" "$sw" "" # ── 顺序读取 printf " ${CYAN}顺序读取 (%dMB)...${NC}\n" "$count" sync; echo 3 > /proc/sys/vm/drop_caches 2>/dev/null local sr sr=$(timed_dd "$DISK_FILE" /dev/null 1024 "$count") print_result "顺序读取" "$sr" "" # ── 随机 4K 写入 printf " ${CYAN}随机 4K 写入 (200次)...${NC}\n" local t0 t1 elapsed iops i=0 t0=$(date +%s) while [ $i -lt 200 ]; do offset=$(( (i * 7919 + 12345) % 9973 )) dd if=/dev/urandom of="$DISK_FILE" bs=4k count=1 seek=$offset conv=notrunc 2>/dev/null i=$(( i + 1 )) done sync t1=$(date +%s) elapsed=$(( t1 - t0 )) if [ "$elapsed" -gt 0 ]; then iops=$(( 200 / elapsed )) print_result "随机4K写入" "${iops} IOPS" "(${elapsed}s)" else print_result "随机4K写入" ">200 IOPS" "(<1s)" fi # ── 随机 4K 读取 printf " ${CYAN}随机 4K 读取 (200次)...${NC}\n" sync; echo 3 > /proc/sys/vm/drop_caches 2>/dev/null t0=$(date +%s); i=0 while [ $i -lt 200 ]; do offset=$(( (i * 7919 + 12345) % 9973 )) dd if="$DISK_FILE" of=/dev/null bs=4k count=1 skip=$offset 2>/dev/null i=$(( i + 1 )) done t1=$(date +%s) elapsed=$(( t1 - t0 )) if [ "$elapsed" -gt 0 ]; then iops=$(( 200 / elapsed )) print_result "随机4K读取" "${iops} IOPS" "(${elapsed}s)" else print_result "随机4K读取" ">200 IOPS" "(<1s)" fi rm -f "$DISK_FILE" } # ── 清理 ───────────────────────────────────────────────────── cleanup() { rm -f "$DISK_FILE" "$MEM_FILE" 2>/dev/null; } trap cleanup EXIT INT TERM # ── 主流程 ─────────────────────────────────────────────────── main() { check_tools print_header show_sysinfo show_memory_info test_memory test_disk printf "\n" printf "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}\n" printf "${BOLD}${CYAN}║ 测试完成 ║${NC}\n" printf "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}\n" printf "\n" } main