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
相关推荐
KIDULT°6 小时前
MySQL系列 (1):数据库原理
运维·数据库·mysql·云计算
wewowilling8 小时前
认识微服务
java·运维·微服务·springcloud
玖石书9 小时前
Ubuntu 24.04 UFW 全命令攻略:从入门到端口开放实战
linux·运维·ubuntu
weifont10 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
拂拉氏11 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
Mikko711 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
光依旧12 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
yichengerp13 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦13 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
信可维14 小时前
自动化操作多平台账号:登录态、双会话与 User Data 的 3 个坑
运维·自动化