feat(dsc): Add the SerDes DSC scan and its offline CSV parser
bcm_dsc_scan.sh runs
bcmcmd -n <unit> -c "dsh -c 'phydiag <port> dsc'"
over the whole port list (one 100G port plus the 200G ranges, ~445 in
total) on both units, writes the raw log under <script_dir>/log, and
copies it to /host/hw-eval/jobs. -f names the log (%u -> u0/u1, %t ->
timestamp), -F overwrites, -u picks one unit, -n dry runs, -C skips the
copy.
Two things it deliberately does not do:
- judge success by bcmcmd's exit code. The diag shell rejects commands
and bcmcmd still exits 0, so the script counts
"SERDES DISPLAY DIAG DATA END" markers instead. Same trap
blanton_traffic_linespeed.sh hit.
- resolve ~. Script A calls it after sudo -i, so paths are derived
from $0 rather than $HOME.
Output is one line per port when stdout is not a tty, so it does not
flood the Tera Term log, and it ends with "### DSC_SCAN_DONE rc=<n>" for
a TTL wait to key on.
bcm_dsc_parse.sh is the host-side half: raw log -> <prefix>_lane.csv
(TXEQ / RXFFE / DFE / EYE / SNR per lane) and <prefix>_pm.csv (per
pm_id). POSIX sh + awk, no Python. Lane rows are read by the order of
their bracket groups rather than by fixed columns, so the SDK re-spacing
its output does not break parsing; a row whose group count is not 9 is
reported as UNPARSED on stderr instead of being parsed wrongly.
A full scan is about 10 minutes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
This commit is contained in:
@@ -0,0 +1,204 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# =============================================================================
|
||||||
|
# bcm_dsc_parse_V1.0.0.sh
|
||||||
|
#
|
||||||
|
# Author : ETWen
|
||||||
|
# Date : 20260906
|
||||||
|
#
|
||||||
|
# Version History
|
||||||
|
# V1.0.0 20260906 Initial Version
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Parse Broadcom diag shell "phydiag <pm_id> dsc" raw log into CSV.
|
||||||
|
# 支援多個 pm_id 串接在同一份 log,以 "pm_id =" 當 record separator。
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
# <prefix>_lane.csv : 每個 lane 一列(TXEQ / RXFFE / DFE / EYE / SNR ...)
|
||||||
|
# <prefix>_pm.csv : 每個 pm_id 一列(溫度 / PLL / lane 數 / SNR-EYE 統計)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./bcm_dsc_parse_V1.0.0.sh -i dsc_raw.log [-o out_prefix]
|
||||||
|
#
|
||||||
|
# Design notes:
|
||||||
|
# - 純 POSIX sh + awk(mawk 相容),不依賴 Python。
|
||||||
|
# - Lane row 採「括號群組出現順序」抽取,而非固定欄位位置,對排版空白
|
||||||
|
# 變動免疫。SDK 若增減欄位,群組數不等於 9,該列會被標記 UNPARSED
|
||||||
|
# 並輸出到 stderr,不會靜默解析錯誤。
|
||||||
|
# - 已知 header 名稱數與資料欄數不一致之處:
|
||||||
|
# * (P RX, CDRxN, UC_CFG, UC_STS, RST, STP) 6 個名字 / 7 個值
|
||||||
|
# (RST 實際是 "0,0" 兩欄)
|
||||||
|
# * TXEQ 尾端多一個逗號
|
||||||
|
# * AVG_TMON 顯示為 "(11) 66C" 兩個 token
|
||||||
|
# - SD / LCK 的 "*" 為 sticky 旗標,本版拆成獨立欄位保留,不做判讀。
|
||||||
|
# - BER 欄若為 "!chk_en" 表示 checker 未開啟,值無效,原樣保留。
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IN=""
|
||||||
|
PREFIX="dsc"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 -i <raw_log> [-o <out_prefix>]" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts "i:o:h" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
i) IN="$OPTARG" ;;
|
||||||
|
o) PREFIX="$OPTARG" ;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$IN" ] || usage
|
||||||
|
[ -f "$IN" ] || { echo "ERROR: input not found: $IN" >&2; exit 2; }
|
||||||
|
|
||||||
|
LANE_CSV="${PREFIX}_lane.csv"
|
||||||
|
PM_CSV="${PREFIX}_pm.csv"
|
||||||
|
|
||||||
|
# 先去掉 CR(console / telnet log 常帶),再交給 awk
|
||||||
|
tr -d '\r' < "$IN" | awk -v LANE_CSV="$LANE_CSV" -v PM_CSV="$PM_CSV" '
|
||||||
|
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
||||||
|
|
||||||
|
# 掃描一列,取出所有頂層括號群組到 G[],群組前的英數 prefix 到 P[],
|
||||||
|
# 其餘純量文字留在 REST。回傳群組數。
|
||||||
|
function split_groups(line, i, ch, d, buf, ng, rest, prefix) {
|
||||||
|
split("", G, ":"); split("", P, ":")
|
||||||
|
ng = 0; d = 0; buf = ""; rest = ""; prefix = ""
|
||||||
|
for (i = 1; i <= length(line); i++) {
|
||||||
|
ch = substr(line, i, 1)
|
||||||
|
if (ch == "(") {
|
||||||
|
d++
|
||||||
|
if (d == 1) {
|
||||||
|
buf = ""; prefix = ""
|
||||||
|
if (match(rest, /[A-Za-z_]+$/)) {
|
||||||
|
prefix = substr(rest, RSTART, RLENGTH)
|
||||||
|
rest = substr(rest, 1, RSTART - 1)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch == ")") {
|
||||||
|
d--
|
||||||
|
if (d == 0) { ng++; G[ng] = buf; P[ng] = prefix; rest = rest " "; continue }
|
||||||
|
}
|
||||||
|
if (d > 0) { buf = buf ch; continue }
|
||||||
|
rest = rest ch
|
||||||
|
}
|
||||||
|
REST = rest
|
||||||
|
return ng
|
||||||
|
}
|
||||||
|
|
||||||
|
# 取括號群組的第 n 個子欄位(以逗號分隔)
|
||||||
|
function gf(idx, n, a, c) {
|
||||||
|
c = split(G[idx], a, ",")
|
||||||
|
if (n > c) return ""
|
||||||
|
return trim(a[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_num(s) { return (s ~ /^[+-]?[0-9]+(\.[0-9]+)?$/) }
|
||||||
|
|
||||||
|
function reset_pm() {
|
||||||
|
core = ""; sdtype = ""; hw_lanes = ""; lane_sel = ""
|
||||||
|
live_temp = ""; avg_tmon = ""; pll_lock = ""
|
||||||
|
n_lane = 0; snr_min = ""; snr_max = ""; eye_min = ""; all_lck = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function flush_pm() {
|
||||||
|
if (pm == "") return
|
||||||
|
printf "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
|
||||||
|
pm, core, sdtype, hw_lanes, lane_sel, n_lane,
|
||||||
|
live_temp, avg_tmon, pll_lock,
|
||||||
|
(snr_min == "" ? "NA" : snr_min),
|
||||||
|
(eye_min == "" ? "NA" : eye_min),
|
||||||
|
(n_lane == 0 ? "NA" : (all_lck ? "PASS" : "FAIL")) > PM_CSV
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
pm = ""; in_lane = 0; core_row = 0; bad = 0
|
||||||
|
print "pm_id,core,lane,serdes_type,sd,sd_sticky,lck,lck_sticky,rxppm,txppm," \
|
||||||
|
"txeq_n3,txeq_n2,txeq_n1,txeq_m,txeq_p1,txeq_p2,txeq_sum," \
|
||||||
|
"rxffe_n3,rxffe_n2,rxffe_n1,rxffe_m,rxffe_p1,rxffe_p2," \
|
||||||
|
"dfe1,dfe2,pf_m,pf_l,pf_h,vga,dco,tp0,tp1,tp2,flt_m,flt_s," \
|
||||||
|
"nlc_u,nlc_l,eye_u,eye_m,eye_l,link_time,snr,ber," \
|
||||||
|
"live_temp,avg_tmon,pll_lock,rx_pol,cdr,uc_cfg,uc_sts,rst,stp" > LANE_CSV
|
||||||
|
print "pm_id,core,serdes_type,hw_lanes,lane_select,active_lanes," \
|
||||||
|
"live_temp,avg_tmon,pll_lock,snr_min,eye_min,lck_all" > PM_CSV
|
||||||
|
}
|
||||||
|
|
||||||
|
/^pm_id[ \t]*=/ { flush_pm(); pm = $NF; reset_pm(); in_lane = 0; next }
|
||||||
|
/^Core[ \t]*=/ { if (match($0, /Core[ \t]*=[ \t]*[0-9]+/)) { core = $0; sub(/.*Core[ \t]*=[ \t]*/, "", core); sub(/[^0-9].*/, "", core) } next }
|
||||||
|
/^SerDes type[ \t]*=/ { sdtype = $NF; next }
|
||||||
|
/^Rev ID # Lanes/ { hw_lanes = $NF; next }
|
||||||
|
/^lane_select[ \t]*=/ { lane_sel = $NF; next }
|
||||||
|
|
||||||
|
/^CORE[ \t]+RST_ST/ { core_row = 1; next }
|
||||||
|
core_row == 1 {
|
||||||
|
line = $0
|
||||||
|
gsub(/\([^)]*\)[ \t]*/, "", line) # 去掉 AVG_TMON 的 "(11)"
|
||||||
|
n = split(trim(line), c, /[ \t]+/)
|
||||||
|
if (n >= 15) { live_temp = c[9]; avg_tmon = c[10]; pll_lock = c[15] }
|
||||||
|
core_row = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
/^LN[ \t]*\(/ { in_lane = 1; next }
|
||||||
|
/SERDES DISPLAY DIAG DATA END/ { in_lane = 0; next }
|
||||||
|
|
||||||
|
in_lane && /^[0-9]+[ \t]*\(/ {
|
||||||
|
ng = split_groups($0)
|
||||||
|
if (ng != 9) {
|
||||||
|
printf "UNPARSED (groups=%d) pm_id=%s: %s\n", ng, pm, $0 > "/dev/stderr"
|
||||||
|
bad++
|
||||||
|
next
|
||||||
|
}
|
||||||
|
nr = split(trim(REST), r, /[ \t]+/)
|
||||||
|
if (nr != 10) {
|
||||||
|
printf "UNPARSED (scalars=%d) pm_id=%s: %s\n", nr, pm, $0 > "/dev/stderr"
|
||||||
|
bad++
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
lane = r[1]
|
||||||
|
sd = r[2]; sd_st = (sd ~ /\*/) ? 1 : 0; gsub(/\*/, "", sd)
|
||||||
|
lck = r[3]; lck_st = (lck ~ /\*/) ? 1 : 0; gsub(/\*/, "", lck)
|
||||||
|
rxppm = r[4]; vga = r[5]; dco = r[6]; txppm = r[7]
|
||||||
|
link_time = r[8]; snr = r[9]; ber = r[10]
|
||||||
|
|
||||||
|
# TXEQ 六個 tap 與總和(僅在全為數值時計算)
|
||||||
|
tsum = 0; tok = 1
|
||||||
|
for (i = 1; i <= 6; i++) {
|
||||||
|
tq[i] = gf(7, i)
|
||||||
|
if (is_num(tq[i])) tsum += tq[i]; else tok = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,", pm, core, lane, sdtype, sd, sd_st, lck, lck_st, rxppm, txppm > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s,%s,%s,", tq[1], tq[2], tq[3], tq[4], tq[5], tq[6], (tok ? tsum : "NA") > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s,%s,", gf(4,1), gf(4,2), gf(4,3), gf(4,4), gf(4,5), gf(4,6) > LANE_CSV
|
||||||
|
printf "%s,%s,", gf(5,1), gf(5,2) > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s,", gf(2,1), gf(2,2), gf(2,3), vga, dco > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s,", gf(3,1), gf(3,2), gf(3,3), gf(6,1), gf(6,2) > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s,", gf(8,1), gf(8,2), gf(9,1), gf(9,2), gf(9,3) > LANE_CSV
|
||||||
|
printf "%s,%s,%s,", link_time, snr, ber > LANE_CSV
|
||||||
|
printf "%s,%s,%s,", live_temp, avg_tmon, pll_lock > LANE_CSV
|
||||||
|
printf "%s,%s,%s,%s,%s/%s,%s\n", gf(1,1), gf(1,2), gf(1,3), gf(1,4), gf(1,5), gf(1,6), gf(1,7) > LANE_CSV
|
||||||
|
|
||||||
|
# per-PM 統計
|
||||||
|
n_lane++
|
||||||
|
if (lck != "1" || sd != "1") all_lck = 0
|
||||||
|
if (is_num(snr)) { if (snr_min == "" || snr + 0 < snr_min + 0) snr_min = snr }
|
||||||
|
for (i = 1; i <= 3; i++) {
|
||||||
|
e = gf(9, i)
|
||||||
|
if (is_num(e)) { if (eye_min == "" || e + 0 < eye_min + 0) eye_min = e }
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
flush_pm()
|
||||||
|
if (bad > 0) printf "WARN: %d lane row(s) unparsed\n", bad > "/dev/stderr"
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
|
echo "OK: ${LANE_CSV}, ${PM_CSV}"
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# =============================================================================
|
||||||
|
# bcm_dsc_scan.sh
|
||||||
|
#
|
||||||
|
# Author : ETWen
|
||||||
|
# Date : 20260906
|
||||||
|
#
|
||||||
|
# Version History
|
||||||
|
# V1.0.0 20260906 Initial Version
|
||||||
|
# V1.1.0 20260906 Log 預設輸出至 <script_dir>/log,掃描後自動複製到
|
||||||
|
# /host/hw-eval/jobs/;非 tty 環境改用逐行進度輸出,
|
||||||
|
# 避免灌爆 Tera Term logopen 的 log
|
||||||
|
# V1.2.0 20260906 新增 -f 自訂 log 檔名(支援 %u / %t 佔位符)與
|
||||||
|
# -F 強制覆蓋;預設不覆蓋既有檔案,自動加序號
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# 在 Blanton SONiC DUT 上批次執行 Broadcom diag shell 的
|
||||||
|
# "phydiag <port> dsc",收集 raw log 供離線解析。
|
||||||
|
#
|
||||||
|
# 實際下的指令為:
|
||||||
|
# bcmcmd -n <unit> -c "dsh -c 'phydiag <port> dsc'"
|
||||||
|
#
|
||||||
|
# Deployment:
|
||||||
|
# Script : /root/Blanton_Script/bcm_dsc_scan.sh
|
||||||
|
# Log : /root/Blanton_Script/log/dsc_raw_u<unit>_<YYYYmmdd_HHMMSS>.log
|
||||||
|
# Copy : /host/hw-eval/jobs/
|
||||||
|
#
|
||||||
|
# Log 檔名可用 -f 覆寫,支援兩個佔位符:
|
||||||
|
# %u -> u0 / u1
|
||||||
|
# %t -> YYYYmmdd_HHMMSS
|
||||||
|
# -f 只接受檔名,不接受路徑;目錄一律由 -o 決定。
|
||||||
|
# 同時掃兩個 unit 但檔名未含 %u 時,會自動在副檔名前插入 _u<unit>,
|
||||||
|
# 避免第二個 unit 覆蓋掉第一個。
|
||||||
|
#
|
||||||
|
# 路徑由 $0 推導,不依賴 $HOME。Script A 以 sudo -i 執行時 ~ 為 /root,
|
||||||
|
# 直接寫死 ~ 會有風險,故一律使用絕對路徑。
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./bcm_dsc_scan.sh # unit 0 與 1 都掃,掃完自動複製
|
||||||
|
# ./bcm_dsc_scan.sh -u 0 # 只掃 unit 0
|
||||||
|
# ./bcm_dsc_scan.sh -n # dry-run,只列出要掃的 port
|
||||||
|
# ./bcm_dsc_scan.sh -C # 不複製到 /host/hw-eval/jobs
|
||||||
|
# ./bcm_dsc_scan.sh -c /other/dir # 指定複製目的地
|
||||||
|
# ./bcm_dsc_scan.sh -u 0 -f dsc_before_reboot.log
|
||||||
|
# ./bcm_dsc_scan.sh -f blanton_dsc_%u_%t.log
|
||||||
|
#
|
||||||
|
# Exit code:
|
||||||
|
# 0 = 全部 port 皆取得完整 dump
|
||||||
|
# 1 = 有 port 未取得完整 dump,或複製失敗
|
||||||
|
# 2 = 參數 / 路徑錯誤
|
||||||
|
# 3 = 找不到 bcmcmd
|
||||||
|
#
|
||||||
|
# 結束時固定輸出一行 "### DSC_SCAN_DONE rc=<n>",供 TTL 端 wait 判斷。
|
||||||
|
#
|
||||||
|
# Design notes:
|
||||||
|
# - 純 POSIX sh,不依賴 bash / Python。
|
||||||
|
# - Port 清單以「範圍字串」定義在下方 PORTS_* 變數,展開後去重。
|
||||||
|
# 100G 清單先展開,故重複的 port 會被判定為 100G(目前僅 port 268)。
|
||||||
|
# - bcmcmd 即使指令失敗通常仍回傳 0,因此不以 exit code 判斷成功與否;
|
||||||
|
# 改由 marker 行 + "SERDES DISPLAY DIAG DATA END" 數量比對。
|
||||||
|
# - 先寫入本機 log 目錄再複製,而非直接寫遠端,避免中途失敗遺失整份資料。
|
||||||
|
#
|
||||||
|
# Assumptions:
|
||||||
|
# - unit 0 與 unit 1 使用相同的 port 清單。
|
||||||
|
# - phydiag 的參數為此清單中的 port 編號(非 port name)。
|
||||||
|
# - 由 root 執行(Script A 的 sudo -i 之後),故不呼叫 sudo。
|
||||||
|
#
|
||||||
|
# Cautions:
|
||||||
|
# - 全清單約 445 port x 2 unit,估計 10 分鐘,請勿把 DELAY 設為 0。
|
||||||
|
# - phydiag ... dsc 為唯讀操作,不會變更 SerDes 設定。
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Port list (以空白分隔的範圍或單一值)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
PORTS_100G="268"
|
||||||
|
PORTS_200G="1-24 40-96 112-168 184-240 256-308 310-327 342-380 382-399 414-452 454-471 486-524 526-543 558-565"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Paths / defaults
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) || exit 2
|
||||||
|
|
||||||
|
UNITS="0 1"
|
||||||
|
OUTDIR="${SCRIPT_DIR}/log"
|
||||||
|
COPYDIR="/host/hw-eval/jobs"
|
||||||
|
DELAY="0.2"
|
||||||
|
DRYRUN=0
|
||||||
|
DOCOPY=1
|
||||||
|
LOGNAME="" # -f 指定的 log 檔名,空字串表示使用預設命名
|
||||||
|
FORCE=0 # -F 允許覆蓋既有檔案
|
||||||
|
PROGRESS_EVERY=50 # 非 tty 環境每 N 筆印一行進度
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat >&2 << 'USAGE'
|
||||||
|
Usage: bcm_dsc_scan.sh [-u <0|1>] [-o <outdir>] [-f <logname>] [-F]
|
||||||
|
[-c <copydir>] [-C] [-d <sec>] [-n] [-h]
|
||||||
|
|
||||||
|
-u <unit> 只掃指定 unit。省略則 unit 0 與 1 都掃。
|
||||||
|
-o <dir> log 輸出目錄 (default: <script_dir>/log)
|
||||||
|
-f <name> 自訂 log 檔名(僅檔名,不含路徑)
|
||||||
|
佔位符: %u -> u0/u1 , %t -> YYYYmmdd_HHMMSS
|
||||||
|
default: dsc_raw_%u_%t.log
|
||||||
|
-F 允許覆蓋既有同名檔案 (default: 自動加序號 _1 / _2 ...)
|
||||||
|
-c <dir> 掃描後複製目的地 (default: /host/hw-eval/jobs)
|
||||||
|
-C 不複製
|
||||||
|
-d <sec> 每筆 phydiag 之間的間隔 (default: 0.2)
|
||||||
|
-n dry-run,列出展開後的 port 清單與指令,不實際執行
|
||||||
|
-h 顯示此說明
|
||||||
|
USAGE
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts "u:o:f:Fc:Cd:nh" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
u)
|
||||||
|
case "$OPTARG" in
|
||||||
|
0|1) UNITS="$OPTARG" ;;
|
||||||
|
*) echo "ERROR: -u 只接受 0 或 1 (got: $OPTARG)" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
o) OUTDIR="$OPTARG" ;;
|
||||||
|
f)
|
||||||
|
case "$OPTARG" in
|
||||||
|
"") echo "ERROR: -f 不可為空" >&2; exit 2 ;;
|
||||||
|
*/*) echo "ERROR: -f 只接受檔名,路徑請用 -o (got: $OPTARG)" >&2; exit 2 ;;
|
||||||
|
*) LOGNAME="$OPTARG" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
F) FORCE=1 ;;
|
||||||
|
c) COPYDIR="$OPTARG" ;;
|
||||||
|
C) DOCOPY=0 ;;
|
||||||
|
d) DELAY="$OPTARG" ;;
|
||||||
|
n) DRYRUN=1 ;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR" 2>/dev/null || { echo "ERROR: cannot create outdir: $OUTDIR" >&2; exit 2; }
|
||||||
|
[ -w "$OUTDIR" ] || { echo "ERROR: outdir not writable: $OUTDIR" >&2; exit 2; }
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# sleep 是否支援小數秒(busybox sleep 可能只吃整數)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if ! sleep "$DELAY" 2>/dev/null; then
|
||||||
|
echo "WARN: sleep '$DELAY' 不支援,改用 sleep 1" >&2
|
||||||
|
DELAY="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 展開 port 清單。100G 先展開,重複者以 100G 為準。
|
||||||
|
# 輸出格式: "<port>,<speed>",依 port 數值排序。
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
PORTFILE=$(mktemp) || { echo "ERROR: mktemp failed" >&2; exit 2; }
|
||||||
|
trap 'rm -f "$PORTFILE"' EXIT
|
||||||
|
trap 'echo "" >&2; echo "### DSC_SCAN_DONE rc=130 (INTERRUPTED)" >&2; rm -f "$PORTFILE"; exit 130' INT TERM
|
||||||
|
|
||||||
|
awk -v a="$PORTS_100G" -v b="$PORTS_200G" '
|
||||||
|
function expand(s, spd, n, i, j, p, k, lo, hi) {
|
||||||
|
n = split(s, p, /[ \t]+/)
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
if (p[i] == "") continue
|
||||||
|
if (split(p[i], k, "-") == 2) { lo = k[1] + 0; hi = k[2] + 0 }
|
||||||
|
else { lo = p[i] + 0; hi = lo }
|
||||||
|
if (hi < lo) { printf "ERROR: bad range %s\n", p[i] > "/dev/stderr"; continue }
|
||||||
|
for (j = lo; j <= hi; j++) if (!(j in SPD)) SPD[j] = spd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
expand(a, "100G") # 先展開 100G,重複的 port 由 100G 取得
|
||||||
|
expand(b, "200G")
|
||||||
|
for (p in SPD) print p "," SPD[p]
|
||||||
|
}' | sort -t, -k1,1n > "$PORTFILE"
|
||||||
|
|
||||||
|
TOTAL=$(wc -l < "$PORTFILE" | tr -d ' ')
|
||||||
|
[ "$TOTAL" -gt 0 ] || { echo "ERROR: port list is empty" >&2; exit 2; }
|
||||||
|
|
||||||
|
N100=$(grep -c ',100G$' "$PORTFILE" || true)
|
||||||
|
N200=$(grep -c ',200G$' "$PORTFILE" || true)
|
||||||
|
|
||||||
|
echo "Script dir : $SCRIPT_DIR" >&2
|
||||||
|
echo "Log dir : $OUTDIR" >&2
|
||||||
|
echo "Port list : $TOTAL ports (200G x2: $N200, 100G: $N100)" >&2
|
||||||
|
echo "Units : $UNITS" >&2
|
||||||
|
echo "Delay : ${DELAY}s" >&2
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Dry-run
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ "$DRYRUN" -eq 1 ]; then
|
||||||
|
for U in $UNITS; do
|
||||||
|
echo "--- unit $U ---"
|
||||||
|
while IFS=, read -r P SPD; do
|
||||||
|
printf 'bcmcmd -n %s -c "dsh -c '\''phydiag %s dsc'\''" # %s\n' "$U" "$P" "$SPD"
|
||||||
|
done < "$PORTFILE"
|
||||||
|
done
|
||||||
|
echo "### DSC_SCAN_DONE rc=0 (dry-run)" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v bcmcmd > /dev/null 2>&1 || {
|
||||||
|
echo "ERROR: bcmcmd not found in PATH" >&2
|
||||||
|
echo "### DSC_SCAN_DONE rc=3" >&2
|
||||||
|
exit 3
|
||||||
|
}
|
||||||
|
|
||||||
|
# stderr 是 tty 才用 \r 進度條;被導向檔案(Tera Term logopen)時改逐行輸出
|
||||||
|
if [ -t 2 ]; then TTY=1; else TTY=0; fi
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Log 檔名處理
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 純 POSIX 的字串取代,避免 sed 對檔名中的特殊字元誤判
|
||||||
|
replace_all() { # $1=haystack $2=needle $3=replacement
|
||||||
|
_s=$1; _n=$2; _r=$3; _out=""
|
||||||
|
while :; do
|
||||||
|
case "$_s" in
|
||||||
|
*"$_n"*)
|
||||||
|
_out="${_out}${_s%%"$_n"*}${_r}"
|
||||||
|
_s=${_s#*"$_n"}
|
||||||
|
;;
|
||||||
|
*) _out="${_out}${_s}"; break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
printf '%s' "$_out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 依 unit 組出 log 檔名。未指定 -f 時使用預設命名。
|
||||||
|
build_logname() { # $1=unit
|
||||||
|
_u=$1
|
||||||
|
_n=${LOGNAME:-dsc_raw_%u_%t.log}
|
||||||
|
case "$_n" in *%u*) _has_u=1 ;; *) _has_u=0 ;; esac
|
||||||
|
_n=$(replace_all "$_n" '%u' "u${_u}")
|
||||||
|
_n=$(replace_all "$_n" '%t' "$TS")
|
||||||
|
# 掃兩個 unit 但檔名沒有 %u,第二個 unit 會覆蓋第一個,故自動加上 _u<unit>
|
||||||
|
if [ "$_has_u" -eq 0 ] && [ "$MULTI" -eq 1 ]; then
|
||||||
|
case "$_n" in
|
||||||
|
*.*) _n="${_n%.*}_u${_u}.${_n##*.}" ;;
|
||||||
|
*) _n="${_n}_u${_u}" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
printf '%s' "$_n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 檔案已存在時自動加序號,避免覆蓋掉先前的掃描結果(-F 可強制覆蓋)
|
||||||
|
uniquify() { # $1=filename
|
||||||
|
_f=$1
|
||||||
|
[ -e "${OUTDIR}/${_f}" ] || { printf '%s' "$_f"; return; }
|
||||||
|
if [ "$FORCE" -eq 1 ]; then
|
||||||
|
echo "WARN: 覆蓋既有檔案 ${OUTDIR}/${_f}" >&2
|
||||||
|
printf '%s' "$_f"; return
|
||||||
|
fi
|
||||||
|
case "$_f" in
|
||||||
|
*.*) _b=${_f%.*}; _e=".${_f##*.}" ;;
|
||||||
|
*) _b=$_f; _e="" ;;
|
||||||
|
esac
|
||||||
|
_i=1
|
||||||
|
while [ -e "${OUTDIR}/${_b}_${_i}${_e}" ]; do _i=$((_i + 1)); done
|
||||||
|
echo "WARN: ${_f} 已存在,改用 ${_b}_${_i}${_e}(-F 可強制覆蓋)" >&2
|
||||||
|
printf '%s' "${_b}_${_i}${_e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
MULTI=0
|
||||||
|
[ "$(echo "$UNITS" | wc -w | tr -d ' ')" -gt 1 ] && MULTI=1
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Scan
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
TS=$(date +%Y%m%d_%H%M%S)
|
||||||
|
RC=0
|
||||||
|
LOGS=""
|
||||||
|
|
||||||
|
for U in $UNITS; do
|
||||||
|
LOG="${OUTDIR}/$(uniquify "$(build_logname "$U")")"
|
||||||
|
: > "$LOG" || { echo "ERROR: cannot write $LOG" >&2; exit 2; }
|
||||||
|
LOGS="$LOGS $LOG"
|
||||||
|
|
||||||
|
printf '### SCAN_BEGIN unit=%s time=%s total=%s script=bcm_dsc_scan_V1.1.0\n' \
|
||||||
|
"$U" "$(date '+%Y-%m-%d %H:%M:%S')" "$TOTAL" >> "$LOG"
|
||||||
|
|
||||||
|
echo "[unit $U] scanning $TOTAL ports -> $LOG" >&2
|
||||||
|
|
||||||
|
SEQ=0
|
||||||
|
while IFS=, read -r P SPD; do
|
||||||
|
SEQ=$((SEQ + 1))
|
||||||
|
printf '### PORT unit=%s port=%s speed=%s seq=%s/%s\n' "$U" "$P" "$SPD" "$SEQ" "$TOTAL" >> "$LOG"
|
||||||
|
bcmcmd -n "$U" -c "dsh -c 'phydiag $P dsc'" >> "$LOG" 2>&1 || RC=1
|
||||||
|
|
||||||
|
if [ "$TTY" -eq 1 ]; then
|
||||||
|
printf '\r[unit %s] %s/%s port=%-5s' "$U" "$SEQ" "$TOTAL" "$P" >&2
|
||||||
|
elif [ $((SEQ % PROGRESS_EVERY)) -eq 0 ] || [ "$SEQ" -eq "$TOTAL" ]; then
|
||||||
|
printf '[unit %s] %s/%s port=%s\n' "$U" "$SEQ" "$TOTAL" "$P" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$DELAY"
|
||||||
|
done < "$PORTFILE"
|
||||||
|
|
||||||
|
[ "$TTY" -eq 1 ] && printf '\n' >&2
|
||||||
|
printf '### SCAN_END unit=%s time=%s\n' "$U" "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
|
||||||
|
|
||||||
|
# 概略檢查:每筆 PORT marker 應對應一個 dsc 區塊
|
||||||
|
MARK=$(grep -c '^### PORT ' "$LOG" || true)
|
||||||
|
DUMP=$(grep -c 'SERDES DISPLAY DIAG DATA END' "$LOG" || true)
|
||||||
|
if [ "$MARK" -ne "$DUMP" ]; then
|
||||||
|
echo "WARN: unit $U markers=$MARK dsc_blocks=$DUMP (有 port 未取得完整 dump)" >&2
|
||||||
|
RC=1
|
||||||
|
fi
|
||||||
|
echo "Saved: $LOG (${DUMP}/${MARK} dumps)" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copy to job directory
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ "$DOCOPY" -eq 1 ]; then
|
||||||
|
if mkdir -p "$COPYDIR" 2>/dev/null && [ -w "$COPYDIR" ]; then
|
||||||
|
for L in $LOGS; do
|
||||||
|
if cp -p "$L" "$COPYDIR/"; then
|
||||||
|
echo "Copied: $COPYDIR/$(basename "$L")" >&2
|
||||||
|
else
|
||||||
|
echo "ERROR: copy failed: $L -> $COPYDIR" >&2
|
||||||
|
RC=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
sync
|
||||||
|
else
|
||||||
|
echo "ERROR: copydir not writable: $COPYDIR (log 仍保留在 $OUTDIR)" >&2
|
||||||
|
RC=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "### DSC_SCAN_DONE rc=${RC}" >&2
|
||||||
|
exit "$RC"
|
||||||
Reference in New Issue
Block a user