You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
11 KiB

# shellcheck shell=bash
if [ -n "$__framework_sh__" ]; then return; fi
__framework_sh__=$(date)
#
# This framework simplifies the scripting of experiments
#
# To use, import the framework source file and pass all arguments to the provided main function
# source "framework.sh"
#
# main "$@"
#
# In your script, implement the following functions above main:
# - experiment_main
#
source "path_join.sh" || exit 1
source "panic.sh" || exit 1
__framework_sh__usage() {
echo "$0 [options...]"
echo ""
echo "Options:"
echo " -t,--target=<target url> Execute as client against remote URL"
echo " -e,--envfile=<file name> Load an Env File. No path and pass filename with *.env extension"
echo " -s,--serve Serve but do not run client"
echo " -d,--debug Debug under GDB but do not run client"
echo " -v,--valgrind Debug under Valgrind but do not run client"
echo " -p,--perf Run under perf. Limited to running on a baremetal Linux host!"
echo " -h,--help Display usage information"
}
# Declares application level global state
__framework_sh__initialize_globals() {
# timestamp is used to name the results directory for a particular test run
# shellcheck disable=SC2155
# shellcheck disable=SC2034
declare -gir __framework_sh__timestamp=$(date +%s)
# Globals used by parse_arguments
declare -g __framework_sh__target=""
declare -g __framework_sh__role=""
declare -g __framework_sh__envfile=""
# Configure environment variables
# shellcheck disable=SC2155
declare -gr __framework_sh__application_directory="$(dirname "$(realpath "$0"))")"
# shellcheck disable=SC2155
declare -gr __framework_sh__path=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
local -r binary_directory="$(cd "$__framework_sh__path" && cd ../../bin && pwd)"
export PATH=$binary_directory:$PATH
export LD_LIBRARY_PATH=$binary_directory:$LD_LIBRARY_PATH
}
# Parses arguments from the user and sets associates global state
__framework_sh__parse_arguments() {
for i in "$@"; do
case $i in
-t=* | --target=*)
if [[ "$__framework_sh__role" == "server" ]]; then
echo "Cannot set target when server"
__framework_sh__usage
return 1
fi
__framework_sh__role=client
__framework_sh__target="${i#*=}"
shift
;;
-s | --serve)
if [[ "$__framework_sh__role" == "client" ]]; then
echo "Cannot use -s,--serve with -t,--target"
__framework_sh__usage
return 1
fi
__framework_sh__role=server
shift
;;
-d | --debug)
if [[ "$__framework_sh__role" == "client" ]]; then
echo "Cannot use -d,--debug with -t,--target"
__framework_sh__usage
return 1
fi
__framework_sh__role=debug
shift
;;
-p | --perf)
if [[ "$__framework_sh__role" == "client" ]]; then
echo "Cannot use -p,--perf with -t,--target"
__framework_sh__usage
return 1
fi
__framework_sh__role=perf
shift
;;
-v | --valgrind)
if [[ "$__framework_sh__role" == "client" ]]; then
echo "Cannot use -v,--valgrind with -t,--target"
__framework_sh__usage
return 1
fi
__framework_sh__role=valgrind
shift
;;
-e=* | --envfile=*)
if [[ "$__framework_sh__role" == "client" ]]; then
echo "Expected to be used with run by the server"
__framework_sh__usage
return 1
fi
__framework_sh__envfile="${i#*=}"
echo "Set envfile to $__framework_sh__envfile"
;;
-h | --help)
__framework_sh__usage
exit 0
;;
*)
echo "$1 is a not a valid option"
__framework_sh__usage
return 1
;;
esac
done
# default to both if no arguments were passed
if [[ -z "$__framework_sh__role" ]]; then
__framework_sh__role="both"
__framework_sh__target="localhost"
fi
# Set globals as read only
declare -r __framework_sh__target
declare -r __framework_sh__role
}
# Log hardware and software info for the execution
__framework_sh__log_environment() {
if ! command -v git &> /dev/null; then
echo "git could not be found"
exit
fi
echo "*******"
echo "* Git *"
echo "*******"
git log | head -n 1 | cut -d' ' -f2
git status
echo ""
echo "************"
echo "* Makefile *"
echo "************"
cat "$(path_join "$__framework_sh__path" ../../Makefile)"
echo ""
echo "**********"
echo "* Run.sh *"
echo "**********"
cat "$(path_join "$__framework_sh__application_directory" ./run.sh)"
echo ""
echo "************"
echo "* Hardware *"
echo "************"
lscpu
echo ""
echo "*************"
echo "* Execution *"
echo "*************"
}
# $1 - Results Directory
# $2 - How to run (foreground|background)
# $3 - JSON specification
__framework_sh__start_runtime() {
printf "Starting Runtime: "
if (($# != 3)); then
printf "[ERR]\n"
panic "invalid number of arguments \"$1\""
return 1
elif ! [[ -d "$1" ]]; then
printf "[ERR]\n"
panic "directory \"$1\" does not exist"
return 1
elif ! [[ $2 =~ ^(foreground|background)$ ]]; then
printf "[ERR]\n"
panic "expected foreground or background was \"$2\""
return 1
elif [[ ! -f "$3" || "$3" != *.json ]]; then
printf "[ERR]\n"
panic "\"$3\" does not exist or is not a JSON"
return 1
fi
local -r scheduler="$1"
local -r how_to_run="$2"
local -r specification="$3"
local -r log_name=log.txt
local log="$RESULTS_DIRECTORY/${log_name}"
__framework_sh__log_environment >> "$log"
case "$how_to_run" in
"background")
sledgert "$specification" >> "$log" 2>> "$log" &
;;
"foreground")
sledgert "$specification"
;;
esac
# Pad with a sleep to allow runtime to initialize before startup tasks run
# This should be improved adding some sort of ping/ping heartbeat to the runtime
# so the script can spin until initializaiton is complete
sleep 1
printf "[OK]\n"
return 0
}
__framework_sh__run_server() {
if (($# != 1)); then
printf "[ERR]\n"
panic "Invalid number of arguments. Saw $#. Expected 1."
return 1
elif ! [[ $1 =~ ^(foreground|background)$ ]]; then
printf "[ERR]\n"
panic "expected foreground or background was \"$3\""
return 1
fi
local -r how_to_run="$1"
__framework_sh__start_runtime "$RESULTS_DIRECTORY" "$how_to_run" "$__framework_sh__application_directory/spec.json" || {
echo "__framework_sh__start_runtime RC: $?"
panic "Error calling __framework_sh__start_runtime $RESULTS_DIRECTORY $how_to_run $__framework_sh__application_directory/spec.json"
return 1
}
return 0
}
__framework_sh__run_perf() {
if ! command -v perf; then
echo "perf is not present."
exit 1
fi
perf record -g -s sledgert "$__framework_sh__application_directory/spec.json"
}
__framework_sh__run_valgrind() {
if ! command -v valgrind; then
echo "valgrind is not present."
exit 1
fi
valgrind --leak-check=full sledgert "$__framework_sh__application_directory/spec.json"
}
# Starts the Sledge Runtime under GDB
__framework_sh__run_debug() {
# shellcheck disable=SC2155
local project_directory=$(cd ../.. && pwd)
if [[ "$project_directory" != "/sledge/runtime" ]]; then
printf "It appears that you are not running in the container. Substituting path to match host environment\n"
gdb \
--eval-command="handle SIGUSR1 noprint nostop" \
--eval-command="handle SIGPIPE noprint nostop" \
--eval-command="set pagination off" \
--eval-command="set substitute-path /sledge/runtime $project_directory" \
--eval-command="run $__framework_sh__application_directory/spec.json" \
sledgert
else
gdb \
--eval-command="handle SIGUSR1 noprint nostop" \
--eval-command="handle SIGPIPE noprint nostop" \
--eval-command="set pagination off" \
--eval-command="run $__framework_sh__application_directory/spec.json" \
sledgert
fi
return 0
}
__framework_sh__run_client() {
experiment_main "$__framework_sh__target" "$RESULTS_DIRECTORY" || return 1
return 0
}
__framework_sh__load_env_file() {
local envfile="$1"
if [[ -f "$envfile" ]]; then
while read -r line; do
echo export "${line?}"
export "${line?}"
done < "$envfile"
fi
}
__framework_sh__unset_env_file() {
local envfile="$1"
if [[ -f "$envfile" ]]; then
while read -r line; do
echo unset "${line//=*/}"
unset "${line//=*/}"
done < "$envfile"
fi
}
__framework_sh__run_both() {
local short_name
shopt -s nullglob
local -i envfiles_found=0
for envfile in "$__framework_sh__application_directory"/*.env; do
((envfiles_found++))
short_name="$(basename "${envfile/.env/}")"
printf "Running %s\n" "$short_name"
__framework_sh__load_env_file "$envfile"
__framework_sh__create_and_export_results_directory "$short_name"
__framework_sh__run_server background || {
panic "Error calling __framework_sh__run_server"
return 1
}
__framework_sh__run_client || {
__framework_sh__unset_env_file "$envfile"
__framework_sh__stop_runtime
return 1
}
__framework_sh__stop_runtime || {
panic "Error calling __framework_sh__stop_runtime"
__framework_sh__unset_env_file "$envfile"
return 1
}
__framework_sh__unset_env_file "$envfile"
done
((envfiles_found == 0)) && echo "No *.env files found. Nothing to run!"
return 0
}
# Optionally accepts a subdirectory
# This is intended to namespace distinct runtime configs run in a single command
__framework_sh__create_and_export_results_directory() {
if (($# > 1)); then
printf "[ERR]\n"
panic "Invalid number of arguments. Saw $#. Expected 0 or 1."
return 1
fi
local -r subdirectory=${1:-""}
local dir=""
# If we are running both client, and server, we need to namespace by scheduler since we run both variants
case "$__framework_sh__role" in
"both")
dir="$__framework_sh__application_directory/res/$__framework_sh__timestamp/$subdirectory"
;;
"client" | "server" | "debug" | "perf" | "valgrind")
dir="$__framework_sh__application_directory/res/$__framework_sh__timestamp"
;;
*)
panic "${FUNCNAME[0]} Unexpected $__framework_sh__role"
return 1
;;
esac
mkdir -p "$dir" || {
panic "mkdir -p $dir"
return 1
}
export RESULTS_DIRECTORY="$dir"
}
# Responsible for ensuring that the experiment file meets framework assumptions
__framework_sh__validate_experiment() {
if [[ $(type -t experiment_main) != "function" ]]; then
panic "function experiment_main was not defined"
return 1
fi
}
main() {
__framework_sh__validate_experiment || exit 1
__framework_sh__initialize_globals || exit 1
__framework_sh__parse_arguments "$@" || exit 1
__framework_sh__create_and_export_results_directory || exit 1
[[ -n "$__framework_sh__envfile" ]] && __framework_sh__load_env_file "$__framework_sh__application_directory/$__framework_sh__envfile"
case $__framework_sh__role in
both)
__framework_sh__run_both
;;
server)
__framework_sh__run_server foreground
;;
debug)
__framework_sh__run_debug
;;
perf)
__framework_sh__run_perf
;;
valgrind)
__framework_sh__run_valgrind
;;
client)
__framework_sh__run_client
;;
*)
echo "Invalid state"
false
;;
esac
return "$?"
}
__framework_sh__stop_runtime() {
printf "Stopping Runtime: "
# Ignoring RC of 1, as it indicates no matching process
pkill sledgert > /dev/null 2> /dev/null
(($? > 1)) && {
printf "[ERR]\npkill sledgrt: %d\n" $?
exit 1
}
pkill hey > /dev/null 2> /dev/null
(($? > 1)) && {
printf "[ERR]\npkill hey: %d\n" $?
exit 1
}
printf "[OK]\n"
}