blob: 8aabde07c48f938c8df7d1fa3df80aa1c0991254 [file] [log] [blame]
#!/bin/bash
# Copyright 2026 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT
# grind - An opinionated tool for running many instances of a command in parallel, each in its own
# GNU screen session, with logging to per-session files.
#
# The primary use of this tool is for running many "QEMU loops" in order to flush out bugs due to
# race conditions.
#
# For example,
#
# ```
# $ grind -n 64 -d ./logs/ "cd $HOME/fuchsia; while true; do \
# fx qemu --arch x64 -z out/core.x64-balanced/obj/zircon/system/utest/core/core-tests.zbi \
# -- -no-reboot; done"
# ```
#
# will start 64 instances of a loop that runs the core-tests. Each instance is automatically place
# in its own GNU screen session so that if the instance locks up, you can attach and break-in using
# the QEMU monitor. GNU screen also provides logging for each instance (the -d argument).
#
# To list the names of the running sessions,
#
# ```
# $ screen -ls
# There are screens on:
# 1246089.grind.64 (03/04/2026 11:35:03 PM) (Detached)
# 1245882.grind.63 (03/04/2026 11:35:03 PM) (Detached)
# 1245302.grind.62 (03/04/2026 11:35:03 PM) (Detached)
# 1245129.grind.61 (03/04/2026 11:35:03 PM) (Detached)
# ...
# ```
#
# To attach to one of them,
#
# ```
# $ screen -dr 1245882.grind.63
# ```
#
# To quickly kill them all,
#
# ```
# $ screen -ls | awk '/[0-9]+\.grind\.[0-9]+/ {print $1}' | xargs -I{} screen -XS {} quit
# ```
#
set -e
main () {
re_number='^[0-9]+$'
while getopts ":n:d:" opt; do
case "${opt}" in
n)
num_instances=${OPTARG}
re='^[0-9]+$'
if ! [[ $num_instances =~ $re_number ]] ; then
echo "error: ${num_instances} is not a number"
echo
usage
fi
;;
d)
log_dir=${OPTARG}
if [ ! -d "${log_dir}" ]; then
echo "error: directory ${log_dir} does not exist"
exit 1
fi
;;
*)
echo "error: unexpected arguments"
echo
usage
;;
esac
done
shift $((OPTIND-1))
cmd="${*}"
if [ -z "${num_instances}" ] || [ -z "${log_dir}" ] || [ -z "${cmd}" ]; then
usage
fi
echo "running ${num_instances} instances, logging to ${log_dir}..."
for i in `seq -w 1 ${num_instances}`; do
log=${log_dir}/grind-${i}.log
screen -d -m -S grind.${i} -L -Logfile ${log} bash -c "${cmd}"
done
}
usage() {
>&2
echo "Runs multiple instances of a command in parallel under screen sessions."
echo
echo "Usage: $0 -n <NUM> -d <DIR> <COMMAND STRING>"
echo " -n <NUM> the number of instances to run in parallel"
echo " -d <DIR> the directory to which screen logs should be written"
echo " <COMMAND STRING> the bash command string (think \"-c foo\") to run"
echo
echo "Example:"
echo " $0 -n 10 -d ./logs \"while true; do ./unit-test; done\""
echo
exit 1
}
main $@