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

265 lines
11 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.
###############################################################################
#
# mpiexec_deadlock_analysis.sh — Deploy attach_deadlock_analysis.sh to every node
# in the MPI job via mpiexec (one rank per node), collect results into a
# shared output directory, then perform a cross-rank coalescing pass that
# highlights backtrace patterns uncommon among ranks.
#
# Usage:
# ./mpiexec_deadlock_analysis.sh <executable_name> [options] [-- <mpiexec-args>...]
#
# Options:
# --directory <dir> Output directory. With a shared filesystem (default)
# this must be visible from all nodes at the same path.
# With --no-shared-fs it is a local path on the head node
# only (default: ./rocshmem_deadlock_analysis_<timestamp>)
# --cull Cull groups stuck in GPU barriers / gridsync
# --cull=p1,p2,... Cull groups matching 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
# --no-color Disable ANSI color output
# --no-coalesce Skip the cross-rank coalescing pass
# --no-shared-fs Do not assume a shared filesystem. Each node writes to a
# local /tmp directory; mpiexec --tag-output is used to stream
# all output to the head node, where it is reconstructed into
# per-PE files before the coalescing pass.
# -- <mpiexec-args>... All arguments after -- are forwarded verbatim to
# mpiexec (e.g. -np 4 -H node1,node2 --hostfile hosts.txt)
#
# mpiexec is invoked with -pernode, placing exactly one rank per allocated
# node. The node set is determined by the scheduler allocation (SLURM, PBS)
# or by the arguments supplied after --.
#
# Environment variables:
# MPIEXEC mpiexec binary to use (default: mpiexec)
# ROCGDB Path to rocgdb binary (default: rocgdb)
# ROCSHMEM_GDB_TIMEOUT Per-process timeout in seconds (default: 120)
#
# Examples:
# ./mpiexec_deadlock_analysis.sh rocshmem_functional_tests \
# --directory /shared/analysis --cull
# # No shared filesystem — output streamed via mpiexec --tag-output:
# ./mpiexec_deadlock_analysis.sh rocshmem_functional_tests \
# --no-shared-fs --directory /tmp/local_analysis
# # Explicit node list via mpiexec arguments:
# ./mpiexec_deadlock_analysis.sh rocshmem_functional_tests \
# --directory /shared/analysis -- -np 4 -H node1,node2,node3,node4
# ./mpiexec_deadlock_analysis.sh rocshmem_functional_tests \
# -- --hostfile /etc/mpi/hosts
###############################################################################
set -o pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ATTACH_SCRIPT="${SCRIPT_DIR}/attach_deadlock_analysis.sh"
COALESCE_SCRIPT="${SCRIPT_DIR}/cross_rank_deadlock_analysis.py"
# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
EXECUTABLE="${1:-}"
if [[ -z "${EXECUTABLE}" ]]; then
echo "Usage: $0 <executable_name> [--directory <dir>] [--cull[=...]] [-- <mpiexec-args>...]" >&2
exit 1
fi
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUTPUT_DIR=""
CULL_ARG=""
CHECK_LANES_ARG=""
STATS_ARG=""
COLOR_ARG=""
RUN_COALESCE=1
NO_SHARED_FS=0
MPIEXEC_EXTRA_ARGS=()
_args=("${@:2}")
_i=0
while [[ ${_i} -lt ${#_args[@]} ]]; do
_arg="${_args[${_i}]}"
if [[ "${_arg}" == "--" ]]; then
# Everything after -- is forwarded verbatim to mpiexec
MPIEXEC_EXTRA_ARGS=("${_args[@]:$(( _i + 1 ))}")
break
elif [[ "${_arg}" == "--directory" ]]; then
(( _i++ ))
OUTPUT_DIR="${_args[${_i}]:-}"
[[ -z "${OUTPUT_DIR}" ]] && { echo "ERROR: --directory requires an argument." >&2; exit 1; }
elif [[ "${_arg}" == --directory=* ]]; then
OUTPUT_DIR="${_arg#--directory=}"
[[ -z "${OUTPUT_DIR}" ]] && { echo "ERROR: --directory= requires a value." >&2; exit 1; }
elif [[ "${_arg}" == "--cull" ]]; then
CULL_ARG="--cull"
elif [[ "${_arg}" == --cull=* ]]; then
CULL_ARG="${_arg}"
elif [[ "${_arg}" == "--check-lanes" ]]; then
CHECK_LANES_ARG="--check-lanes"
elif [[ "${_arg}" == "--stats" ]]; then
STATS_ARG="--stats"
elif [[ "${_arg}" == "--color" ]]; then
COLOR_ARG="--color"
elif [[ "${_arg}" == "--no-color" ]]; then
COLOR_ARG="--no-color"
elif [[ "${_arg}" == "--no-coalesce" ]]; then
RUN_COALESCE=0
elif [[ "${_arg}" == "--no-shared-fs" ]]; then
NO_SHARED_FS=1
else
echo "ERROR: Unknown option '${_arg}'." >&2
exit 1
fi
(( _i++ ))
done
OUTPUT_DIR="${OUTPUT_DIR:-./rocshmem_deadlock_analysis_${TIMESTAMP}}"
MPIEXEC="${MPIEXEC:-mpiexec}"
# ---------------------------------------------------------------------------
# Prerequisite checks
# ---------------------------------------------------------------------------
for _req in "${ATTACH_SCRIPT}" "${COALESCE_SCRIPT}"; do
if [[ ! -f "${_req}" ]]; then
echo "ERROR: Required script not found: ${_req}" >&2
exit 1
fi
done
if ! command -v "${MPIEXEC}" >/dev/null 2>&1; then
echo "ERROR: '${MPIEXEC}' not found in PATH." >&2
echo " Set the MPIEXEC environment variable to the full path." >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: python3 not found in PATH (required for cross-rank coalescing)." >&2
exit 1
fi
mkdir -p "${OUTPUT_DIR}" || { echo "ERROR: Cannot create output directory: ${OUTPUT_DIR}" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Deploy attach_deadlock_analysis.sh on each node via mpiexec
# ---------------------------------------------------------------------------
echo "Deploying deadlock analysis..."
echo "Executable: ${EXECUTABLE}"
echo "Output directory: ${OUTPUT_DIR}"
[[ ${NO_SHARED_FS} -eq 1 ]] && echo "Filesystem mode: no shared fs (--output-filename)"
[[ -n "${CULL_ARG}" ]] && echo "Cull option: ${CULL_ARG}"
[[ -n "${CHECK_LANES_ARG}" ]] && echo "Check lanes: yes"
[[ -n "${STATS_ARG}" ]] && echo "Stats: yes"
[[ -n "${COLOR_ARG}" ]] && echo "Color: ${COLOR_ARG}"
[[ ${#MPIEXEC_EXTRA_ARGS[@]} -gt 0 ]] && echo "mpiexec args: ${MPIEXEC_EXTRA_ARGS[*]}"
echo ""
# In no-shared-fs mode each node writes to a local /tmp directory (same path
# on every node, based on the timestamp) and emits file contents to stdout via
# the --stdout flag. mpiexec --output-filename captures each rank's stdout
# into a separate file on the head node.
if [[ ${NO_SHARED_FS} -eq 1 ]]; then
_node_local_dir="/tmp/rocshmem_deadlock_${TIMESTAMP}"
_mpi_output_prefix="${OUTPUT_DIR}/mpi_rank_output"
_attach_args=("${EXECUTABLE}" "--directory" "${_node_local_dir}" "--stdout")
else
_attach_args=("${EXECUTABLE}" "--directory" "${OUTPUT_DIR}")
fi
[[ -n "${CULL_ARG}" ]] && _attach_args+=("${CULL_ARG}")
[[ -n "${CHECK_LANES_ARG}" ]] && _attach_args+=("${CHECK_LANES_ARG}")
[[ -n "${STATS_ARG}" ]] && _attach_args+=("${STATS_ARG}")
[[ -n "${COLOR_ARG}" ]] && _attach_args+=("${COLOR_ARG}")
# -pernode launches exactly one process per allocated node (supported by
# OpenMPI, MPICH, and Intel MPI). ROCGDB and ROCSHMEM_GDB_TIMEOUT are
# forwarded automatically via the inherited environment.
# MPIEXEC_EXTRA_ARGS (from arguments after --) supply the node list / count,
# e.g. -np 4 -H node1,node2 or --hostfile hosts.txt.
if [[ ${NO_SHARED_FS} -eq 1 ]]; then
# --output-filename <prefix> writes each rank's stdout to
# <prefix>.<jobid>.<rank> on the head node (OpenMPI convention).
"${MPIEXEC}" "${MPIEXEC_EXTRA_ARGS[@]}" -pernode \
--output-filename "${_mpi_output_prefix}" \
bash "${ATTACH_SCRIPT}" "${_attach_args[@]}"
else
"${MPIEXEC}" "${MPIEXEC_EXTRA_ARGS[@]}" -pernode \
bash "${ATTACH_SCRIPT}" "${_attach_args[@]}"
fi
MPIEXEC_EXIT=$?
echo ""
if [[ ${MPIEXEC_EXIT} -ne 0 ]]; then
echo "WARNING: mpiexec exited with status ${MPIEXEC_EXIT}." \
"Some nodes may have failed." >&2
fi
# ---------------------------------------------------------------------------
# Reconstruct pe*_pid_*.txt files from mpiexec --output-filename captures
# ---------------------------------------------------------------------------
if [[ ${NO_SHARED_FS} -eq 1 ]]; then
echo "Reconstructing per-PE output files from mpiexec output..."
_reconstructed=0
# OpenMPI names files <prefix>.<jobid>.<rank>; glob broadly to handle
# varying formats across MPI implementations.
for _rank_file in "${_mpi_output_prefix}"*; do
[[ -f "${_rank_file}" ]] || continue
# Extract each embedded pe*_pid_*.txt block using awk.
# fname is validated against the expected pattern before use to
# prevent a compromised node from writing outside OUTPUT_DIR via
# a path-traversal payload in the BEGIN FILE marker.
awk -v outdir="${OUTPUT_DIR}" '
/^=== BEGIN FILE: / {
fname = $4
if (fname !~ /^pe[A-Za-z0-9]*_pid_[0-9]+\.txt$/) { fname = ""; next }
next
}
/^=== END FILE: / { fname = ""; next }
fname != "" { print > (outdir "/" fname) }
' "${_rank_file}"
(( _reconstructed++ )) || true
done
if [[ ${_reconstructed} -eq 0 ]]; then
echo "WARNING: No mpiexec output files found under ${_mpi_output_prefix}*" >&2
else
echo "Reconstructed output from ${_reconstructed} rank file(s)."
fi
echo ""
fi
# ---------------------------------------------------------------------------
# Cross-rank coalescing pass
# ---------------------------------------------------------------------------
if [[ ${RUN_COALESCE} -eq 1 ]]; then
echo "Running cross-rank coalescing pass..."
echo ""
_coalesce_args=("${OUTPUT_DIR}")
[[ -n "${COLOR_ARG}" ]] && _coalesce_args+=("${COLOR_ARG}")
python3 "${COALESCE_SCRIPT}" "${_coalesce_args[@]}"
fi