ubuntu下测试nvme带宽和健康度

拷机样机设备说明文档

> 飞腾 **D3000 + 昇腾 310P** 高低温拷机样机

> 系统:麒麟 UKUI · 内核 `5.4.18-87.76-generic` · 用户 `kylin`

> 工程路径:`/home/kylin/hdd3/main/QTracker_booster_G4040`


1. 硬件配置

| 部件 | 型号/规格 | 说明 |

|------|-----------|------|

| CPU | 飞腾 D3000(8 核) | `nproc` = 8,频率约 2100 MHz |

| NPU | 昇腾 310P | 卡号多为 **16**,芯片 ID **0**;`npu-smi` 表内 AICore 常为 NA |

| 系统盘 | 梵想 S790 4TB NVMe | `/dev/nvme0n1`,序列号示例 FXS790233120414 |

| 数据/克隆盘 | 梵想 S790 4TB NVMe | `/dev/nvme1n1`,序列号示例 FXS790253820192 |

| 机箱风扇 PWM | 飞腾 X100 PCI `1db7:dc2f` | 驱动 `pwm-phytium-pci.ko`,见 PWM风扇驱动说明.md(./PWM风扇驱动说明.md) |

| 远程 | ToDesk 4.8.5.1 / SSH | 无显示器时优先 SSH;ToDesk 临时密码需桌面或固定安全密码 |

关键 PCIe(摘录)

```text

04:00.6 NPU Controller 1db7:dc24

0f:00.7 PWM Controller 1db7:dc2f

07:00.0 nvme0 (系统)

08:00.0 nvme1 (第二块 NVMe)

```


2. 软件与目录

| 路径 | 用途 |

|------|------|

| `/home/kylin/1.sh` | 终端 2 监控屏(温度/功耗/NPU/CPU/拷机状态) |

| `/home/kylin/2.sh` | 终端 1 拷机主程序(FP16+NPU 显存+CPU 内存+CPU 算力) |

| `/home/kylin/npu_stress_mem_worker.py` | NPU 显存 / CPU 内存 / CPU 算力 worker |

| `/home/kylin/npu_stress_suite/` | 拷机报告输出根目录(按时间戳子目录) |

| `/home/kylin/check_nvme_health.sh` | NVMe SMART 健康 + 顺序读/写带宽测试 |

| `/home/kylin/pwm-phytium-pci.ko` | PCI PWM 风扇驱动 |

| `docs/高低温测试操作手册.md` | 拷机操作步骤(简版) |

| `docs/PWM风扇驱动说明.md` | PWM 安装与占空比 |

| `scripts/install_pwm_phytium_pci.sh` | PWM 开机自启安装 |

| `scripts/apply_pwm_x100.sh` | PWM 高/低转速占空比 |

业务程序(拷机前需停止)

```bash

sudo pkill -9 -f QTracker_booster

```

**禁止** `sudo` 运行 `2.sh` / `1.sh`(会找不到 CANN / ascend-dmi 环境)。


3. 高低温拷机流程(标准两步)

详细步骤见 高低温测试操作手册.md(./高低温测试操作手册.md)。

3.1 准备(每次)

```bash

/home/kylin/1.sh fixtime # 系统时间异常(2092 年)时必做

sudo pkill -9 -f QTracker_booster

npu-smi info # 确认 NPU 在线

```

3.2 推荐顺序

| 顺序 | 终端 | 命令 | 作用 |

|------|------|------|------|

| 1 | 终端 2 | `/home/kylin/1.sh` | 开监控(会清理上次拷机残留) |

| 2 | 终端 1 | `/home/kylin/2.sh` 或 `burn 86400 0` | 开始拷机 |

> `1.sh` 启动时会停掉上次 `2.sh` 进程并清空 `npu_stress_suite` 旧报告。

> **不要**在终端 1 已跑 `2.sh` 时再重启终端 2 的 `1.sh`。

3.3 拷机时长

| 时长 | 命令 |

|------|------|

| 1 小时(默认) | `/home/kylin/2.sh` |

| 8 小时 | `/home/kylin/2.sh burn 28800 0` |

| 12 小时 | `/home/kylin/2.sh burn 43200 0` |

| 24 小时 | `/home/kylin/2.sh burn 86400 0` |

| 快速试跑 ~5 分钟 | `/home/kylin/2.sh quick` |

3.4 跑满判定(看 1.sh 或 npu-smi)

| 指标 | 正常拷机 |

|------|----------|

| NPU 即时功耗 | 经常 **≥ 70W**(满负荷约 80~90W) |

| NPU 显存 | **~90%+** |

| CPU 内存(采集) | **~90%+** |

| CPU 占用(采集) | **~80~100%**(需 `stress-ng`) |

| 1.sh 状态行 | **● 拷机进行中** |

仅跑 `1.sh` **不会**加压;`○ 未检测到 2.sh` 且 NPU ~30W 表示没在拷机。

3.5 报告位置

```text

/home/kylin/npu_stress_suite/YYYYMMDD_HHMMSS/

report/summary.txt

report/report.html

csv/metrics.csv

```


4. NVMe 硬盘

4.1 健康与带宽一键体检

```bash

sudo /home/kylin/check_nvme_health.sh # SMART + 顺序读带宽

sudo /home/kylin/check_nvme_health.sh all # 再加顺序写(写到已挂载分区临时文件)

sudo /home/kylin/check_nvme_health.sh health # 仅 SMART

```

输出类似 CrystalDiskInfo 中文摘要 + fio 测速。

4.2 整盘克隆(示例)

```bash

sudo dd if=/dev/nvme0n1 of=/dev/nvme1n1 bs=1M conv=fsync status=progress

```

监控克隆速度(**写扇区看第 10 列**):

```bash

DEV=nvme1n1

prev=(awk -v d="DEV" '3==d{print 10}' /proc/diskstats)

tprev=$(date +%s)

while true; do

sleep 2

read cur tcur <<< "(awk -v d="DEV" -v t=(date +%s) '3==d{print $10, t}' /proc/diskstats)"

awk -v p="prev" -v c="cur" -v tp="tprev" -v tc="tcur" 'BEGIN{

dt=tc-tp; if(dt<=0) exit

printf "已写 %.2f GiB | 当前 %.1f MiB/s\n", c*512/1024/1024/1024, (c-p)*512/1024/1024/dt

}'

prev=cur; tprev=tcur

done

```

4.3 新旧盘区分(经验)

| 盘 | 典型特征 |

|----|----------|

| **nvme0n1** | 有系统分区,SMART 读写各约 1TB 级,Host 读写命令均衡 |

| **nvme1n1** | 克隆目标;dd 前读取极少;克隆中写入量快速上升 |

以 **序列号** 贴机箱标签最可靠。


5. 机箱风扇 PWM(可选)

```bash

驱动开机自启

sudo /home/kylin/hdd3/main/QTracker_booster_G4040/scripts/install_pwm_phytium_pci.sh

高转速 90%(拷机/高温)

sudo /home/kylin/hdd3/main/QTracker_booster_G4040/scripts/apply_pwm_x100.sh high

开机自动高转速(可选)

sudo cp .../scripts/pwm-x100-fan.service /etc/systemd/system/

sudo systemctl enable --now pwm-x100-fan.service

```

厂商参数:`period=50000ns`,高占空 `duty=45000`,低占空 `duty=5000`。详见 PWM风扇驱动说明.md(./PWM风扇驱动说明.md)。


6. 远程与维护

| 方式 | 说明 |

|------|------|

| **SSH** | 拷机维护首选;`kylin@<IP>` |

| **ToDesk** | 设备代码见 `/opt/todesk/config/config.ini` 的 `clientid`;临时密码需 GUI 或预设安全密码 |

| **Cursor 远程** | 会占用一定 CPU,与拷机无直接关系 |

系统时间

屏顶出现 **2092 年** 必须先校时,否则报告目录时间混乱:

```bash

/home/kylin/1.sh fixtime

```


7. 常见问题

CPU 很高但没在拷机

多为 **`kylin-software-properties-service`** 卡死,不是 `1.sh`:

```bash

ps aux --sort=-%cpu | head -8

sudo systemctl stop kylin-software-properties.service

sudo kill -9 <PID>

```

1.sh 显示满格但 NPU 只有 ~30W

  • 在读 **旧 csv** 或 **2092 年僵尸目录**;以 **NPU 功耗(即时)** 为准。

  • 先 `fixtime`,必要时:`rm -rf /home/kylin/npu_stress_suite/2092*`

监控误用

| 错误 | 正确 |

|------|------|

| `1.sh 86400` 当拷机时长 | 刷新间隔默认 2s;24h 拷机用 `2.sh burn 86400 0` |

| `sudo ./1.sh` | `/home/kylin/1.sh` |

| 只跑 1.sh 期望满负载 | 终端 1 必须跑 `2.sh` |

NPU 相关

  • 310P **不支持** `ascend-dmi -p TDP`(0x1e);拷机用 **FP16 循环**。

  • 命令末尾 `0` 是 **芯片 ID**,不是 CPU 核数。

  • 建议:`sudo apt install -y stress-ng`


8. 迁移到其他同配置机器

压缩包示例:`/home/kylin/npu_stress_package_YYYYMMDD.tar.gz`

```bash

tar -xzvf npu_stress_package_*.tar.gz -C /home/kylin/

cp /home/kylin/npu_stress_package/scripts/* /home/kylin/

chmod +x /home/kylin/1.sh /home/kylin/2.sh /home/kylin/check_nvme_health.sh

mkdir -p /home/kylin/npu_stress_suite

```

最少拷贝:`2.sh`、`1.sh`、`npu_stress_mem_worker.py`。目标机需:`npu-smi`、CANN、ascend-dmi、python3(acl)、建议 `stress-ng` / `fio` / `smartmontools`。


9. 文档索引

| 文档 | 内容 |

|------|------|

| 设备说明文档.md(./设备说明文档.md) | 本文:整机与脚本总览 |

| 高低温测试操作手册.md(./高低温测试操作手册.md) | 拷机步骤、判据、故障 |

| PWM风扇驱动说明.md(./PWM风扇驱动说明.md) | PWM 驱动与占空比 |


10. 记录表(现场填写)

| 项目 | 内容 |

|------|------|

| 样机编号 | |

| 设备名 / ToDesk ID | |

| nvme0 序列号 | |

| nvme1 序列号 | |

| 温箱温度 ℃ | |

| 拷机时长 | 1h / 8h / 12h / 24h |

| 开始 / 结束时间 | |

| 结论 | `summary.txt` PASS/FAIL |

| 备注 | |


*文档随现场脚本更新;若路径与压缩包不一致,以 `/home/kylin/` 下实际文件为准。*

复制代码
#!/bin/bash
# NVMe 一键体检:SMART 健康摘要 + 顺序读/写带宽(类似 CrystalDiskInfo + 测速)
#
# 用法:
#   sudo ./check_nvme_health.sh              健康 + 顺序读带宽(默认)
#   sudo ./check_nvme_health.sh all            健康 + 读 + 写(写到各盘分区临时文件)
#   sudo ./check_nvme_health.sh health         仅 SMART 健康
#   sudo ./check_nvme_health.sh bw             仅带宽
#   sudo ./check_nvme_health.sh /dev/nvme0n1   指定盘
#
# 环境变量:
#   BW_SEC=10        每项 fio 测速秒数
#   BW_SIZE=2G       写测试文件大小上限(在分区可用空间内自动缩小)

set -u

MODE=health+bw
BW_SEC="${BW_SEC:-10}"
BW_SIZE="${BW_SIZE:-2G}"
DISKS=()

usage() {
  cat <<EOF
用法: sudo $0 [health|bw|all|help] [设备...]

  (无参数)     SMART 健康 + 顺序读带宽
  all          健康 + 顺序读 + 顺序写(写到该盘已挂载分区的临时文件,安全)
  health       仅 SMART
  bw           仅带宽测试

示例:
  sudo $0
  sudo $0 all /dev/nvme0n1 /dev/nvme1n1
  BW_SEC=15 sudo $0 bw

依赖: smartmontools, fio(读/写带宽);无 fio 时读测速回退 dd。
EOF
}

need_root() {
  if [[ "$(id -u)" -ne 0 ]]; then
    echo "请使用 root: sudo $0 $*" >&2
    exit 1
  fi
}

run_smartctl() {
  smartctl -H -A "$1" 2>/dev/null
}

health_zh() {
  case "$1" in
    PASSED) echo "良好" ;;
    FAILED) echo "故障" ;;
    *)      echo "${1:-未知}" ;;
  esac
}

warn_zh() {
  local w="$1"
  if [[ "$w" == "0x00" || "$w" == "0" ]]; then echo "无"
  elif [[ -z "$w" ]]; then echo "未知"
  else echo "有告警 ($w)"; fi
}

parse_smart_nvme() {
  local _out="$1"
  health="" cw="" temp="" spare="" used=""
  rd="" rd_u="" wr="" wr_u=""
  poh="" pc="" us="" mde="" err="" t1="" t2=""
  while IFS='=' read -r k v; do
    v="${v# }"
    case "$k" in
      health) health="$v" ;; cw) cw="$v" ;; temp) temp="$v" ;;
      spare) spare="$v" ;; used) used="$v" ;;
      rd) rd="$v" ;; rd_u) rd_u="$v" ;; wr) wr="$v" ;; wr_u) wr_u="$v" ;;
      poh) poh="$v" ;; pc) pc="$v" ;; us) us="$v" ;;
      mde) mde="$v" ;; err) err="$v" ;; t1) t1="$v" ;; t2) t2="$v" ;;
    esac
  done < <(echo "$_out" | awk '
    /^SMART overall-health/ { sub(/.*: /,""); health=$0 }
    /^Critical Warning:/              { cw=$NF }
    /^Temperature:/ && !/Sensor/      { temp=$(NF-1) }
    /^Available Spare:/ && !/Threshold/ { spare=$NF; gsub(/%/,"",spare) }
    /^Percentage Used:/               { used=$NF; gsub(/%/,"",used) }
    /^Data Units Read:/                { rd=$(NF-2); rd_u=$(NF-1)" "$NF; gsub(/[\[\]]/,"",rd_u) }
    /^Data Units Written:/             { wr=$(NF-2); wr_u=$(NF-1)" "$NF; gsub(/[\[\]]/,"",wr_u) }
    /^Power On Hours:/                 { poh=$NF }
    /^Power Cycles:/                   { pc=$NF }
    /^Unsafe Shutdowns:/               { us=$NF }
    /^Media and Data Integrity Errors:/ { mde=$NF }
    /^Error Information Log Entries:/   { err=$NF }
    /^Temperature Sensor 1:/           { t1=$(NF-1) }
    /^Temperature Sensor 2:/           { t2=$(NF-1) }
    END {
      print "health=" health; print "cw=" cw; print "temp=" temp
      print "spare=" spare; print "used=" used
      print "rd=" rd; print "rd_u=" rd_u; print "wr=" wr; print "wr_u=" wr_u
      print "poh=" poh; print "pc=" pc; print "us=" us
      print "mde=" mde; print "err=" err; print "t1=" t1; print "t2=" t2
    }')
}

# 从 fio 输出提取 BW=xxxMiB/s
fio_parse_bw() {
  grep -oE 'BW=[0-9.]+[KMG]?i?B/s' | tail -1 | sed 's/BW=//'
}

# 从 dd 输出提取速度
dd_parse_bw() {
  awk '/copied/ {
    for (i=1;i<=NF;i++)
      if ($(i+1) ~ /\/s$/) { print $i" "$(i+1); exit }
  }'
}

# 找该整盘下空间最大的已挂载分区,用于写测试
find_write_target() {
  local dev=$1 base mp avail best_mp="" best_kb=0 kb
  base=$(basename "$dev")
  while read -r mp; do
    [[ -n "$mp" && "$mp" != "[SWAP]" ]] || continue
    avail=$(df -k "$mp" 2>/dev/null | awk 'NR==2{print $4}')
    [[ "$avail" =~ ^[0-9]+$ ]] || continue
    if (( avail > best_kb )); then best_kb=$avail; best_mp=$mp; fi
  done < <(lsblk -ln -o MOUNTPOINT "/dev/${base}"* 2>/dev/null | sort -u)
  if [[ -n "$best_mp" ]]; then
    echo "${best_mp}/.nvme_bw_test_$$.bin"
  fi
}

run_fio_read() {
  local dev=$1
  local out bw
  if ! command -v fio &>/dev/null; then
    echo "  顺序读(dd): 测试中 (${BW_SEC}s 量级)..."
    out=$(dd if="$dev" of=/dev/null bs=1M count=$(( BW_SEC * 512 )) iflag=direct 2>&1) || true
    bw=$(echo "$out" | dd_parse_bw)
    echo "  顺序读: ${bw:-解析失败}"
    return
  fi
  echo "  顺序读(fio): ${BW_SEC}s direct 1M ..."
  out=$(fio --name=seqread --filename="$dev" --rw=read --bs=1M --iodepth=16 \
    --direct=1 --ioengine=libaio --runtime="$BW_SEC" --time_based --group_reporting \
    --output-format=normal 2>&1) || true
  bw=$(echo "$out" | fio_parse_bw)
  [[ -z "$bw" ]] && bw=$(echo "$out" | grep -i 'read.*iops' | tail -1)
  echo "  顺序读: ${bw:-见下方 fio 摘要}"
  echo "$out" | grep -E 'READ:|lat |iops|bw' | tail -3 | sed 's/^/    /'
}

run_fio_write_file() {
  local dev=$1 target size_human out bw
  target=$(find_write_target "$dev")
  if [[ -z "$target" ]]; then
    echo "  顺序写: 跳过(${dev} 无已挂载分区,避免破坏整盘数据)"
    return
  fi
  size_human="$BW_SIZE"
  if ! command -v fio &>/dev/null; then
    echo "  顺序写(dd): -> $target"
    out=$(dd if=/dev/zero of="$target" bs=1M count=512 oflag=direct conv=fsync 2>&1) || true
    rm -f "$target"
    bw=$(echo "$out" | dd_parse_bw)
    echo "  顺序写: ${bw:-解析失败}"
    return
  fi
  echo "  顺序写(fio): ${BW_SEC}s -> $target"
  out=$(fio --name=seqwrite --filename="$target" --rw=write --bs=1M --iodepth=8 \
    --direct=1 --ioengine=libaio --runtime="$BW_SEC" --time_based --group_reporting \
    --size="$size_human" --output-format=normal 2>&1) || true
  rm -f "$target"
  bw=$(echo "$out" | fio_parse_bw)
  echo "  顺序写: ${bw:-见下方 fio 摘要}"
  echo "$out" | grep -E 'WRITE:|lat |iops|bw' | tail -3 | sed 's/^/    /'
}

section_health() {
  local dev out model serial fw hzh wzh mark tip
  need_root
  if ! command -v smartctl &>/dev/null; then
    echo "未安装 smartctl: apt install -y smartmontools" >&2
    return 1
  fi
  local now_str
  now_str=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "未知")
  echo "════════════════════════════════════════════════════════"
  echo "  NVMe SMART 健康摘要  $now_str"
  if [[ "$now_str" =~ ^209[0-9] ]]; then
    echo "  ⚠ 系统时间异常,请先: /home/kylin/1.sh fixtime"
  fi
  echo "════════════════════════════════════════════════════════"

  for dev in "${DISKS[@]}"; do
    [[ -b "$dev" ]] || { echo ""; echo "[跳过] $dev"; continue; }
    out=$(run_smartctl "$dev") || { echo ""; echo "【$dev】SMART 读取失败"; continue; }
    if ! echo "$out" | grep -q 'SMART/Health Information'; then
      echo ""; echo "【$dev】非 NVMe SMART"; continue
    fi
    parse_smart_nvme "$out"
    model=$(cat "/sys/block/$(basename "$dev")/device/model" 2>/dev/null | xargs || echo "?")
    serial=$(cat "/sys/block/$(basename "$dev")/device/serial" 2>/dev/null | xargs || echo "?")
    fw=$(cat "/sys/block/$(basename "$dev")/device/firmware_rev" 2>/dev/null | xargs || echo "?")
    hzh=$(health_zh "$health")
    wzh=$(warn_zh "$cw")
    if [[ "$health" == "PASSED" && "${mde:-}" == "0" && "${err:-}" == "0" ]]; then mark="✓"; else mark="!"; fi
    echo ""
    echo "┌─ $dev  $mark"
    echo "│  型号/序列 : $model  ($serial)"
    echo "│  固件      : $fw"
    echo "│  健康状态  : $hzh  (SMART: ${health:-?})"
    echo "│  关键告警  : $wzh"
    echo "│  温度      : ${temp:-?}°C  (传感器 ${t1:-?} / ${t2:-?}°C)"
    echo "│  备用块    : ${spare:-?}%   已磨损: ${used:-?}%"
    echo "│  累计读/写 : ${rd:-?} ${rd_u:-}  |  ${wr:-?} ${wr_u:-}"
    echo "│  通电/上电 : ${poh:-?}h / ${pc:-?}次   异常断电: ${us:-?}次"
    echo "│  介质错误  : ${mde:-?}   错误日志: ${err:-?}条"
    echo "└────────────────────────────────────────"
    if [[ "$health" == "PASSED" ]]; then
      tip="状态正常。"
    elif [[ -n "$health" ]]; then
      tip="建议备份并更换。"
    else
      tip="SMART 解析失败。"
    fi
    [[ -n "${us:-}" && "$us" -gt 5 ]] && tip="${tip} 异常断电偏多。"
    echo "   → $tip"
  done
  echo ""
}

section_bandwidth() {
  need_root
  echo "════════════════════════════════════════════════════════"
  echo "  NVMe 顺序带宽测试  (每盘约 ${BW_SEC}s,direct I/O)"
  echo "════════════════════════════════════════════════════════"
  local do_write=0
  [[ "$MODE" == *all* || "$MODE" == *write* ]] && do_write=1

  for dev in "${DISKS[@]}"; do
    [[ -b "$dev" ]] || continue
    echo ""
    echo "【$dev】 $(cat /sys/block/$(basename "$dev")/device/model 2>/dev/null | xargs)"
    run_fio_read "$dev"
    if (( do_write )); then
      run_fio_write_file "$dev"
    fi
  done

  if (( ! do_write )); then
    echo ""
    echo "  提示: 加写带宽测试请用: sudo $0 all"
  fi
  echo ""
  echo "════════════════════════════════════════════════════════"
}

# ── 参数解析 ──
args=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    help|-h|--help) usage; exit 0 ;;
    health) MODE=health; shift ;;
    bw|bandwidth) MODE=bw; shift ;;
    all) MODE=all; shift ;;
    /dev/*) DISKS+=("$1"); shift ;;
    *) echo "未知参数: $1 (用 help)" >&2; exit 1 ;;
  esac
done

if [[ ${#DISKS[@]} -eq 0 ]]; then
  mapfile -t DISKS < <(ls -1 /dev/nvme*n1 2>/dev/null | sort -V)
fi
[[ ${#DISKS[@]} -gt 0 ]] || { echo "未找到 /dev/nvme*n1" >&2; exit 1; }

case "$MODE" in
  health)   section_health ;;
  bw)       section_bandwidth ;;
  all)      section_health; section_bandwidth ;;
  health+bw|*) section_health; section_bandwidth ;;
esac
相关推荐
HLC++5 小时前
Linux文件操作
linux·运维·服务器
InfraSense5 小时前
多门店运维闭环全景架构:监控+告警+工单+SLA+复盘,一套最小可用系统怎么串起来
运维·msp
Sirius Wu5 小时前
当前主流 RAG 架构全景及轻量级向量库选型深度分析
运维·人工智能·架构·aigc
晚风予卿云月5 小时前
【Linux】进程控制(二)——进程等待 全方位详解
linux·运维·服务器·进程控制·进程等待
上天_去_做颗惺星 EVE_BLUE5 小时前
【新 Linux 服务器上手全攻略】系统巡检、存储规划与开发环境初始化
linux·运维·服务器·ubuntu·macos·centos
团象科技5 小时前
出海内容创作链路实地调研 关于GPU服务器视频渲染的落地观察
运维·服务器
c238565 小时前
linux文件权限深入了解(下)
linux·运维·服务器
Zh&&Li5 小时前
保姆级安装AI全自动渗透工具(pentestswarm)
linux·运维·服务器·人工智能
木雷坞5 小时前
Playwright MCP Docker 部署:mcr 镜像、浏览器工具和权限配置
运维·docker·容器·mcp