上一篇总结了SONiC Switch Telemetry的方法:Sonic Telemetry Deployment,由于测试RDMA时不仅需要监控交换机,还要监控服务器,因此此篇就是在原有架构基础之上融入Server测的相关监控。Server测主要通过node exporter采集本地信息,然后吐给Prometheus,本地信息主要包含如下信息:
- 支持RDMA相关的各种硬件Counter信息(CNP/ECN、重传、QP 状态等),专有计数
- 支持Ethtools中Per-Priority相关的counter,这个是通用计数
另外更新之前自动生成配置的shell脚本,覆盖服务器这边的配置。
架构概览
整体架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ Server Telemetry │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ auto-refresh.sh v3 │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ switches.conf│ │ servers.conf │ ← new │ │
│ │ │ switch list │ │ server list │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ prometheus.yml (auto generated) │ │ │
│ │ │ ┌────────────────────┬────────────────────────────┐ │ │ │
│ │ │ │ job: gnmic │ job: rdma_servers │ │ │ │
│ │ │ │ (switch metric) │ (server RDMA metric) ← new │ │ │ │
│ │ │ └────────────────────┴────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ gNMIc │ │ Prometheus │ │ Grafana │ │
│ │ :9804 │→ │ :9090 │→ │ :3000 │ │
│ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │
└─────────┼────────────────┼──────────────────────────────────────────────────┘
│ gNMI │ HTTP scrape
▼ ▼
┌─────────────────┐ ┌───────────┐
│ SONiC Switch │ │RDMA Server│
│ :8080 (gNMI) │ │┌─────────┐│
└─────────────────┘ ││ node_ ││
││ exporter││
││ :9100 ││
│└────┬────┘│
│ │ │
│┌────▼────┐│
││ textfile││
││ rdma. ││
││ prom ││
│└────┬────┘│
│ │ │
│┌────▼────┐│
││ rdma_ ││
││collector││
││ (1s) ││
│└─────────┘│
│ │ │
│┌────▼────┐│
││ mlx5_0 ││
││ mlx5_1 ││
││ (NIC) ││
│└─────────┘│
└───────────┘
Server架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ RDMA Server │
│ ┌─────────────────────┐ ┌─────────────────────────────────────────────┐ │
│ │ Source Data1: RDMA │ │ node_exporter (:9100) │ │
│ │ │ │ ┌─────────────────────────────────────┐ │ │
│ │ /sys/class/ │ │ │ textfile collector │ │ │
│ │ infiniband/ │───▶│ │ /var/lib/node_exporter/textfile/ │ │ │
│ │ mlx5_x/ports/1/ │ │ │ └── rdma.prom │ │ │
│ │ ├── counters/ │ │ └─────────────────────────────────────┘ │ │
│ │ └── hw_counters/ │ │ │
│ │ │ └───▲──────────────┬──────────────────────────┘ │
│ │ rdma res show qp │ │ │ │
│ └─────────────────────┘ │ │ HTTP /metrics │
│ ┌─────────────────────┐ │ │ │
│ │ Source Data2: ethtool │ │ │
│ │ │────────┘ │ │
│ │ ethtool -S {iface} │ │ │
│ └─────────────────────┘ │ │
│ ┌─────────────────────┐ │ │
│ │ rdma_collector.sh │──────────────────────▶│ │
│ │ (systemd timer 1s) │ write to .prom │ │
│ └─────────────────────┘ │ │
└────────────────────────────────────────────────┼────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Prometheus Server │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ prometheus.yml │ │
│ │ - job_name: 'rdma_servers' │ │
│ │ scrape_interval: 1s │ │
│ │ static_configs: │ │
│ │ - targets: ['10.26.4.91:9100', '10.26.4.92:9100', ...] │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Prometheus TSDB │ │
│ │ - Store all metrics │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────┬────────────────────────────┘
│
▼ PromQL Search
┌─────────────────────────────────────────────────────────────────────────────┐
│ Grafana │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Server RDMA Monitor Dashboard │ │
│ │ - var: $server, $device │ │
│ │ - pannel: QP, CNP, ECN, Retran, Traffics... │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
数据源与指标
指标分类
| 指标类型 | 数据源 | Prometheus 指标名 | 说明 |
| 端口计数器 | /sys/class/infiniband/{dev}/ports/{port}/counters/ | rdma_port_counter | TX/RX 字节、包数、错误 |
| 硬件计数器 | /sys/class/infiniband/{dev}/ports/{port}/hw_counters/ | rdma_hw_counter | CNP、ECN、重传等 |
| QP 统计 | rdma res show qp | rdma_qp_count | 活跃 QP 数量 |
| Priority 流量 | ethtool -S {iface} | nic_prio_* | 按优先级统计的流量 |
| PFC 统计 | ethtool -S {iface} | nic_pfc_* | 按优先级统计PFC |
关键硬件计数器-拥塞相关
| counter 名称 | 含义 | 增长原因 |
| np_cnp_sent | 本机作为 NP 发出的 CNP 包数 | 收到 ECN 标记的数据包 |
| np_ecn_marked_roce_packets | 收到的被 ECN 标记的 RoCE 包数 | 路径上有交换机拥塞 |
| rp_cnp_handled | 本机作为 RP 收到并处理的 CNP 包数 | 本机发送导致拥塞,收到 CNP 后降速 |
| rp_cnp_ignored | 收到但忽略的 CNP 包数 | QP 不存在或 CNP 格式错误 |
关键硬件计数器-重传相关
| counter 名称 | 含义 | 增长原因 |
| roce_adp_retrans | RoCE 自适应重传次数 | 检测到丢包 |
| roce_adp_retrans_to | 重传超时次数 | ACK 超时,严重问题 |
| roce_slow_restart | 慢启动重传次数 | 连续多次重传失败 |
关键硬件计数器-乱序与错误
| counter 名称 | 含义 | 增长原因 |
| out_of_sequence | 收到的乱序包数量 | ECMP 多路径或路由问题 |
| packet_seq_err | 包序列号错误 | 丢包后收到后续包 |
| out_of_buffer | 接收缓冲区溢出 | 接收端处理不过来(严重),RQ WQE 耗尽,应用层丢包 |
| rnr_nak_retry_err | RNR NAK 重试错误 | 接收端没有准备好 WQE |
| local_ack_timeout_err | 本地 ACK 超时 | 对端无响应或链路故障 |
| duplicate_request | 收到的重复请求 | ACK 丢失导致重传 |
注:下面是乱序错误Counter产生的顺序展示,packet_seq_err 就是 PSN NAK 导致的结果,只是计数器名字不叫 NAK:
接收端检测乱序 → out_of_sequence++
↓
发送 PSN NAK
↓
发送端收到 NAK → packet_seq_err++
↓
触发重传 → roce_adp_retrans++
Per-Priority 流量
| 指标名 | 增长原因 |
| nic_prio_rx_bytes{interface, priority} | 按优先级接收字节数 |
| nic_prio_tx_bytes{interface, priority} | 按优先级发送字节数 |
| nic_prio_rx_packets{interface, priority} | 按优先级接收包数 |
| nic_prio_tx_packets{interface, priority} | 按优先级发送包数 |
Per-Priority PFC
| 指标名 | 增长原因 |
| nic_pfc_rx_pause{interface, priority} | 按优先级接收PFC |
| nic_pfc_rx_pause{interface, priority} | 按优先级发送PFC |
| nic_pfc_rx_pause_duration{interface, priority} | 按优先级接收PFC pause duration |
| nic_pfc_tx_pause_duration{interface, priority} | 按优先级发送PFC pause duration |
PFC pause和pause duration单位和量级不一样,不建议在grafana中展示在一个pannel中。另外服务器主动发送PFC的场景如下:
- PCIe 瓶颈(NUMA 不对齐)
- QP/应用层 post WQE 速度不足
- IRQ 处理不及时
Discards
| 指标名 | 增长原因 |
| nic_prio_rx_discards{interface, priority} | 按优先级统计RX Discards,per-priority reserved buffer 耗尽后的丢包,是 HRM 被打穿的体现 |
| nic_prio_rx_buf_discard{interface, priority} | 按优先级统计RX Buf Discards,per-priority shared buffer 耗尽后的丢包 |
| nic_rx_discards_phy{interface} | 按端口统计FEC 后 RX Discards |
| nic_tx_discards_phy{interface} | 按端口统计TX Buffer Overload导致的丢包 |
QP 统计 (rdma_qp_count)
| 标签 | 说明 |
| device | RDMA 设备名 (mlx5_0, mlx5_1) |
| state | QP 状态 (RTS, RTR, INIT, ERR, total) |
QP 状态说明:
- RTS (Ready To Send): 正常工作状态,可以收发数据
- RTR (Ready To Receive): 只能接收
- INIT: 初始化中
- ERR: 错误状态
- total: 总数
服务器端部署
安装Node Exporter
cd /opt
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xvf node_exporter-1.7.0.linux-amd64.tar.gz
mv node_exporter-1.7.0.linux-amd64 node_exporter
mkdir -p /var/lib/node_exporter/textfile
创建采集脚本
cat > /opt/node_exporter/rdma_collector.sh << 'EOF'
#!/bin/bash
OUTPUT="/var/lib/node_exporter/textfile/rdma.prom"
TEMP="${OUTPUT}.tmp"
{
# ============================================================
# RDMA Port Counters
# ============================================================
echo "# HELP rdma_port_counter RDMA port counter"
echo "# TYPE rdma_port_counter counter"
echo "# HELP rdma_hw_counter RDMA hardware counter"
echo "# TYPE rdma_hw_counter counter"
for dev in /sys/class/infiniband/*; do
[ -d "$dev" ] || continue
devname=$(basename "$dev")
for port in "$dev"/ports/*; do
[ -d "$port" ] || continue
portnum=$(basename "$port")
# 批量读取 counters
for counter in "$port"/counters/*; do
[ -f "$counter" ] || continue
name=$(basename "$counter")
value=$(cat "$counter" 2>/dev/null)
[[ "$value" =~ ^[0-9]+$ ]] && echo "rdma_port_counter{device=\"$devname\",port=\"$portnum\",counter=\"$name\"} $value"
done
# 批量读取 hw_counters
for counter in "$port"/hw_counters/*; do
[ -f "$counter" ] || continue
name=$(basename "$counter")
value=$(cat "$counter" 2>/dev/null)
[[ "$value" =~ ^[0-9]+$ ]] && echo "rdma_hw_counter{device=\"$devname\",port=\"$portnum\",counter=\"$name\"} $value"
done
done
done
# ============================================================
# QP Count
# ============================================================
echo ""
echo "# HELP rdma_qp_count Number of Queue Pairs by state"
echo "# TYPE rdma_qp_count gauge"
rdma res show qp 2>/dev/null | awk '
{
dev=""; state=""
for(i=1; i<=NF; i++) {
if($i == "link") { split($(i+1), parts, "/"); dev=parts[1] }
if($i == "state") state=$(i+1)
}
if(dev != "" && state != "") { count[dev","state]++; total[dev]++ }
}
END {
for(k in count) { split(k, a, ","); print "rdma_qp_count{device=\""a[1]"\",state=\""a[2]"\"} "count[k] }
for(d in total) { print "rdma_qp_count{device=\""d"\",state=\"total\"} "total[d] }
}'
# ============================================================
# Per-Priority: Traffic + PFC + Discards (每网口只调用一次 ethtool)
# ============================================================
echo ""
echo "# HELP nic_prio_rx_bytes Received bytes by priority"
echo "# TYPE nic_prio_rx_bytes counter"
echo "# HELP nic_prio_tx_bytes Transmitted bytes by priority"
echo "# TYPE nic_prio_tx_bytes counter"
echo "# HELP nic_prio_rx_packets Received packets by priority"
echo "# TYPE nic_prio_rx_packets counter"
echo "# HELP nic_prio_tx_packets Transmitted packets by priority"
echo "# TYPE nic_prio_tx_packets counter"
echo "# HELP nic_pfc_rx_pause PFC pause frames received by priority"
echo "# TYPE nic_pfc_rx_pause counter"
echo "# HELP nic_pfc_tx_pause PFC pause frames transmitted by priority"
echo "# TYPE nic_pfc_tx_pause counter"
echo "# HELP nic_pfc_rx_pause_duration PFC pause duration received by priority (512-bit time quanta)"
echo "# TYPE nic_pfc_rx_pause_duration counter"
echo "# HELP nic_pfc_tx_pause_duration PFC pause duration transmitted by priority (512-bit time quanta)"
echo "# TYPE nic_pfc_tx_pause_duration counter"
echo "# HELP nic_prio_rx_discards RX discards by priority"
echo "# TYPE nic_prio_rx_discards counter"
echo "# HELP nic_prio_rx_buf_discard RX buffer discards by priority"
echo "# TYPE nic_prio_rx_buf_discard counter"
echo "# HELP nic_rx_discards_phy RX physical layer discards (total) after FEC"
echo "# TYPE nic_rx_discards_phy counter"
echo "# HELP nic_tx_discards_phy TX buffer overload"
echo "# TYPE nic_tx_discards_phy counter"
for iface in $(ls /sys/class/net/ | grep -E '^(ens|eth|enp)'); do
ethtool -S "$iface" 2>/dev/null | awk -v dev="$iface" '
/rx_prio[0-7]_bytes:/ { print "nic_prio_rx_bytes{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/tx_prio[0-7]_bytes:/ { print "nic_prio_tx_bytes{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_prio[0-7]_packets:/ { print "nic_prio_rx_packets{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/tx_prio[0-7]_packets:/ { print "nic_prio_tx_packets{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_prio[0-7]_pause:/ { print "nic_pfc_rx_pause{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/tx_prio[0-7]_pause:/ { print "nic_pfc_tx_pause{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_prio[0-7]_pause_duration:/ { print "nic_pfc_rx_pause_duration{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/tx_prio[0-7]_pause_duration:/ { print "nic_pfc_tx_pause_duration{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_prio[0-7]_discards:/ { print "nic_prio_rx_discards{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_prio[0-7]_buf_discard:/ { print "nic_prio_rx_buf_discard{interface=\""dev"\",priority=\""substr($1,8,1)"\"} "$2 }
/rx_discards_phy:/ { print "nic_rx_discards_phy{interface=\""dev"\"} "$2 }
/tx_discards_phy:/ { print "nic_tx_discards_phy{interface=\""dev"\"} "$2 }
'
done
} > "$TEMP" && mv "$TEMP" "$OUTPUT"
EOF
chmod +x /opt/node_exporter/rdma_collector.sh
创建 systemd 服务
node_exporter.service:
cat > /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Node Exporter
After=network.target
[Service]
Type=simple
ExecStart=/opt/node_exporter/node_exporter \
--collector.textfile.directory=/var/lib/node_exporter/textfile \
--web.listen-address=:9100
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
rdma-collector.service:
cat > /etc/systemd/system/rdma-collector.service << 'EOF'
[Unit]
Description=RDMA Metrics Collector
[Service]
Type=oneshot
ExecStart=/opt/node_exporter/rdma_collector.sh
EOF
rdma-collector.timer:
cat > /etc/systemd/system/rdma-collector.timer << 'EOF'
[Unit]
Description=Run RDMA Collector every second
[Timer]
OnBootSec=5
OnUnitActiveSec=1s
AccuracySec=100ms
[Install]
WantedBy=timers.target
EOF
启动服务
systemctl daemon-reload
systemctl enable --now node_exporter
systemctl enable --now rdma-collector.timer
验证
# 检查采集时间(应 < 0.3s)
time /opt/node_exporter/rdma_collector.sh
# 检查数据
cat /var/lib/node_exporter/textfile/rdma.prom | head -30
# 检查 node_exporter 暴露的指标
curl -s http://localhost:9100/metrics | grep -E "^(rdma_|nic_prio)" | head -20
# 检查服务状态
systemctl status node_exporter
systemctl status rdma-collector.timer
Prometheus 配置
在 prometheus.yml 中添加
scrape_configs:
- job_name: 'rdma_servers'
scrape_interval: 1s
static_configs:
- targets:
- '10.26.4.91:9100'
- '10.26.4.92:9100'
- '10.26.4.93:9100'
# ... 添加更多服务器
labels:
group: 'rdma_servers'
relabel_configs: # ← 追加在这里,过滤掉:9100
- source_labels: [__address__]
regex: '(.*):9100'
target_label: instance
replacement: '$1'
重启 Prometheus并验证
docker compose restart prometheus
# 或
systemctl restart prometheus
# 检查 target 状态
curl -s "http://localhost:9090/api/v1/targets" | jq '.data.activeTargets[] | select(.job=="rdma_servers") | {instance, health}'
# 测试查询
curl -s "http://localhost:9090/api/v1/query?query=rdma_qp_count" | jq '.data.result[0]'
Grafana Dashboard
Dashboard 变量
| 变量名 | 标签 | 作用 | 影响的面板 |
| $server | Server | 过滤服务器 | 所有面板 |
| $device | RDMA Device | 过滤 mlx5_0/mlx5_1 | CNP/ECN/Retrans/QP |
| $interface | NIC Interface | 过滤网卡 | Per-Priority 流量 |
| $priority | Priority | 过滤优先级 0-7 | Per-Priority 流量 |
面板布局
┌─────────────────────────────────────────────────────────────────────┐
│ Row 1: QP Overview │
│ ┌──────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ Active QPs (Table) │ │ QP Count Over Time (TimeSeries) │ │
│ │ - Server │ │ │ │
│ │ - Device │ │ │ │
│ │ - QP Count │ │ │ │
│ └──────────────────────┘ └──────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Row 2: Per-Priority traffics (control by $interface, $priority) │
│ ┌────────────────────────────┐ ┌────────────────────────────────┐ │
│ │ Per-Priority RX Traffic │ │ Per-Priority TX Traffic │ │
│ │ (Gbps) │ │ (Gbps) │ │
│ └────────────────────────────┘ └────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Row 3: CNP │
│ ┌────────────────────────────┐ ┌────────────────────────────────┐ │
│ │ CNP Sent (NP → Switch) │ │ CNP Received (RP ← Switch) │ │
│ └────────────────────────────┘ └────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Row 4: ECN & Retran │
│ ┌────────────────────────────┐ ┌────────────────────────────────┐ │
│ │ ECN Marked Packets │ │ Retransmissions │ │
│ └────────────────────────────┘ └────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Row 5: Out of Sequence & NAK │
│ ┌────────────────────────────┐ ┌────────────────────────────────┐ │
│ │ Out of Sequence / Errors │ │ NAK Errors │ │
│ └────────────────────────────┘ └────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Row 6: Port Errors & CQE │
│ ┌────────────────────────────┐ ┌────────────────────────────────┐ │
│ │ RDMA Port Errors │ │ CQE Errors │ │
│ └────────────────────────────┘ └────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
诊断命令
# 检查 RDMA 设备
ibstat
# 检查 QP
rdma res show qp | head -10
# 手动读取计数器
cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_cnp_sent
# 检查 per-priority 统计
ethtool -S ens3np0 | grep prio
# 检查 node_exporter 指标
curl -s http://localhost:9100/metrics | grep rdma_hw_counter | head -10
# 检查 Prometheus 数据
curl -s "http://<prometheus>:9090/api/v1/query?query=rdma_qp_count" | jq
文件清单
| 文件 | 位置 | 说明 |
| rdma_collector.sh | /opt/node_exporter/ | 采集脚本 |
| rdma.prom | /var/lib/node_exporter/textfile/ | 采集数据(自动生成) |
| node_exporter.service | /etc/systemd/system/ | node_exporter 服务 |
| rdma-collector.service | /etc/systemd/system/ | 采集服务(oneshot) |
| rdma-collector.timer | /etc/systemd/system/ | 定时器(1秒间隔) |
注意:
- 如需添加其他指标,编辑
/opt/node_exporter/rdma_collector.sh,在末尾添加新的采集逻辑;
- 添加完后,需要使用
time /opt/node_exporter/rdma_collector.sh 确认下执行时间,如果执行时间超过1s,那么更新频率就需要变更,否则会失败;