#!/bin/bash
#
# Copyright (c) 2013-2023 University of Utah and the Flux Group.
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#

echo -n ' Cpucheck..'

source checkutils.sh

x=$(caller)
[[ "${x/NULL}" = "$x" ]] && declare -ri cpucheck_standalone=0 || declare -ri cpucheck_standalone=1

declare arch="" failed="" s=""
declare -i sockets=0 cores_socket=0 threads_core=0 mhz=0 err=0
declare -i hyperthread=0  x64bit=0 hwvirt=0 cores_enabled=0 thread_count=0
declare p1="" p2="" p3=""
declare phy="" cid=" "
declare d=0
declare p p1 p3 p3 phy

# this function is only called if not running in MFS mode or error
finish() {
    echo -n "Cpucheck `date`: " >> ${logfile}
#    cat ${tmplog} >> ${logfile} 
    summary="Arch:$arch Sockets:$sockets Cores_socket:$cores_socket Threads_core:$threads_core Mhz:$mhz HT:${hyperthread} 64bit:${x64bit} HV:${hwvirt}"
    if [ -z "${failed}" ]
    then
	echo -n "$summary"
	echo "$summary" >> ${logfile}
	echo "OK"
    else
	echo "$failed"
    fi
}

round2nearest() {
    # round up 1 place
    in=$1

    #truncate decimal
    y=${in%%.*}

    # retrive the numbers to the left of the digit
    d=${in##*.}
    # what if there is only one number, times it by 10
    n=${#d}
    if [ $n == 1 ] ; then
	d=$(( $d * 10 ))
    fi
    # if the 10th and 100th digits are greater then 50 add one unit, if less then just truncate decimal
    if (( 50 >= ${d} )) ; then
	# This happens to work if $in does not have a decimal number and is greater then 50.
	echo -n $y
	return 0
    fi	
    
    #count digits
    n=${#y}
    # according to #digits add roundup and get most sig-bits
    if [ $n == 6 ] ; then
	y=$(($y + 10000))
	y=$(($y / 10000))
	y=$(($y * 10000))
	echo -n $y
    elif [ $n == 5 ] ; then
	y=$(($y + 1000))
	y=$(($y / 1000))
	y=$(($y * 1000))
	echo -n $y
    elif [ $n == 4 ] ; then
	y=$(($y + 100))
	y=$(($y / 100))
	y=$(($y * 100))
	echo -n $y
    elif [ $n == 3 ] ; then
	y=$(($y + 10))
	y=$(($y / 10))
	y=$(($y * 10))
	echo -n $y
    else
	echo -n $y 
	return 1
    fi
    return 0
}

# given 3 args 'foundx','dby' and 'close'. If the precentage diff between 'foundx' and 'dby' is smaller then 'close' return dby else return foundx
# function to find percentage between two numbers = ((200 * $a/$b -  100 * $a/$b ))
compareXandY() {
    local foundx dby close
    foundx=$1
    dby=$2
    close=$3 # percent diff between the two numbers has to be smaller then this var.

    foundx=${foundx%%.*}
    dby=${dby%%.*}
    
    percent=$((200 * $dby/$foundx - 100 * $dby/$foundx ))

    if [ $percent -gt 100 ] ; then
	percent=$(($percent - 100 ))
    elif [ $percent -lt 100 ] ; then
	percent=$((100 - $percent))
    else
	percent=0
    fi

    [[ $percent -gt $close ]] && echo -n $foundx || echo -n $dby
    
    return 0
}

initialize $@
cp /dev/null ${tmplog}

# check that external needed programs are installed

s=""
case $os in
    Linux )
	progs="grep"
	;;
    FreeBSD )
	progs="grep"
	;;
    * )
	failed="Unknown OS :$os: FAILED"
	finish
	exit 1
	;;
esac

for i in $progs ; do
    type $i &>/dev/null && continue  || s="$s $i "
done
if [ -n "$s" ] ; then
    failed=" Unable to run need missing command(s) $s FAILED"
    echo "$failed" >> ${tmplog}
    finish
fi

case $os in
    Linux )
	save_e
	set +e

        # If dmidecode can give the info we need use it over /proc/cpuinfo
	type dmidecode &>/dev/null && s='1' || s=''
	if [ -n "$s" ] ; then 
	    x=$(dmidecode -t processor | grep "Thread")
	    ttf=/tmp/.$$ttf
	    $(dmidecode -t processor > $ttf)

	    # sockets = count # Version lines
	    sockets=$(grep 'Version:' $ttf | grep -v "Not Specified" | wc -l)

	    # cores_socket = Core Count
	    x=$(grep "Core Count" $ttf)
            unset -v d ; declare -a d=(${x// / })
	    cores_socket=${d[2]}

	    # threads_core = Thread Count - Core Enabled
	    x=$(grep "Core Enabled" $ttf)
            unset -v d ; declare -a d=(${x// / })
	    cores_enabled=${d[2]}
	    x=$(grep "Thread Count" $ttf)
            unset -v d ; declare -a d=(${x// / })
	    thread_count=${d[2]}
	    threads_core=$((thread_count / cores_enabled))

	    #MHz = Current Speed
	    x=$(grep "Current Speed" $ttf)
            unset -v d ; declare -a d=(${x// / })
	    mhz=${d[2]}

	    # hyperthread = Thread count greater then Core Enabled
	    [[ ${thread_count} -gt ${cores_enabled} ]] && hyperthread=1 || hyperthread=0

	    # ARCH = Version
	    x=$(grep Version $ttf | head -1)
	    # strip
	    arch=${x/"Version: "/}

	    # x64bit = 64-bit capable
	    x=$(grep "64-bit" $ttf)
	    [[ -n "$x" ]] && x64bit=1 || x64bit=0

	    # hwvirt = Enhanced Virtualization
	    x=$(grep "Virtualization" $ttf)
	    [[ -n $x ]] && hwvirt=1 || hwvit=0

	    rm -f $ttf
	else
	    x=$(grep "model name" /proc/cpuinfo | head -1)
	    x=${x##*: }  #remove first part of string upto colon
	    x=${x// /} 	    #takeout all the spaces
	    [[ "${x:0:16}" == "Intel(R)Core(TM)" ]] && arch="x86_64" || arch=$x
	    [[ "${x:0:15}" == "Intel(R)Xeon(R)" ]] && arch="x86_64" || arch=$x
	    [[ "${x:2:17}" == "thGenIntel(R)Core" ]] && arch="x86_64" || arch=$x
	    [[ "${x:0:3}" == "AMD" ]] && arch="x86_64" || arch=$x

	    # count sockets using the max 'physical id' string
	    # need to find how many different ids there are, to bad on some 
	    # it starts at zero and on other machines it starts at 1
	    x=$(grep "physical id" /proc/cpuinfo)
	    if [ -z "$x" ] ; then
		sockets=1
	    else
		while read -rd p p1 p2 p3 phy  ; do
		    cid+="$phy "
		done <<< $x
		first=999 last=0
		for i in $cid ; do
		    [[ $i -lt $first ]] && first=$i
		    [[ $i -gt $last ]]  && last=$i
		done
		sockets=$(($last - $first))
		((++sockets))
	    fi

    	    x=$(grep "cpu cores" /proc/cpuinfo | head -1)
	    [[ "$x" ]] || x="cpu cores	: 1" 
	    x=${x##*: }
	    cores_socket=$x

    	    x=$(grep "siblings" /proc/cpuinfo | head -1)
	    [[ "$x" ]] || x="siblings	: 1" 
	    x=${x##*: }
	    threads_core=$(($x / $cores_socket))

	    #better if dmesg not to old; else use proc/cpuinfo
    	    dmesgmhz=$(dmesg | grep -i mhz | grep processor)
	    if [ -n "$dmesgmhz" ] ; then
		x=${dmesgmhz#*Detected }
		x=${x% MHz*}
	    else
		x=$(grep -i mhz /proc/cpuinfo | head -1)
		x=${x#*: }
	    fi
	    rawmhz=$x
	    mhz=$(round2nearest $rawmhz)

	    cpuflags=$(grep flags /proc/cpuinfo | head -1)
	    restore_e
	    [[ "${cpuflags}" = "${cpuflags/ht/}" ]] && hyperthread=0 || hyperthread=1
	    [[ "${cpuflags}" = "${cpuflags/lm/}" ]] && x64bit=0 || x64bit=1
	    [[ ("${cpuflags}" = "${cpuflags/vmx/}") && ("${cpuflags}" = "${cpuflags/svm/}") ]] && hwvirt=0 || hwvirt=1
	    fi
	    ;;
    FreeBSD )
	    # find system arch
	    x=y=""
	    x=$(grep 'CPU:' /var/run/dmesg.boot | grep CPU:)

	    x=${x#CPU: }
	    x=${x%%CPU*}
	    x=${x%%([0-9]*}
		x=${x/Intel(R) /}
		x=${x/(R) /}
		x=${x// /} # remove all spaces
	    #standardize name
	    case $x in
		"IntelPentiumIII") arch="i686" ;;
		"Xeon(TM)" | "Xeon" ) arch=x86_64 ;;
		"IntelPentiumII") arch="i386" ;;
		"AMDEPYC") arch="amd64" ;;
		* ) arch="Unknown" ;;
	    esac
	    # find number of sockets
	    save_e
	    set +e
	    x=$(grep 'FreeBSD/SMP:' /var/run/dmesg.boot | grep package)
	    x=${x#FreeBSD/SMP: }
	    [[ $x ]] && sockets=${x%%package*} || sockets=1

	    # find number number of cores pre socket
	    x=$(grep 'FreeBSD/SMP:' /var/run/dmesg.boot | grep core)
	    x=${x#FreeBSD/SMP: }
	    x=${x#*package(s) x }
	    # AMDs and FreeBSD 12
	    if [ "$x" != "${x#*cache groups x }" ]; then
		mult=${x%%cache groups*}
		x=${x#*cache groups x }
	    else
		mult=1
	    fi
	    [[ $x ]] && cores_socket=${x%%core(s)*} || cores_socket=1
	    cores_socket=$(($cores_socket*$mult))

	    # find number of thread per cord
	    x=$(grep 'FreeBSD/SMP:' /var/run/dmesg.boot | grep thread)
	    x=${x#FreeBSD/SMP: } # chop front off
	    x=${x#*core(s) x } # chop end off
	    # set end delimiter 
	    x=${x/HTT/SMT} # pc3000 vs r710
	    x=${x/hardware/SMT} # xl170, d6515
	    [[ $x ]] && threads_core=${x%%SMT*} || threads_core=1
	    restore_e
	    
	    # find cpu speed from system
	    x=$(grep 'CPU\|SMP' /var/run/dmesg.boot | grep CPU:)
	    # example CPU: Intel(R) Xeon(TM) CPU 3.00GHz (2992.71-MHz 686-class CPU)
	    # from the end chop of and thing and including -MHz
	    x=${x%%-MHz*}
	    # from the front chop off everything up to the (
	    x=${x##*(}

	    # get the speed from the database
	    tbinfo=$(getfromtb CPUINFO)
	    # example 'SOCKETS=2 CORES=8 THREADS=2 SPEED=2600 BITS=64 HV=1'
	    y=${tbinfo##*SPEED=} #chop off front
	    y=${y%% B*} #chop off end
	    # is x any y within 5 percent of each other
	    mhz=$(compareXandY $x $y 5 )

	    # find the cpu flags
	    cpuflags="$(grep -E 'Features2?=' /var/run/dmesg.boot)"
	    [[ "${cpuflags}" = "${cpuflags/HTT/}" ]] && hyperthread=0 || hyperthread=1
	    [[ "${cpuflags}" = "${cpuflags/LM/}" ]] && x64bit=0 || x64bit=1
	    [[ ("${cpuflags}" = "${cpuflags/VMX/}") && ("${cpuflags}" = "${cpuflags/SVM/}") ]] && hwvirt=0 || hwvirt=1
	    ;;
	    
	  * )
	    # can't recover for unknown os type 
	    echo "internal error cpucheck"
	    failed=FAIL
	    exit 1
	    ;;
esac

# if saving data for testbed database

[[ ${x64bit} == 1 ]] && bits=64 || bits=32
(( $collect_flag )) && printf "CPUINFO SOCKETS=%s CORES=%s THREADS=%s SPEED=%s BITS=%s HV=%s\n"\
      ${sockets} ${cores_socket} ${threads_core} ${mhz} ${bits} ${hwvirt} >> ${logfile4tb}

# we are done if in MFS mode
if (( $mfsmode )) ; then
   (( $cpucheck_standalone )) && exit 0 || return 0
fi

if (( $check_flag )) ; then
    tbinfo=$(getfromtb CPUINFO)
    err=$?
    if [ $err != 0 ] ; then 
	failed="call to tbinfo $tbinfo FAILED"; finish
    fi

    if [ "$tbinfo" = "SOCKETS=$sockets CORES=$cores_socket THREADS=$threads_core SPEED=$mhz BITS=$bits HV=$hwvirt" ] ; then
	failed=""
    else
	failed="TBmiss Have |SOCKETS=$sockets CORES=$cores_socket THREADS=$threads_core SPEED=$mhz BITS=$bits HV=$hwvirt| Want |$tbinfo| FAILED"
    fi
fi


finish

(( $cpucheck_standalone )) && exit 0 || return 0

