Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

370 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
###############################################################################
# Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
###############################################################################
#
# attach_deadlock_analysis.sh — Attach rocgdb to every running instance of an
# executable, run the rocSHMEM deadlock analysis script on each, save full
# output to per-process files, and print a compact summary to stdout.
#
# Usage:
# ./attach_deadlock_analysis.sh <executable_name> [options]
#
# Options:
# --directory <dir> Output directory (default: rocshmem_deadlock_analysis_<timestamp>)
# --cull Cull groups stuck in GPU barriers / gridsync (built-in patterns)
# --cull=p1,p2,... Cull groups whose backtrace contains any of the given substrings
# --check-lanes Enable per-lane register divergence check inside each group
# --stats Print per-group wavefront statistics
# --color Force ANSI color output (default when stdout is a tty)
# --no-color Disable ANSI color output
# --stdout After analysis, emit all output files to stdout wrapped in
# BEGIN/END FILE markers (used by mpiexec --tag-output collection)
#
# Options (via environment variables):
# ROCGDB Path to rocgdb binary (default: rocgdb)
# ROCSHMEM_GDB_TIMEOUT Per-process timeout in seconds (default: 120)
#
# Examples:
# ./attach_deadlock_analysis.sh rocshmem_functional_tests
# ./attach_deadlock_analysis.sh rocshmem_functional_tests --directory /tmp/my_analysis
# ./attach_deadlock_analysis.sh rocshmem_functional_tests --cull
# ./attach_deadlock_analysis.sh rocshmem_functional_tests --check-lanes --stats
# ./attach_deadlock_analysis.sh rocshmem_functional_tests --directory /tmp/my_analysis --cull=__syncthreads,cooperative_groups
# ROCSHMEM_GDB_TIMEOUT=60 ./attach_deadlock_analysis.sh my_app
###############################################################################
set -o pipefail
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GDB_SCRIPT="${SCRIPT_DIR}/rocgdb_deadlock_analysis.py"
EXECUTABLE="${1:-}"
if [[ -z "${EXECUTABLE}" ]]; then
echo "Usage: $0 <executable_name> [--directory <dir>] [--cull[=p1,p2,...]] [--check-lanes] [--stats] [--color|--no-color]" >&2
echo " Attaches rocgdb to all running <executable_name> processes on this host," >&2
echo " runs the rocSHMEM deadlock analysis, and saves output to the output dir." >&2
exit 1
fi
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUTPUT_DIR=""
CULL_ENV=""
CHECK_LANES_ENV="0"
STATS_ENV="0"
COLOR_ENV=""
STDOUT_MODE="0"
# Parse options from remaining arguments
_args=("${@:2}")
_i=0
while [[ ${_i} -lt ${#_args[@]} ]]; do
_arg="${_args[${_i}]}"
if [[ "${_arg}" == "--directory" ]]; then
(( _i++ ))
OUTPUT_DIR="${_args[${_i}]:-}"
if [[ -z "${OUTPUT_DIR}" ]]; then
echo "ERROR: --directory requires an argument." >&2
exit 1
fi
elif [[ "${_arg}" == --directory=* ]]; then
OUTPUT_DIR="${_arg#--directory=}"
elif [[ "${_arg}" == "--cull" ]]; then
CULL_ENV="1"
elif [[ "${_arg}" == --cull=* ]]; then
CULL_ENV="${_arg#--cull=}"
elif [[ "${_arg}" == "--check-lanes" ]]; then
CHECK_LANES_ENV="1"
elif [[ "${_arg}" == "--stats" ]]; then
STATS_ENV="1"
elif [[ "${_arg}" == "--color" ]]; then
COLOR_ENV="always"
elif [[ "${_arg}" == "--no-color" ]]; then
COLOR_ENV="never"
elif [[ "${_arg}" == "--stdout" ]]; then
STDOUT_MODE="1"
else
echo "ERROR: Unknown option '${_arg}'." >&2
exit 1
fi
(( _i++ ))
done
OUTPUT_DIR="${OUTPUT_DIR:-rocshmem_deadlock_analysis_${TIMESTAMP}}"
ROCGDB="${ROCGDB:-rocgdb}"
GDB_TIMEOUT="${ROCSHMEM_GDB_TIMEOUT:-120}"
if [[ ! "${GDB_TIMEOUT}" =~ ^[0-9]+$ ]]; then
echo "ERROR: ROCSHMEM_GDB_TIMEOUT must be a non-negative integer, got: '${GDB_TIMEOUT}'" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Color support for the script's own output
# ---------------------------------------------------------------------------
# Resolve color mode: explicit flag > env var > tty auto-detect
_use_color=0
if [[ "${COLOR_ENV}" == "always" ]]; then
_use_color=1
elif [[ "${COLOR_ENV}" == "never" ]]; then
_use_color=0
elif [[ -t 1 ]]; then
_use_color=1
fi
if [[ ${_use_color} -eq 1 ]]; then
_c_reset=$'\033[0m'
_c_ok=$'\033[1;32m' # bold green — OK
_c_warn=$'\033[1;33m' # bold yellow — WARN
_c_bad=$'\033[1;31m' # bold red — TIMEOUT / SKIP / bad counts
_c_header=$'\033[1;34m' # bold blue — section headers
_c_hint=$'\033[1;36m' # bold cyan — HINT text (matches rocgdb)
_c_fn=$'\033[1;32m' # bold green — rocSHMEM function name
else
_c_reset=''
_c_ok=''
_c_warn=''
_c_bad=''
_c_header=''
_c_hint=''
_c_fn=''
fi
# ---------------------------------------------------------------------------
# Prerequisite checks
# ---------------------------------------------------------------------------
if [[ ! -f "${GDB_SCRIPT}" ]]; then
echo "ERROR: GDB analysis script not found: ${GDB_SCRIPT}" >&2
exit 1
fi
if ! command -v "${ROCGDB}" >/dev/null 2>&1; then
echo "ERROR: '${ROCGDB}' not found in PATH." >&2
echo " Set the ROCGDB environment variable to the full path of rocgdb." >&2
exit 1
fi
if ! command -v timeout >/dev/null 2>&1; then
echo "ERROR: 'timeout' command not found (required for per-process timeout)." >&2
exit 1
fi
mkdir -p "${OUTPUT_DIR}" || {
echo "ERROR: Cannot create output directory: ${OUTPUT_DIR}" >&2
exit 1
}
# ---------------------------------------------------------------------------
# Discover PIDs
# ---------------------------------------------------------------------------
PIDS=()
# Use ps 'args' (full command line) rather than 'comm' (truncated to 15 chars)
# so that long executable names like rocshmem_functional_tests are matched.
# Strip any leading path from argv[0] before comparing.
while IFS= read -r pid; do
[[ -n "${pid}" ]] && PIDS+=("${pid}")
done < <(ps -eo pid,args 2>/dev/null \
| awk -v exe="${EXECUTABLE}" \
'NR>1 { cmd=$2; sub(".*/","",cmd); if (cmd==exe) print $1 }')
if [[ ${#PIDS[@]} -eq 0 ]]; then
echo "No processes found matching executable '${EXECUTABLE}'." >&2
echo "Ensure the application is running on this host." >&2
exit 1
fi
echo "Found ${#PIDS[@]} process(es) matching '${EXECUTABLE}': ${PIDS[*]}"
echo "Output directory: ${OUTPUT_DIR}"
echo "Per-process timeout: ${GDB_TIMEOUT}s"
echo ""
# ---------------------------------------------------------------------------
# Attach and analyze each process
# ---------------------------------------------------------------------------
ANALYZED=0
FAILED=0
for PID in "${PIDS[@]}"; do
# Read the MPI PE rank from the process environment (null-separated entries).
# Falls back to "unknown" if the process has already exited or is not an MPI rank.
PE_RANK="$(tr '\0' '\n' < "/proc/${PID}/environ" 2>/dev/null \
| sed -n 's/^OMPI_COMM_WORLD_RANK=//p' \
| tr -cd '0-9' | head -c 10)"
PE_RANK="${PE_RANK:-unknown}"
OUT_FILE="${OUTPUT_DIR}/pe${PE_RANK}_pid_${PID}.txt"
printf "Analyzing PID %s (PE %s) ... " "${PID}" "${PE_RANK}"
# Check the process still exists before attempting to attach
if ! kill -0 "${PID}" 2>/dev/null; then
echo "SKIP (process no longer exists)"
echo "SKIP: PID ${PID} exited before attach" >> "${OUT_FILE}"
(( FAILED++ )) || true
continue
fi
# Run rocgdb in batch mode.
# ROCSHMEM_DEADLOCK_AUTO_ANALYZE=1 causes the Python script to run the
# analysis immediately upon attach (the process is stopped on attach).
_gdb_env=(
ROCSHMEM_DEADLOCK_AUTO_ANALYZE=1
ROCSHMEM_DEADLOCK_CULL="${CULL_ENV}"
ROCSHMEM_DEADLOCK_CHECK_LANES="${CHECK_LANES_ENV}"
ROCSHMEM_DEADLOCK_STATS="${STATS_ENV}"
)
[[ -n "${COLOR_ENV}" ]] && _gdb_env+=(ROCSHMEM_DEADLOCK_COLOR="${COLOR_ENV}")
timeout "${GDB_TIMEOUT}" \
env "${_gdb_env[@]}" \
"${ROCGDB}" -batch \
-p "${PID}" \
-x "${GDB_SCRIPT}" \
>"${OUT_FILE}" 2>&1
GDB_EXIT=$?
# Check if we got useful output regardless of exit code
# (rocgdb often returns non-zero even on success in batch mode)
if grep -q 'Unique backtrace groups:' "${OUT_FILE}" 2>/dev/null; then
printf "${_c_ok}OK${_c_reset}\n"
(( ANALYZED++ )) || true
elif [[ ${GDB_EXIT} -eq 124 ]]; then
printf "${_c_bad}TIMEOUT${_c_reset} (${GDB_TIMEOUT}s)\n"
echo "TIMEOUT: rocgdb did not finish within ${GDB_TIMEOUT}s" >> "${OUT_FILE}"
(( FAILED++ )) || true
elif grep -q 'No GPU wavefront threads found' "${OUT_FILE}" 2>/dev/null; then
printf "${_c_ok}OK${_c_reset} (no GPU threads)\n"
(( ANALYZED++ )) || true
else
printf "${_c_warn}WARN${_c_reset} (rocgdb exit=${GDB_EXIT}; see ${OUT_FILE})\n"
(( FAILED++ )) || true
fi
done
# ---------------------------------------------------------------------------
# Compact stdout summary
# ---------------------------------------------------------------------------
echo ""
printf "${_c_header}=== Attach and Analyze Summary ===${_c_reset}\n"
printf "Processes analyzed: %d / %d\n" "${ANALYZED}" "${#PIDS[@]}"
echo ""
for PID in "${PIDS[@]}"; do
PE_RANK="$(tr '\0' '\n' < "/proc/${PID}/environ" 2>/dev/null \
| sed -n 's/^OMPI_COMM_WORLD_RANK=//p' \
| tr -cd '0-9' | head -c 10)"
PE_RANK="${PE_RANK:-unknown}"
OUT_FILE="${OUTPUT_DIR}/pe${PE_RANK}_pid_${PID}.txt"
# If the process has exited since the analysis loop, fall back to any file
# matching this PID (the rank may have been resolved then).
if [[ ! -f "${OUT_FILE}" ]]; then
OUT_FILE="$(ls "${OUTPUT_DIR}"/pe*_pid_${PID}.txt 2>/dev/null | head -1)"
fi
if [[ -z "${OUT_FILE}" || ! -f "${OUT_FILE}" ]]; then
printf " PE %-4s PID %-8s ${_c_bad}SKIPPED${_c_reset} (no output file)\n" "${PE_RANK}" "${PID}"
continue
fi
# Extract group count from the analysis output
# (avoid using $GROUPS which is a bash special variable for the user's GIDs)
NUM_GROUPS="$(grep 'Unique backtrace groups:' "${OUT_FILE}" 2>/dev/null \
| awk '{print $NF}' | head -1)"
NUM_GROUPS="${NUM_GROUPS:-?}"
# Strip ANSI when extracting text for sorting/matching; re-apply the
# script's own colors for display so the summary is consistently colored
# regardless of whether rocgdb wrote colors into the output file.
_strip_ansi='s/\x1b\[[0-9;]*m//g'
# Always extract wavefront count inside rocSHMEM from the Summary section.
ROCSHMEM_WFS="$(grep 'wavefront(s) inside rocSHMEM' "${OUT_FILE}" 2>/dev/null \
| sed "${_strip_ansi}" | awk '{print $1}' | head -1)"
# Extract the dominant rocSHMEM API function (most frequently occurring).
STUCK_FN="$(grep '\[rocSHMEM\] Stuck in:' "${OUT_FILE}" 2>/dev/null \
| sed "${_strip_ansi}" \
| sort | uniq -c | sort -rn | head -1 \
| sed 's/^[[:space:]]*[0-9]*[[:space:]]*//' \
| sed 's/.*\[rocSHMEM\] Stuck in:[[:space:]]*//')"
# Determine the dominant stuck pattern from [HINT] lines
# (most frequently occurring hint).
HINT="$(grep '\[HINT\]' "${OUT_FILE}" 2>/dev/null \
| sed "${_strip_ansi}" \
| sort | uniq -c | sort -rn | head -1 \
| sed 's/^[[:space:]]*[0-9]*[[:space:]]*//' \
| sed 's/^\[HINT\][[:space:]]*//')"
# Apply script-level colors to the extracted plain-text fields.
[[ -n "${STUCK_FN}" ]] && STUCK_FN="${_c_fn}${STUCK_FN}${_c_reset}"
[[ -n "${HINT}" ]] && HINT="${_c_hint}${HINT}${_c_reset}"
if [[ -n "${ROCSHMEM_WFS}" ]]; then
DOMINANT="${_c_bad}${ROCSHMEM_WFS}${_c_reset} wf(s) in ${STUCK_FN:-rocSHMEM}"
if [[ -n "${HINT}" ]]; then
DOMINANT="${DOMINANT}; ${HINT}"
else
DOMINANT="${DOMINANT}; No deadlock hint available"
fi
elif grep -q 'No GPU wavefront threads found' "${OUT_FILE}" 2>/dev/null; then
DOMINANT="(no GPU threads found)"
elif grep -q 'SKIP\|TIMEOUT' "${OUT_FILE}" 2>/dev/null; then
DOMINANT="${_c_bad}$(grep -m1 'SKIP\|TIMEOUT' "${OUT_FILE}" \
| sed "${_strip_ansi}" | sed 's/^[[:space:]]*//')${_c_reset}"
else
DOMINANT="${HINT:-0 wf(s) in rocSHMEM}"
fi
printf " PE %-4s PID %-8s groups=%-3s %s\n" "${PE_RANK}" "${PID}" "${NUM_GROUPS}" "${DOMINANT}"
done
if [[ "${STDOUT_MODE}" -eq 0 ]]; then
echo ""
echo "Full output in: ${OUTPUT_DIR}/"
fi
# In --stdout mode, emit every output file to stdout wrapped in markers so that
# the caller (mpiexec --tag-output) can reconstruct the files on the head node.
if [[ "${STDOUT_MODE}" -eq 1 ]]; then
for _f in "${OUTPUT_DIR}"/pe*_pid_*.txt; do
[[ -f "${_f}" ]] || continue
echo "=== BEGIN FILE: $(basename "${_f}") ==="
cat "${_f}"
echo "=== END FILE: $(basename "${_f}") ==="
done
fi
# Exit with non-zero if any process failed analysis
[[ ${FAILED} -eq 0 ]]