2 Commits
Author SHA1 Message Date
etwenandClaude Opus 5 b84188322d feat(ttl): Take a SerDes DSC dump in Script A and Script C
Script A scans just before 'show uptime', Script C just before the NVMe
error info, so the pair brackets the soak:

    ./Blanton_Script/bcm_dsc_scan.sh -f blanton_dsc_ScriptA.log -F
    ./Blanton_Script/bcm_dsc_scan.sh -f blanton_dsc_ScriptC.log -F

Neither name carries %u, so the scan inserts _u0 / _u1 itself and unit 1
cannot overwrite unit 0 - that is where the four file names Script C
copies to /host/hw-eval/jobs come from. The scan already copies its own
output there; the explicit cp lines are a second pass, so a copy that
fails inside the scan still leaves the data on the USB stick.

Also removed the commented-out 100G PRBS call sites from all three
macros, along with a duplicated commented 100G port status block in
Script A and the stale bmc_monitor_ddr.sh block in Script C. They had
been dead since bring-up. port_prbs_monitor.sh still ships and still
runs by hand.

Script A's header goes to V1.1.9 and its Version History covers all of
the above, including the note that both new scripts are recorded 100644
and need chmod +x on the DUT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-09-06 23:22:59 +08:00
etwenandClaude Opus 5 b4148c9571 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
2026-09-06 23:22:50 +08:00
5 changed files with 622 additions and 69 deletions
+71 -32
View File
@@ -1,7 +1,7 @@
; =============================================================================
; Script A for Blanton
; Version : V1.1.8
; Date : 2026-09-06
; Version : V1.1.9
; Date : 2026-09-07
; Author : ETWen
; =============================================================================
; Version History:
@@ -419,6 +419,71 @@
; happened to have the binaries locally would have shipped
; a 100 MB zip while a clone shipped 200 KB. The bundle is
; now the same either way
; V1.1.9 2026-09-07
; Blanton_Script/bcm_dsc_scan.sh Created V1.2.0 - batch SerDes DSC dump
; Runs bcmcmd -n <unit> -c "dsh -c 'phydiag <port> dsc'"
; over the whole port list (1 x 100G + 445 x 200G ranges),
; both units, and leaves the raw log in <script_dir>/log
; before copying 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. -f takes a filename only; the directory comes from
; -o
; bcmcmd exits 0 even when the diag shell rejects a
; command, so success is judged by counting 'SERDES DISPLAY
; DIAG DATA END' markers, not by the exit code - the same
; trap blanton_traffic_linespeed.sh hit
; Paths come from $0, not $HOME, because Script A runs it
; after sudo -i and a literal ~ would depend on the
; environment. Ends with '### DSC_SCAN_DONE rc=<n>' for a
; TTL wait to key on
; Non-tty output is one line per port instead of a progress
; bar, so it does not flood the Tera Term log
; !! phydiag dsc is read-only and does not change any
; SerDes setting, but a full scan is ~10 min per run.
; ScriptA and ScriptC each do one, so the test is ~20
; min longer than before. Do not set DELAY to 0
; Blanton_Script/bcm_dsc_parse.sh Created V1.0.0 - offline DSC log to CSV
; Host side. -i <raw log> -o <prefix> produces
; <prefix>_lane.csv (one row per lane: TXEQ / RXFFE / DFE /
; EYE / SNR) and <prefix>_pm.csv (one row per pm_id:
; temperature / PLL / lane count / SNR-EYE stats)
; POSIX sh + awk, mawk compatible, no Python. Lane rows are
; read by the order of their bracket groups rather than
; fixed columns, so re-spacing by the SDK does not break
; it; a row whose group count is not 9 is marked UNPARSED
; on stderr instead of being parsed wrongly
; SD / LCK sticky '*' flags are kept as separate columns
; and not interpreted; a BER field of '!chk_en' means the
; checker was off and is passed through unchanged
; ScriptA Collect the SerDes DSC baseline before 'show uptime'
; ./Blanton_Script/bcm_dsc_scan.sh -f
; blanton_dsc_ScriptA.log -F
; The name has no %u, so the script inserts _u0 / _u1
; itself and unit 1 cannot overwrite unit 0 - that is where
; the file names ScriptC copies come from
; ScriptA / ScriptB / ScriptC Remove the commented-out 100G PRBS blocks
; The port_prbs_monitor.sh calls had been commented out in
; all three since bring-up, together with a second copy of
; the 100G port status block in ScriptA and the stale
; bmc_monitor_ddr.sh block in ScriptC
; port_prbs_monitor.sh still ships and still runs by hand;
; only the dead call sites are gone
; ScriptC Collect the closing DSC dump and carry the four logs out
; ./Blanton_Script/bcm_dsc_scan.sh -f
; blanton_dsc_ScriptC.log -F, then cp of
; blanton_dsc_Script{A,C}_u{0,1}.log into
; /host/hw-eval/jobs/
; The scan already copies its own output there; these four
; are a second pass so a failed copy inside the scan still
; leaves the data on the USB stick
; Blanton_Script Both new scripts need the execute bit on the DUT
; !! git records them 100644 like every other .sh here, and
; ScriptA / ScriptC invoke them as
; ./Blanton_Script/bcm_dsc_scan.sh, so they join
; bmc_monitor.sh and friends in the chmod +x line after
; deployment. Without it both scans fail with Permission
; denied and the macro walks straight past it
; =============================================================================
include "config.ttl"
@@ -619,36 +684,6 @@ sendln "config save -y"
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== 100G PRBS ==========
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh start"
;wait prompt_sonic_root
;sendln "sleep 10"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh status"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh stop"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh report"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh clear"
; ========== 100G Port Status ==========
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet513"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet514"
;wait prompt_sonic_root
;sendln "config interface -n asic0 startup Ethernet513"
;wait prompt_sonic_root
;sendln "config interface -n asic1 startup Ethernet514"
;wait prompt_sonic_root
;sendln "config save -y"
;wait prompt_sonic_root
;sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== First Traffic Test ==========
; Silent MAC
@@ -712,6 +747,10 @@ else
endif
; ========== Collect bcm dsc data ==========
wait prompt_sonic_root
sendln "./Blanton_Script/bcm_dsc_scan.sh -f blanton_dsc_ScriptA.log -F"
; ========== Show Power on uptime ==========
wait prompt_sonic_root
sendln "show uptime"
@@ -123,12 +123,6 @@ sendln "~/Blanton_Script/nfc_polling.sh status"
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== 100G PRBS ==========
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh start"
;=============================================================================================================
; ========== Traffic START ==========
; Silent MAC
+12 -31
View File
@@ -33,23 +33,6 @@ sendln "~/Blanton_Script/mgmt_ping_monitor.sh stop"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh stop"
;=============================================================================================================
; 100G PRBS STOP
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh stop"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet513"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet514"
;wait prompt_sonic_root
;sendln "config interface -n asic0 startup Ethernet513"
;wait prompt_sonic_root
;sendln "config interface -n asic1 startup Ethernet514"
;=============================================================================================================
; ========== TAKE DATA ==========
include "utils/setup_pmon.ttl"
include "utils/show_dmesg.ttl"
@@ -66,15 +49,6 @@ include "utils/show_pcie_error_reg_SSD.ttl"
wait prompt_sonic_root
sendln "/usr/sbin/ras-mc-ctl --summary"
; ========== BMC DDR TAKE DATA ==========
;wait prompt_sonic_root
;sendln "./Blanton_Script/bmc_monitor_ddr.sh stop"
;wait prompt_sonic_root
;sendln "./Blanton_Script/bmc_monitor.sh stop"
;wait prompt_sonic_root
;sendln "cat ./Blanton_Script/log/bmc_poll.log"
;wait prompt_sonic_root
;sendln "cat ./Blanton_Script/log/bmc_ddr.log"
; ========== BMC Monitor TAKE DATA ==========
wait prompt_sonic_root
@@ -108,10 +82,6 @@ sendln "~/Blanton_Script/nfc_polling.sh report"
; ========== 100G Port ==========
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
;wait prompt_sonic_root
;sendln "cat ~/Blanton_Script/log/port_prbs.log "
;=============================================================================================================
; ========== CHECK Traffic counters ==========
if SWB_UNIT0 = 1 && SWB_UNIT1 = 1 then
@@ -180,6 +150,10 @@ else
endif
; ========== Collect bcm dsc data ==========
wait prompt_sonic_root
sendln "./Blanton_Script/bcm_dsc_scan.sh -f blanton_dsc_ScriptC.log -F"
; ========== NVME Err Info ==========
wait prompt_sonic_root
@@ -225,7 +199,14 @@ wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/mgmt_ping.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/nfc_poll.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/blanton_dsc_ScriptA_u0.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/blanton_dsc_ScriptA_u1.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/blanton_dsc_ScriptC_u0.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/blanton_dsc_ScriptC_u1.log /host/hw-eval/jobs/"
wait prompt_sonic_root
getdate ts_date "%Y%m%d"
@@ -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"