ubuntu下测试nvme带宽和健康度
最少拷贝:`2.sh`、`1.sh`、`npu_stress_mem_worker.py`。目标机需:`npu-smi`、CANN、ascend-dmi、python3(acl)、建议 `stress-ng` / `fio` / `smartmontools`。| **ToDesk** | 设备代码见 `/opt/todesk/config/config.ini` 的 `clientid`;| 2
# 拷机样机设备说明文档
> 飞腾 **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
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)