blob: 669a19dd50cb61ffca52965e61bdab6b8f2a0b2d [file]
#!/bin/bash
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Collect system-wide profile information using system_profiler.py
# for the duration of a wrapped command, logging outputs to file.
readonly script="$0"
# assume script is always with path prefix, e.g. "./$script"
readonly script_dir="${script%/*}"
readonly script_basename="${script##*/}"
function msg() {
echo >&2 "[$script_basename] $*"
}
function usage() {
cat <<END
Usage: $script \
--system-log system_logfile \
[script_args] -- command...
END
}
system_logfile=
interval=1 # seconds
prev_opt=
for opt
do
# handle --option arg
if [[ -n "$prev_opt" ]]
then
eval "$prev_opt"=\$opt
prev_opt=
shift
continue
fi
# Extract optarg from --opt=optarg
optarg=
case "$opt" in
-*=*) optarg="${opt#*=}" ;; # remove-prefix, shortest-match
esac
case "$opt" in
--system-log) prev_opt=system_logfile ;;
--system-log=*) system_logfile="$optarg" ;;
-n) prev_opt=interval ;;
-n=*) interval="$optarg" ;;
--) shift ; break ;;
*) echo "Unknown $0 option: $opt" ; usage ; exit 1 ;;
esac
shift
done
[[ -n "$system_logfile" ]] || {
echo "--system-log is required."
exit 1
}
# Everything else after '--' is the command to run.
cmd=("$@")
[[ "$#" > 0 ]] || { echo "Missing command to run (after --)." ; exit 1; }
shutdown_pids=()
if [[ -n "$system_logfile" ]]
then
rm -f "$system_logfile"
"${PREBUILT_PYTHON3:-python3}" -S -u "${script_dir}/system_profiler.py" \
--interval "$interval" \
--output "$system_logfile" \
--pid "$$" \
--metadata "FX_BUILD_UUID:${FX_BUILD_UUID:-}" &
readonly system_profiler_pid=$!
shutdown_pids+=( "$system_profiler_pid" )
fi
# Terminate system_profiler when main command is complete (or interrupted).
function shutdown() {
if [[ "${#shutdown_pids[@]}" > 0 ]]
then
if [[ "${_interrupted:-0}" == "1" ]]; then
msg "Stopping background profile collection..."
fi
kill "${shutdown_pids[@]}"
fi
}
trap shutdown EXIT
# Wait for a command while ignoring signals to ensure the parent outlives the child.
# This prevents the shell from exiting prematurely and orphaning backgrounded
# subprocesses during a signal (like Ctrl-C).
#
# Because the command is run in the same process group, signals (like SIGINT)
# are broadcast by the TTY to both the shell and the child, so no manual
# signal forwarding is required here. Successive signals will continue to reach
# the child as long as it is alive.
function wait-ignoring-signals {
local child_pid=""
local sig_count=0
# Acknowledge signals and forward them to the child.
function _signal_acknowledgement_handler {
local sig="$1"
_interrupted=1
sig_count=$((sig_count + 1))
if [[ $sig_count -eq 1 ]]; then
msg "Received ${sig}. Forwarding to child (PID: ${child_pid:-unknown}) and waiting for graceful shutdown..."
else
msg "Received ${sig} again (${sig_count}). Still waiting for cleanup..."
fi
if [[ -n "${child_pid}" ]]; then
# Signal the child. Since we are likely in a separate process group
# (due to wrappers above us or set -m below), we signal the group.
kill -"${sig}" "-${child_pid}" 2>/dev/null || true
fi
}
trap '_signal_acknowledgement_handler SIGINT' INT
trap '_signal_acknowledgement_handler SIGTERM' TERM
trap '_signal_acknowledgement_handler SIGHUP' HUP
# Run the command in a subshell with default signal dispositions.
# We use set -m to ensure the child is in its own process group,
# making it easier to signal the entire sub-tree.
set -m
( trap - INT TERM HUP ; exec "$@" ) &
child_pid=$!
set +m
local status=0
wait "${child_pid}" || status=$?
trap - INT TERM HUP
return "$status"
}
_interrupted=0
wait-ignoring-signals "${cmd[@]}"
exit "$?"