feat(prbs): Add 100G PRBS monitor; fix the job log path and USB mount

port_prbs_monitor.sh drives PRBS on both 100G uplinks: lpmode off, arm
the pattern, then poll `phy diag <phy> prbs get` in the background until
stopped, counting a poll as PASS only when the output says "PRBS OK!".
prbsstat Ber is captured beside every poll for the record but does not
decide the verdict. `stop` runs prbsstat STOp and prbs clear before
rendering the report, so the test does not leave PRBS armed.

`start a` / `start b` runs one uplink alone, which is how a genuine port
fault is separated from the DUT not coping with two PRBS streams at
once. A port that was not run reports SKIP rather than FAIL -- deciding
on poll count alone would have made a deliberate single-port run look
like half the hardware was broken.

The port status is deliberately not printed at `start`: with PRBS armed
the link reports DOWN, which reads as a failure to anyone glancing at
the console. It appears in the report instead, after prbs clear and
labelled as the recovered state.

The calls in Script A, B and C are committed but commented out -- PRBS
is still under bring-up.

Fix: the job directory was written as /host/hw-eval/current/jobs in some
places and /host/hw-eval/jobs in others, so what Script B wrote was not
what Script C collected. Unified on /host/hw-eval/jobs.

Fix: Script C copied to /mnt/usb assuming it was mounted. It now finds
the device with usb_target.sh and mounts it first, so the logs actually
leave the DUT.

Script C also reorders its results -- stress logs, then the MGMT ping
report, then 100G status -- and cats the bgctl ssd/usb logs.

Script A -> V1.0.9. Docs and docs/release-notes/v1.0.9.md updated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
This commit is contained in:
2026-08-26 15:35:48 +08:00
co-authored by Claude Opus 5
parent 5e1e4a6a8f
commit 03d609cc1f
7 changed files with 627 additions and 26 deletions
+41 -4
View File
@@ -1,7 +1,7 @@
; =============================================================================
; Script A for Blanton
; Version : V1.0.8
; Date : 2026-08-25
; Version : V1.0.9
; Date : 2026-08-26
; Author : ETWen
; =============================================================================
; Version History:
@@ -58,6 +58,13 @@
; ScriptA MGMT ping baseline 45s -> 30s, clear the log afterwards
; ScriptB Show mgmt_ping status after start
; ScriptC Show mgmt_ping status before stop, cat the report after
; V1.0.9 2026-08-26 Blanton_Script/port_prbs_monitor.sh Add 100G Port PRBS test
; start [both|a|b] - single port isolates a port fault from
; the DUT not coping with two PRBS streams at once
; !! TTL calls are commented out in A/B/C - still under bring-up
; ScriptA/B/C Job log path /host/hw-eval/current/jobs -> /host/hw-eval/jobs
; ScriptC Mount the USB (usb_target.sh) before copying the job logs
; ScriptC Reorder results: stress logs, then MGMT ping, then 100G status
; =============================================================================
include "config.ttl"
@@ -94,7 +101,7 @@ sendln cmd
;Stress log clear
wait prompt_sonic_root
sendln "rm /host/hw-eval/current/jobs/*"
sendln "rm /host/hw-eval/jobs/*"
; ========== HW Test Session ==========
wait prompt_sonic_root
@@ -218,7 +225,6 @@ sendln "~/Blanton_Script/mgmt_ping_monitor.sh clear"
; ========== 100G Port Status ==========
; Lpmode Disable
wait prompt_sonic_root
sendln "sudo sfputil lpmode off Ethernet513"
wait prompt_sonic_root
@@ -233,6 +239,37 @@ 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
wait prompt_sonic_root
@@ -40,7 +40,7 @@ sendln "bgctl run /usr/sbin/memtester 1G" ; Continue Execture
; ========== SSD read/write ==========
wait prompt_sonic_root
sendln "bgctl run qfx5252-stress-ssd 1G --runtime 3600 --passes 5 --write --force --log /host/hw-eval/current/qfx5252-stress-ssd.log"
sendln "bgctl run qfx5252-stress-ssd 1G --runtime 3600 --passes 5 --write --force --log /host/hw-eval/jobs/qfx5252-stress-ssd.log"
; ========== USB read/write ==========
; ---- Get USB partition node -> usb_dev ----
@@ -51,7 +51,7 @@ waitregex '<<DEV=([^>]*)>>'
usb_dev = groupmatchstr1
wait prompt_sonic_root
sprintf2 cmd "bgctl run qfx5252-stress-usb %s 256M --runtime 60 --passes 1 --write --log /host/hw-eval/qfx5252-stress-usb.log" usb_dev
sprintf2 cmd "bgctl run qfx5252-stress-usb %s 256M --runtime 60 --passes 1 --write --log /host/hw-eval/jobs/qfx5252-stress-usb.log" usb_dev
sendln cmd
;wait prompt_sonic_root
@@ -105,6 +105,12 @@ sendln "./Blanton_Script/mgmt_ping_monitor.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
+50 -11
View File
@@ -25,8 +25,23 @@ wait prompt_sonic_root
sendln "~/Blanton_Script/mgmt_ping_monitor.sh status"
wait prompt_sonic_root
sendln "~/Blanton_Script/mgmt_ping_monitor.sh stop"
wait prompt_sonic_root
sendln "cat ~/Blanton_Script/log/mgmt_ping.log"
;=============================================================================================================
; 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"
@@ -64,16 +79,26 @@ sendln "cat ./Blanton_Script/log/bmc_poll.log"
;wait prompt_sonic_root
;sendln "journalctl -u qfx5252-bmc-usb-net-test.service --no-pager"
; ========== 10G/1G MGMT Test result ==========
wait prompt_sonic_root
sendln "ip -s link show eth0"
wait prompt_sonic_root
sendln "ip -s link show eth1"
; ========== CHECK Stress results ==========
wait prompt_sonic_root
sendln "cat ~/hammer/tools/amd/mlucas_amm_log"
wait prompt_sonic_root
sendln "cat /host/hw-eval/jobs/qfx5252-stress-ssd.log"
wait prompt_sonic_root
sendln "cat /host/hw-eval/jobs/qfx5252-stress-usb.log"
; ========== 10G/1G MGMT Test result ==========
wait prompt_sonic_root
sendln "cat ~/Blanton_Script/log/mgmt_ping.log"
; ========== 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
@@ -109,17 +134,31 @@ sendln "show reboot-cause"
wait prompt_sonic_root
sendln "show uptime"
; ---- Get USB partition node -> usb_dev ----
usb_dev = ''
wait prompt_sonic_root
sendln 'D=$(bash ~/Blanton_Script/usb_target.sh -o dev 2>/dev/null); echo "<<D""EV=${D:-NONE}>>"'
waitregex '<<DEV=([^>]*)>>'
usb_dev = groupmatchstr1
wait prompt_sonic_root
sendln "sync"
wait prompt_sonic_root
sprintf2 cmd "mount %s /mnt/usb" usb_dev
sendln cmd
; ========== Copy Stress Log to USB ==========
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/bmc_poll.log /host/hw-eval/current/jobs/"
sendln "cp ./Blanton_Script/log/bmc_poll.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/mgmt_ping.log /host/hw-eval/current/jobs/"
sendln "cp ./Blanton_Script/log/mgmt_ping.log /host/hw-eval/jobs/"
wait prompt_sonic_root
getdate ts_date "%Y%m%d"
gettime ts_time "%H%M"
sprintf2 usb_dst "/mnt/usb/jobs-%s_%s" ts_date ts_time
sprintf2 cmd "cp -r /host/hw-eval/current/jobs/ %s" usb_dst
sprintf2 cmd "cp -r /host/hw-eval/jobs/ %s" usb_dst
sendln cmd
; ========== HW Test Session ==========
@@ -0,0 +1,431 @@
#!/bin/bash
###############################################################################
# port_prbs_monitor.sh
#
# Version : V1.0.0
# Author : ETWen
# Date : 20260826
#
# Purpose : Run a PRBS test on the two 100G uplinks AT THE SAME TIME and keep
# polling until stopped. Each port gets its own background worker:
#
# setup : sfputil lpmode off <port>
# phy diag <phy> prbs set <poly>
# phy diag <phy> prbsstat STArt Interval=<n>
# poll : phy diag <phy> prbs get <- PASS/FAIL comes from here
# phydiag <phy> prbsstat Ber <- recorded only
# stop : phydiag <phy> prbsstat STOp
# phydiag <phy> prbs clear
#
# Every command and its full output goes to that port's raw log.
# `stop` tears the test down and renders the report; `report`
# prints the PASS/FAIL tally per port.
#
# Notes : - A poll counts as PASS only when the output contains "PRBS OK!".
# bcmcmd exits 0 even when the BCM shell rejects a command, so the
# exit status cannot be used -- only the output can.
# - bcmcmd is always run with </dev/null. Without it, it inherits the
# polling loop's stdin and eats it, and the loop runs once.
#
# Version History
# V1.0.0 20260826 Initial Version
###############################################################################
set -u
###############################################################################
# User Configurable Section -- normally this is the only part you need to edit
###############################################################################
# --- port A ---
PORT_A_NAME="Ethernet513"
PORT_A_UNIT=0 # bcmcmd -n <unit>
PORT_A_PHY=268 # phy diag <phy>
# --- port B ---
PORT_B_NAME="Ethernet514"
PORT_B_UNIT=1
PORT_B_PHY=268
# Which ports `start` runs when no argument is given: both | a | b
# Override per run: start a / start b / start both
# Running one at a time is how you tell a genuine port fault from the DUT not
# coping with two PRBS streams at once.
PORTS="both"
# --- PRBS ---
PRBS_POLY="p=3" # passed to "prbs set"
PRBS_STAT_INTERVAL=5 # passed to "prbsstat STArt Interval="
POLL_INTERVAL_SEC=10 # seconds between polls
PASS_PATTERN="PRBS OK!" # a poll is PASS only if the output contains this
# Seconds to wait after the setup commands before the first poll, so the link
# has settled and the counters mean something.
SETTLE_SEC=10
# Stop after this many polls per port. 0 = run until stopped manually.
MAX_POLLS=0
# How many recent poll blocks the report shows per port.
TAIL_ENTRIES=20
# Prefix for privileged commands; empty because this already runs as root.
SUDO=""
BCMCMD="bcmcmd"
# Logging
LOG_DIR="" # empty = <script directory>/log
LOG_NAME="port_prbs.log"
# What `start` does with the log left behind by the previous run:
# new | archive | append
START_LOG_MODE="new"
###############################################################################
# Internal
###############################################################################
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_NAME}"
[ -n "${LOG_DIR}" ] || LOG_DIR="${SCRIPT_DIR}/log"
LOG_FILE="${LOG_DIR}/${LOG_NAME}"
BASE="${LOG_DIR}/${LOG_NAME%.log}"
PID_FILE="${BASE}.pid"
STATE_FILE="${BASE}.state"
RAW_A="${BASE}_${PORT_A_NAME}.raw"
RAW_B="${BASE}_${PORT_B_NAME}.raw"
ts() { date '+%Y-%m-%d %H:%M:%S'; }
# port_tally <name> <raw> -> "<polls> <pass> <fail> <verdict>"
# A raw without a "PRBS START" line means the port was not run this session,
# which is SKIP -- reporting it as FAIL would make a deliberate single-port
# run look like half the hardware is broken.
port_tally() {
local name="$1" raw="$2" pass fail polls verdict
if [ ! -s "${raw}" ] || ! grep -aq "PRBS START ${name}" "${raw}" 2>/dev/null; then
printf '0 0 0 SKIP'; return 0
fi
pass=$(grep -ac "PRBS ${name} poll=[0-9]* PASS" "${raw}" 2>/dev/null); pass=${pass:-0}
fail=$(grep -ac "PRBS ${name} poll=[0-9]* FAIL" "${raw}" 2>/dev/null); fail=${fail:-0}
polls=$(( pass + fail ))
if [ "${polls}" -gt 0 ] && [ "${fail}" -eq 0 ]; then verdict=PASS; else verdict=FAIL; fi
printf '%s %s %s %s' "${polls}" "${pass}" "${fail}" "${verdict}"
}
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
# bcm <unit> <dsh command> -- output on stdout, never trusts the exit status.
# </dev/null so it cannot consume the caller's stdin.
bcm() {
local unit="$1" cmd="$2"
${SUDO} "${BCMCMD}" -n "${unit}" -c "dsh -c \"${cmd}\"" </dev/null 2>&1
}
# run_step <unit> <dsh command>
# The command and its full output are written to the LOG via stderr, because
# the worker has stderr pointed at the raw file. Only the output itself goes to
# stdout, so `out="$(run_step ...)"` captures the output without swallowing the
# log lines -- writing both to stdout would have put the whole record inside
# the variable and left the log with nothing but the poll verdicts.
run_step() {
local unit="$1" cmd="$2" out
out="$(bcm "${unit}" "${cmd}")"
{
printf '[%s] CMD : bcmcmd -n %s -c '\''dsh -c "%s"'\''\n' "$(ts)" "${unit}" "${cmd}"
printf '%s\n' "${out}"
} >&2
printf '%s' "${out}"
}
# Echo "<pid_a> <pid_b>" and return 0 while at least one worker is alive. A
# port that was not started this run is recorded as "-", so a single-port run
# is a first-class case rather than a half-broken two-port one.
is_running() {
local pa pb alive=0
[ -f "${PID_FILE}" ] || return 1
pa="$(sed -n '1p' "${PID_FILE}" 2>/dev/null)"; pa="${pa:--}"
pb="$(sed -n '2p' "${PID_FILE}" 2>/dev/null)"; pb="${pb:--}"
[[ "${pa}" =~ ^[0-9]+$ ]] && kill -0 "${pa}" 2>/dev/null && alive=1
[[ "${pb}" =~ ^[0-9]+$ ]] && kill -0 "${pb}" 2>/dev/null && alive=1
[ "${alive}" -eq 1 ] || return 1
printf '%s %s' "${pa}" "${pb}"
return 0
}
###############################################################################
# Worker -- one per port, launched by `start`
###############################################################################
WORKER_STOP=0
worker_on_signal() { WORKER_STOP=1; }
# __worker <name> <unit> <phy> <rawfile>
do_worker() {
local name="$1" unit="$2" phy="$3" raw="$4"
local n=0 pass=0 fail=0 out
exec >>"${raw}" 2>&1
trap worker_on_signal INT TERM
printf '#############################################################\n'
printf '[%s] PRBS START %s (unit %s, phy %s) poly=%s interval=%ss poll=%ss\n' \
"$(ts)" "${name}" "${unit}" "${phy}" "${PRBS_POLY}" \
"${PRBS_STAT_INTERVAL}" "${POLL_INTERVAL_SEC}"
printf '#############################################################\n'
# --- setup ---
printf '[%s] CMD : sfputil lpmode off %s\n' "$(ts)" "${name}"
${SUDO} sfputil lpmode off "${name}" </dev/null 2>&1
run_step "${unit}" "phy diag ${phy} prbs set ${PRBS_POLY}" >/dev/null
run_step "${unit}" "phy diag ${phy} prbsstat STArt Interval=${PRBS_STAT_INTERVAL}" >/dev/null
sleep "${SETTLE_SEC}"
# --- poll ---
while [ "${WORKER_STOP}" -eq 0 ]; do
n=$(( n + 1 ))
printf -- '---------- %s POLL %d @ %s ----------\n' "${name}" "${n}" "$(ts)"
out="$(run_step "${unit}" "phy diag ${phy} prbs get")"
# Ber is recorded for the log only; it does not decide the verdict.
run_step "${unit}" "phydiag ${phy} prbsstat Ber" >/dev/null
if printf '%s' "${out}" | grep -qF "${PASS_PATTERN}"; then
pass=$(( pass + 1 ))
printf '[%s] PRBS %s poll=%d PASS\n' "$(ts)" "${name}" "${n}"
else
fail=$(( fail + 1 ))
printf '[%s] PRBS %s poll=%d FAIL\n' "$(ts)" "${name}" "${n}"
fi
[ "${MAX_POLLS}" -gt 0 ] && [ "${n}" -ge "${MAX_POLLS}" ] && break
[ "${WORKER_STOP}" -eq 0 ] || break
sleep "${POLL_INTERVAL_SEC}"
done
# --- teardown ---
run_step "${unit}" "phydiag ${phy} prbsstat STOp" >/dev/null
run_step "${unit}" "phydiag ${phy} prbs clear" >/dev/null
printf '[%s] PRBS STOP %s polls=%d PASS=%d FAIL=%d\n' \
"$(ts)" "${name}" "${n}" "${pass}" "${fail}"
}
###############################################################################
# Report
###############################################################################
# render_port <name> <unit> <phy> <raw>
render_port() {
local name="$1" unit="$2" phy="$3" raw="$4"
local t polls pass fail verdict
t="$(port_tally "${name}" "${raw}")"
polls="$(printf '%s' "${t}" | awk '{print $1}')"
pass="$(printf '%s' "${t}" | awk '{print $2}')"
fail="$(printf '%s' "${t}" | awk '{print $3}')"
verdict="$(printf '%s' "${t}" | awk '{print $4}')"
if [ "${verdict}" = "SKIP" ]; then
printf -- '----- %s (unit %s, phy %s) : not run this session -----\n\n' \
"${name}" "${unit}" "${phy}"
printf '[%s] RESULT %-12s polls=%-4s PASS=%-4s FAIL=%-4s %s\n\n' \
"$(ts)" "${name}" "${polls}" "${pass}" "${fail}" "${verdict}"
return 0
fi
printf -- '----- %s (unit %s, phy %s) : last %s poll blocks -----\n' \
"${name}" "${unit}" "${phy}" "${TAIL_ENTRIES}"
grep -aE "^-{10} ${name} POLL|PRBS ${name} poll=|prbsstat Ber|^ *[0-9]+ *: " "${raw}" 2>/dev/null \
| tail -n $(( TAIL_ENTRIES * 3 ))
printf '\n'
printf '[%s] RESULT %-12s polls=%-4s PASS=%-4s FAIL=%-4s %s\n\n' \
"$(ts)" "${name}" "${polls}" "${pass}" "${fail}" "${verdict}"
}
write_report() {
local started
started="$(sed -n '1p' "${STATE_FILE}" 2>/dev/null)"
{
printf '#############################################################\n'
printf '[%s] 100G PRBS report\n' "$(ts)"
printf ' started : %s\n' "${started:-unknown}"
printf ' stopped : %s\n' "$(ts)"
printf ' %s : unit %s, phy %s\n' "${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}"
printf ' %s : unit %s, phy %s\n' "${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}"
printf ' poly=%s prbsstat Interval=%s poll every %ss\n' \
"${PRBS_POLY}" "${PRBS_STAT_INTERVAL}" "${POLL_INTERVAL_SEC}"
printf ' a poll is PASS only when the output contains "%s"\n' "${PASS_PATTERN}"
printf '#############################################################\n\n'
render_port "${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}" "${RAW_A}"
render_port "${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}" "${RAW_B}"
# Taken after prbsstat STOp + prbs clear, so this is the recovered
# state. During the test the same command would have reported DOWN.
printf -- '--- show interfaces status %s,%s (after prbsstat STOp + prbs clear) ---\n' \
"${PORT_A_NAME}" "${PORT_B_NAME}"
${SUDO} show interfaces status "${PORT_A_NAME},${PORT_B_NAME}" </dev/null 2>&1
} >> "${LOG_FILE}"
}
###############################################################################
# Sub-commands
###############################################################################
prepare_log_on_start() {
local stamp
case "${START_LOG_MODE}" in
append) return 0 ;;
archive)
if [ -s "${LOG_FILE}" ]; then
stamp="$(date '+%Y%m%d-%H%M%S')"
mv -f "${LOG_FILE}" "${LOG_FILE}.${stamp}" || die "cannot archive ${LOG_FILE}"
fi ;;
new)
[ -f "${LOG_FILE}" ] && { : > "${LOG_FILE}" || die "cannot truncate ${LOG_FILE}"; } ;;
*) die "invalid START_LOG_MODE: ${START_LOG_MODE}" ;;
esac
}
# do_start [both|a|b|<port name>]
do_start() {
local which="${1:-${PORTS}}" pids pa="-" pb="-" run_a=0 run_b=0
case "${which}" in
both|BOTH|all) run_a=1; run_b=1 ;;
a|A|"${PORT_A_NAME}") run_a=1 ;;
b|B|"${PORT_B_NAME}") run_b=1 ;;
*) die "unknown port selector '${which}' (use: both | a | b | ${PORT_A_NAME} | ${PORT_B_NAME})" ;;
esac
pids="$(is_running)" && die "already running (pids=${pids})"
command -v "${BCMCMD}" >/dev/null 2>&1 || die "${BCMCMD} not found"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
prepare_log_on_start
# Only the selected ports are truncated. A raw with no "PRBS START" line is
# what the report uses to tell "not run this session" from "ran and failed".
[ "${run_a}" -eq 1 ] && : > "${RAW_A}"
[ "${run_b}" -eq 1 ] && : > "${RAW_B}"
printf '%s\n' "$(ts)" > "${STATE_FILE}"
# One worker per selected port; with both, they run at the same time.
# Invoked through bash so the file does not need the execute bit.
if [ "${run_a}" -eq 1 ]; then
setsid bash "${SCRIPT_PATH}" __worker \
"${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}" "${RAW_A}" >/dev/null 2>&1 &
pa=$!
disown "${pa}" 2>/dev/null
fi
if [ "${run_b}" -eq 1 ]; then
setsid bash "${SCRIPT_PATH}" __worker \
"${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}" "${RAW_B}" >/dev/null 2>&1 &
pb=$!
disown "${pb}" 2>/dev/null
fi
printf '%s\n%s\n' "${pa}" "${pb}" > "${PID_FILE}"
sleep 1
[ "${run_a}" -eq 1 ] && { kill -0 "${pa}" 2>/dev/null || die "${PORT_A_NAME} worker failed, see ${RAW_A}"; }
[ "${run_b}" -eq 1 ] && { kill -0 "${pb}" 2>/dev/null || die "${PORT_B_NAME} worker failed, see ${RAW_B}"; }
printf 'started (%s pid=%s, %s pid=%s)\nlog: %s\n' \
"${PORT_A_NAME}" "${pa}" "${PORT_B_NAME}" "${pb}" "${LOG_FILE}"
# Deliberately NOT printing "show interfaces status" here. With PRBS armed
# the link is out of normal operation and reports DOWN, which reads as a
# failure to anyone glancing at the console. The status is shown in the
# report instead, once PRBS has been cleared.
}
do_stop() {
local pids pa pb i
pids="$(is_running)" || { printf 'not running\n'; rm -f "${PID_FILE}"; return 0; }
pa="${pids% *}"; pb="${pids#* }"
# TERM lets each worker finish its poll and run prbsstat STOp / prbs clear.
[[ "${pa}" =~ ^[0-9]+$ ]] && kill -TERM "${pa}" 2>/dev/null
[[ "${pb}" =~ ^[0-9]+$ ]] && kill -TERM "${pb}" 2>/dev/null
for (( i = 0; i < 120; i++ )); do
kill -0 "${pa}" 2>/dev/null || kill -0 "${pb}" 2>/dev/null || break
sleep 0.5
done
if kill -0 "${pa}" 2>/dev/null || kill -0 "${pb}" 2>/dev/null; then
printf 'workers did not exit in time, sending SIGKILL (PRBS may be left running)\n' >&2
kill -KILL "${pa}" 2>/dev/null
kill -KILL "${pb}" 2>/dev/null
fi
write_report
rm -f "${PID_FILE}"
printf 'stopped\nreport: %s\n' "${LOG_FILE}"
}
do_report() {
local name raw pass fail polls verdict
printf '%-14s %-8s %-8s %-8s %s\n' PORT POLLS PASS FAIL RESULT
printf '%-14s %-8s %-8s %-8s %s\n' -------------- -------- -------- -------- ------
for spec in "${PORT_A_NAME}:${RAW_A}" "${PORT_B_NAME}:${RAW_B}"; do
name="${spec%%:*}"; raw="${spec#*:}"
set -- $(port_tally "${name}" "${raw}")
printf '%-14s %-8s %-8s %-8s %s\n' "${name}" "$1" "$2" "$3" "$4"
done
}
do_status() {
local pids
if pids="$(is_running)"; then
printf 'status : running (%s pid=%s, %s pid=%s)\n' \
"${PORT_A_NAME}" "${pids% *}" "${PORT_B_NAME}" "${pids#* }"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
do_report
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} {start [both|a|b]|stop|status|report|clear}
start Set up PRBS and poll in the background. With no argument it runs
\$PORTS (currently "${PORTS}"); "a" or "b" runs that port alone,
which is how you tell a genuine port fault from the DUT not coping
with two PRBS streams at once.
start both ports at the same time
start a ${PORT_A_NAME} only
start b ${PORT_B_NAME} only
Port status is NOT shown here: with PRBS armed the link reports
DOWN, which looks like a failure. See the report instead.
stop Stop polling, run prbsstat STOp + prbs clear on both ports, and
render the report into the log
report Print the PASS/FAIL tally per port
status Show worker pids plus the current tally
clear Remove the log and raw captures (must be stopped first)
Log file : ${LOG_FILE}
Raw : ${RAW_A}
${RAW_B}
A poll is PASS only when "phy diag <phy> prbs get" reports "${PASS_PATTERN}".
prbsstat Ber is captured alongside every poll but does not decide the verdict.
A port that was not run in this session reports SKIP, not FAIL.
EOF
}
###############################################################################
# Entry point
###############################################################################
case "${1:-}" in
start) shift; do_start "${1:-${PORTS}}" ;;
stop) do_stop ;;
status) do_status ;;
report) do_report ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}" "${LOG_FILE}".[0-9]* "${RAW_A}" "${RAW_B}" "${STATE_FILE}"
printf 'log cleared\n' ;;
__worker)
shift
do_worker "$1" "$2" "$3" "$4" ;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac