#!/bin/bash
# BEGIN_ICS_COPYRIGHT8 ****************************************
#
# Copyright (c) 2015-2020, Intel Corporation
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Intel Corporation nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# END_ICS_COPYRIGHT8   ****************************************

# [ICS VERSION STRING: unknown]

# Might want to tune this later.
NBs=168

# Generate some reasonable defaults based on the current node.
default_numcores=$(lscpu | grep "^CPU(s):" | awk '{print $2}')
bytesram=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
default_megsram=$(( $bytesram / 1024 ))
default_pressure=0.3

# If we have an mpi_hosts file, use that for the default # of hosts.
if [ -e /usr/src/opa/mpi_hosts ]; then 
	default_numnodes=$(sort -u mpi_hosts | wc -l)
else
	default_numnodes=1
fi

numcores=$default_numcores
megsram=$default_megsram
pressure=$default_pressure
numnodes=$default_numnodes

USAGE() {
	echo >&2
	echo >&2 "Usage:"
	echo >&2 "	./hpl_dat_gen.sh [-2|-1][-n <nodes>][-c <cores>][-r <ram>][-p <pressure>][-d]"
	echo >&2 
	echo >&2 "  This tool generates an HPL.dat file and copies it to the appropriate hpl"
	echo >&2 "  executable directory (hpl-2.3/bin/ICS.Linux.*)"
	echo >&2 
	echo >&2 "  The HPL.dat file can be tuned for different numbers of nodes, cores per node,"
	echo >&2 "  and the approximate amount of RAM per node to use. The script chooses"
	echo >&2 "  defaults based on the characteristics of the node the script is run on, but"
	echo >&2 "  these values can be overridden either by command line options or by"
	echo >&2 "  prompting the user to enter new values."
	echo >&2 
	echo >&2 "Options:"
	echo >&2 
	echo >&2 "	--nodes <nodes>"
	echo >&2 "	-n <nodes>      # of nodes in the fabric."
	echo >&2 
	echo >&2 "	--cores <cores>" 
	echo >&2 "	-c <cores>      # of cores per node."
	echo >&2 
	echo >&2 "	--ram <ram>"
	echo >&2 "	-r <ram>        Amount of RAM per node in MB."
	echo >&2 
	echo >&2 "	--pressure <p>"
	echo >&2 "	-p <p>          Total memory pressure, expressed as a number"
	echo >&2 "	                between 0.1 and 0.9. This value scales the"
	echo >&2 "	                problem size against the amount of RAM available."
	echo >&2 "	                (For example, a pressure of 0.5 will use about"
	echo >&2 "	                half the available RAM on each node.)"
	echo >&2 
	echo >&2 "	--use-defaults"
	echo >&2 "	-d              Use defaults for unspecified values."
	echo >&2 "	                (otherwise prompt for the missing values.)"
	echo >&2 
	echo >&2 "Defaults for this system are:"
	echo >&2 " 	-2"
	echo >&2 " 	-n $default_numnodes"
	echo >&2 "	-c $default_numcores"
	echo >&2 "	-r $default_megsram"
	echo >&2 "	-p $default_pressure"
}

P=0
Q=0
HPL_PQ() {
	local cores=$1
	local pp
	local qq

	# Find values of P and Q that use every available core and are as close
	# in value as possible, with Q being slightly larger than P.
	# For example, if the # of cores is 36, the optimal (P,Q) is (4,9) rather
	# than (6,6).
	for pp in $(seq 1 $cores); do
		qq=$(( $cores / $pp))
		ll=$(( $pp * $qq))
		if [ $pp -ge $qq ]; then
			 break
		fi
		if [ $ll -eq $cores ]; then 
			P=$pp;
			Q=$qq;
		fi
	done
}

Ns=""
CALC_NS() {
	local numnodes=$1
	local megspernode=$2
	local pressure=$3

	# This calculation adjusts the size of the HPL problem to a percentage
	# of the available RAM. The formula is:
	#
	# "Take the square root of the total amount of memory in the cluster
	# divided by the number of bytes in a double precision number, then 
	# multiply by the requested memory pressure and round up to a multiple
	# of the block size."
	Ns=$(echo "sqrt($megspernode * 1024 * 1024 * $numnodes / 8) * $pressure / $NBs * $NBs" | bc)
}

inputval=""
GET_INPUT() {
	local defval=""
	local prompt="Input"
	local value=""

	if [ $# -ge 1 ]; then
		prompt=$1
	fi

	if [ $# -ge 2 ]; then
		defval=$2
	fi

	while [ -z $value ]; do
		if [ ! -z $defval ]; then
			echo -n "$prompt [$defval]? "
		else
			echo -n "$prompt? "
		fi
		read value
		if [ -z $value ]; then
			value=$defval
		fi
	done
	
	inputval=$value
}

TEMP_FILE="$(mktemp)"
trap "rm -rf $TEMP_FILE; exit 1" SIGINT SIGHUP SIGTERM
trap "rm -rf $TEMP_FILE" EXIT

arch=ICS.`uname -s`.`./get_mpi_cc.sh`

got_nodes=0
got_cores=0
got_ram=0
got_pressure=0
use_defaults=0

OPTIONS=$(getopt -o "n:c:r:p:hd12" --long "nodes:,cores:,ram:,pressure:,help,use-defaults" -- "$@")

if [ $? -ne 0 ]; then
	USAGE; exit 1
fi

eval set -- "$OPTIONS"

while true; do
	case "$1" in
		-n | --nodes ) 
			numnodes=$2; got_nodes=1;
			shift; shift;;
		-c | --cores ) 
			numcores=$2; got_cores=1;
			shift; shift;;
		-r | --ram ) 
			megsram=$2; got_ram=1;
			shift; shift;;
		-p | --pressure ) 
			pressure=$2; got_pressure=1;
			shift; shift;;
		-d | --use-defaults ) 
			use_defaults=1;
			shift;;
		-- ) 
			# End of arguments list.
			break;;
		-h | --help ) 
			USAGE; exit 0;;
		* )
			USAGE; exit 1;;
	esac
done

if ! [[ $numnodes =~ ^[0-9]+$ ]]; then
	echo >&2 "\"$numnodes\" is not a valid # of nodes"
	USAGE; exit 1
elif ! [[ $numcores =~ ^[0-9]+$ ]]; then
	echo >&2 "\"$numcores\" is not a valid # of cores"
	USAGE; exit 1
elif ! [[ $megsram =~ ^[0-9]+$ ]]; then
	echo >&2 "\"$megsram\" is not a valid amount of RAM"
	USAGE; exit 1
elif ! [[ $pressure =~ ^0\.[1-9][0-9]*$ ]]; then
	echo >&2 "\"$pressure\" is not a valid amount memory pressure."
	USAGE; exit 1
fi

while [ $got_nodes -eq 0 -a $use_defaults -eq 0 ]
do
	GET_INPUT "# of compute nodes" $default_numnodes
	numnodes=$inputval
	if [[ $numnodes =~ ^[0-9]+$ ]]; then
		got_nodes=1
	fi
done

while [ $got_cores -eq 0 -a $use_defaults -eq 0 ]
do
	GET_INPUT "# of cores per node" $default_numcores
	numcores=$inputval
	if [[ $numcores =~ ^[0-9]+$ ]]; then
		got_cores=1
	fi
done

while [ $got_ram -eq 0 -a $use_defaults -eq 0 ]
do
	GET_INPUT "# of RAM per node (in MB)" $default_megsram
	megsram=$inputval
	if [[ $megsram =~ ^[0-9]+$ ]]; then
		got_ram=1
	fi
done

while [ $got_pressure -eq 0 -a $use_defaults -eq 0 ]
do
	GET_INPUT "Memory pressure (range between 0.1 and 0.9)" $default_pressure
	pressure=$inputval
	if [[ $pressure =~ ^0\.[1-9][0-9]*$ ]]; then
		got_pressure=1
	fi
done

totnumcores=$(( $numcores * $numnodes ))

HPL_PQ $totnumcores

CALC_NS $numnodes $megsram $pressure

echo
echo "Use \"./run_hpl2 $totnumcores\" to use this configuration."
echo
echo

DATFILE=$PWD/hpl-2.3/bin/$arch/HPL.dat

cat <<EOF | tee $TEMP_FILE $DATFILE
HPLinpack benchmark input file - $totnumcores processes, $pressure memory size
Generated by hpl_dat_gen.sh.
HPL.out		output file name (if any)
6		device out (6=stdout,7=stderr,file)
1		# of problems sizes (N)
$Ns		Ns
1		# of NBs
$NBs		NBs
0		PMAP process mapping (0=Row-,1=Column-major)
1		# of process grids (P x Q)
$P		Ps
$Q		Qs
16.0		threshold
1		# of panel fact
1		PFACTs (0=left, 1=Crout, 2=Right)
1		# of recursive stopping criterium
4		NBMINs (>= 1)
1		# of panels in recursion
2		NDIVs
1		# of recursive panel fact.
2		RFACTs (0=left, 1=Crout, 2=Right)
1		# of broadcast
1		BCASTs (0=1rg,1=1rM,2=2rg,3=2rM,4=Lng,5=LnM)
1		# of lookahead depth
1		DEPTHs (>=0)
2		SWAP (0=bin-exch,1=long,2=mix)
$NBs		swapping threshold
0		L1 in (0=transposed,1=no-transposed) form
0		U  in (0=transposed,1=no-transposed) form
1		Equilibration (0=no,1=yes)
8		memory alignment in double (> 0)
EOF
