commit 593cb41fe47efdfab55523c693089bc0f5a04354 Author: zun.yang Date: Mon Jul 31 18:49:41 2023 +0800 Init: project - initial commit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..24907af --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Castaway Labs LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README-CN.md b/README-CN.md new file mode 100644 index 0000000..295c938 --- /dev/null +++ b/README-CN.md @@ -0,0 +1,48 @@ +# SSH基准测试工具 + +> 这是一款支持离线和批处理执行的服务器基准测试工具。 + +--- + +## 说明 + +当前工具的实现流程描述: + +1. 检查主机配置是否正确。 +2. 检查主机是否可以连通(连通性测试)。 +3. 启动Iperf3服务器端。 +4. 将压力测试脚本上传到目标服务器并根据主机列表批量执行脚本(串行执行)。 +5. 实际并显示结果。 +6. 清理脚本数据(完成)。 + +--- + +## 工具打包和编译。 + +```bash +go mod tidy +go build -o ssh-benchmark main.go +``` + +如果您已安装了 [task](https://taskfile.dev/),也可以使用它。 + +```bash +task build:binary +``` + +--- + +## 使用说明 + +1. 修改 `config.json` 中的配置 + + > 1. 如果您没有Linux版Geekbench 5的许可证,可以通过电子邮件联系我()。我愿意与您分享。 + > 2. 如果您希望对多个节点同时进行测试,可以在 `Host` 字段中填入逗号分隔的列表。 + +2. 执行此项目预编译的 ssh-benchmark 文件 + + ```bash + ./ssh-benchmark + ``` + + ![2023-07-31 18.15.23 2](images/README/2023-07-31%2018.15.23%202.gif) diff --git a/README.md b/README.md new file mode 100644 index 0000000..38eff2d --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ + + +# SSH Benchmark + +> A server benchmarking tool that supports offline and batch execution. + +--- + +## Explanation + +Current tool implementation process description: + +1. Check if the host configuration is correct. +2. Check if the host is connectable (connectivity test). +3. Launch Iperf3 server-side. +4. Stress test script uploaded to target server and execute the script in batches according to the host list (executed serially). +5. Actual and display of results. +6. Clean up script data (complete). + +--- + +## Tool packaging and compilation. + +```bash +go mod tidy +go build -o ssh-benchmark main.go +``` + +If you have installed the [task](https://taskfile.dev/), you can also use it. + +```bash +task build:binary +``` + +--- + +## Instructions for use + +1. Modify the configuration in `config.json` + + > 1. If you don't have a license for Geekbench 5 for Linux, you can contact me by email (). I'd be willing to share it with you. + > 2. If you want to perform simultaneous testing on multiple nodes, fill in the `Host` field with a comma-separated list. + + +2. Execute the pre-compiled ssh-benchmark file of this project + + ```bash + ./ssh-benchmark + ``` + + ![2023-07-31 18.15.23 2](images/README/2023-07-31%2018.15.23%202.gif) diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..1042c2d --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,48 @@ +version: '3' + +vars: + BASE_DIR: + sh: dirname $(pwd) + PROJECT_NAME: + sh: pwd|sed "s#{{.BASE_DIR}}/##g" + GOOS: '' + GOARCH: '' + +tasks: + default: + cmds: + - task: deps + - task: build:binary + silent: true + + deps: + desc: Install all dependencies (except dredd requirements) + cmds: + - task: deps:be + - task: deps:tools + + deps:be: + desc: application dependencies + cmds: + - go mod tidy + - zip -r benchmark.zip benchmark/ + + deps:tools: + desc: Installs tools needed for building + vars: + GORELEASER_VERSION: "1.19.2" + cmds: + - '{{ if ne OS "windows" }} sh -c "curl -L https://github.com/goreleaser/goreleaser/releases/download/v{{ .GORELEASER_VERSION }}/goreleaser_$(uname -s)_$(uname -m).tar.gz | tar -xz -C $(go env GOPATH)/bin goreleaser"{{ else }} {{ end }}' + - '{{ if ne OS "windows" }} chmod +x $(go env GOPATH)/bin/goreleaser{{ else }} {{ end }}' + - '{{ if eq OS "windows" }} echo "NOTICE: You must download goreleaser manually to build this application https://github.com/goreleaser/goreleaser/releases "{{ else }}:{{ end }}' + - '{{ if eq OS "windows" }} echo "NOTICE: You need to install golangci-lint manually to build this application https://github.com/golangci/golangci-lint#install"{{ else }}{{ end }}' + + build:binary: + desc: Build a binary for the current architecture + platforms: [amd64] + cmds: + - env CGO_ENABLED=0 GOOS={{ .GOOS }} GOARCH={{ .GOARCH }} go build -o ./bin/{{.PROJECT_NAME}}{{ if eq OS "windows" }}.exe{{ end }} + + release:prod: + cmds: + - goreleaser \ No newline at end of file diff --git a/benchmark/run.sh b/benchmark/run.sh new file mode 100755 index 0000000..8da65d4 --- /dev/null +++ b/benchmark/run.sh @@ -0,0 +1,901 @@ +#!/bin/bash + +export PATH=$PATH:$(pwd)/tools +MAIN_VERSION="V2022-11-24" +BENCHMARK_HOST="${1}" +GEEKBENCH_LICENSE="${2}" + +echo -e '# ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #' +echo -e '# RUN-BenchMark-Script #' +echo -e '# '$MAIN_VERSION' #' +echo -e '# https://gitlab-ee.treesir.pub/devops/benchmark #' +echo -e '# ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #' + +echo -e +date +TIME_START=$(date '+%Y%m%d-%H%M%S') + +# override locale to eliminate parsing errors (i.e. using commas as delimiters rather than periods) +if locale -a | grep ^C$ > /dev/null ; then + # locale "C" installed + export LC_ALL=C +else + # locale "C" not installed, display warning + echo -e "\nWarning: locale 'C' not detected. Test outputs may not be parsed correctly." +fi + +# determine architecture of host +ARCH=$(uname -m) +if [[ $ARCH = *x86_64* ]]; then + # host is running a 64-bit kernel + ARCH="x64" +elif [[ $ARCH = *i?86* ]]; then + # host is running a 32-bit kernel + ARCH="x86" +elif [[ $ARCH = *aarch* || $ARCH = *arm* ]]; then + KERNEL_BIT=`getconf LONG_BIT` + if [[ $KERNEL_BIT = *64* ]]; then + # host is running an ARM 64-bit kernel + ARCH="aarch64" + else + # host is running an ARM 32-bit kernel + ARCH="arm" + fi + echo -e "\nARM compatibility is considered *experimental*" +else + # host is running a non-supported kernel + echo -e "Architecture not supported by YYDS." + exit 1 +fi + +# flags to skip certain performance tests +unset PREFER_BIN SKIP_FIO SKIP_IPERF SKIP_GEEKBENCH PRINT_HELP REDUCE_NET GEEKBENCH_4 GEEKBENCH_5 DD_FALLBACK IPERF_DL_FAIL JSON JSON_SEND JSON_RESULT JSON_FILE +GEEKBENCH_5="True" # gb5 test enabled by default +IPV4_CHECK="true" # iperf3 test enable by default + +# get any arguments that were passed to the script and set the associated skip flags (if applicable) +while getopts 'bfdighr49jw:s:' flag; do + case "${flag}" in + b) PREFER_BIN="True" ;; + f) SKIP_FIO="True" ;; + d) SKIP_FIO="True" ;; + i) SKIP_IPERF="True" ;; + g) SKIP_GEEKBENCH="True" ;; + h) PRINT_HELP="True" ;; + r) REDUCE_NET="True" ;; + # 4) GEEKBENCH_4="True" && unset GEEKBENCH_5 ;; + # 9) GEEKBENCH_4="True" && GEEKBENCH_5="True" ;; + j) JSON+="j" ;; + w) JSON+="w" && JSON_FILE=${OPTARG} ;; + s) JSON+="s" && JSON_SEND=${OPTARG} ;; + *) exit 1 ;; + esac +done + +# check for local fio/iperf installs +command -v fio >/dev/null 2>&1 && LOCAL_FIO=true || unset LOCAL_FIO +command -v iperf3 >/dev/null 2>&1 && LOCAL_IPERF=true || unset LOCAL_IPERF + +# check for curl/wget +command -v curl >/dev/null 2>&1 && LOCAL_CURL=true || unset LOCAL_CURL + +# test if the host has IPv4/IPv6 connectivity +# [[ ! -z $LOCAL_CURL ]] && IP_CHECK_CMD="curl -s -m 4" || IP_CHECK_CMD="wget -qO- -T 4" +# IPV4_CHECK=$((ping -4 -c 1 -W 4 ipv4.google.com >/dev/null 2>&1 && echo true) || $IP_CHECK_CMD -4 icanhazip.com 2> /dev/null) +# IPV6_CHECK=$((ping -6 -c 1 -W 4 ipv6.google.com >/dev/null 2>&1 && echo true) || $IP_CHECK_CMD -6 icanhazip.com 2> /dev/null) +# if [[ -z "$IPV4_CHECK" && -z "$IPV6_CHECK" ]]; then +# echo -e +# echo -e "Warning: Both IPv4 AND IPv6 connectivity were not detected. Check for DNS issues..." +# fi + +# print help and exit script, if help flag was passed +if [ ! -z "$PRINT_HELP" ]; then + echo -e + echo -e "Usage: ./YYDS.sh [-flags]" + echo -e " curl -sL YYDS.sh | bash" + echo -e " curl -sL YYDS.sh | bash -s -- -flags" + echo -e " wget -qO- YYDS.sh | bash" + echo -e " wget -qO- YYDS.sh | bash -s -- -flags" + echo -e + echo -e "Flags:" + echo -e " -b : prefer pre-compiled binaries from repo over local packages" + echo -e " -f/d : skips the fio disk benchmark test" + echo -e " -i : skips the iperf network test" + echo -e " -g : skips the geekbench performance test" + echo -e " -h : prints this lovely message, shows any flags you passed," + echo -e " shows if fio/iperf3 local packages have been detected," + echo -e " then exits" + echo -e " -r : reduce number of iperf3 network locations (to only three)" + echo -e " to lessen bandwidth usage" + echo -e " -4 : use geekbench 4 instead of geekbench 5" + echo -e " -9 : use both geekbench 4 AND geekbench 5" + echo -e " -j : print jsonified YYDS results at conclusion of test" + echo -e " -w : write jsonified YYDS results to disk using file name provided" + echo -e " -s : send jsonified YYDS results to URL" + echo -e + echo -e "Detected Arch: $ARCH" + echo -e + echo -e "Detected Flags:" + [[ ! -z $PREFER_BIN ]] && echo -e " -b, force using precompiled binaries from repo" + [[ ! -z $SKIP_FIO ]] && echo -e " -f/d, skipping fio disk benchmark test" + [[ ! -z $SKIP_IPERF ]] && echo -e " -i, skipping iperf network test" + [[ ! -z $SKIP_GEEKBENCH ]] && echo -e " -g, skipping geekbench test" + [[ ! -z $REDUCE_NET ]] && echo -e " -r, using reduced (3) iperf3 locations" + [[ ! -z $GEEKBENCH_5 ]] && echo -e " running geekbench 5" + echo -e + echo -e "Local Binary Check:" + [[ -z $LOCAL_FIO ]] && echo -e " fio not detected, will download precompiled binary" || + [[ -z $PREFER_BIN ]] && echo -e " fio detected, using local package" || + echo -e " fio detected, but using precompiled binary instead" + [[ -z $LOCAL_IPERF ]] && echo -e " iperf3 not detected, will download precompiled binary" || + [[ -z $PREFER_BIN ]] && echo -e " iperf3 detected, using local package" || + echo -e " iperf3 detected, but using precompiled binary instead" + echo -e + echo -e "Detected Connectivity:" + [[ ! -z $IPV4_CHECK ]] && echo -e " IPv4 connected" || + echo -e " IPv4 not connected" + [[ ! -z $IPV6_CHECK ]] && echo -e " IPv6 connected" || + echo -e " IPv6 not connected" + echo -e + echo -e "JSON Options:" + [[ -z $JSON ]] && echo -e " none" + [[ $JSON = *j* ]] && echo -e " printing json to screen after test" + [[ $JSON = *w* ]] && echo -e " writing json to file ($JSON_FILE) after test" + [[ $JSON = *s* ]] && echo -e " sharing json YYDS results to $JSON_SEND" + echo -e + echo -e "Exiting..." + + exit 0 +fi + +# format_size +# Purpose: Formats raw disk and memory sizes from kibibytes (KiB) to largest unit +# Parameters: +# 1. RAW - the raw memory size (RAM/Swap) in kibibytes +# Returns: +# Formatted memory size in KiB, MiB, GiB, or TiB +function format_size { + RAW=$1 # mem size in KiB + RESULT=$RAW + local DENOM=1 + local UNIT="KiB" + + # ensure the raw value is a number, otherwise return blank + re='^[0-9]+$' + if ! [[ $RAW =~ $re ]] ; then + echo "" + return 0 + fi + + if [ "$RAW" -ge 1073741824 ]; then + DENOM=1073741824 + UNIT="TiB" + elif [ "$RAW" -ge 1048576 ]; then + DENOM=1048576 + UNIT="GiB" + elif [ "$RAW" -ge 1024 ]; then + DENOM=1024 + UNIT="MiB" + fi + + # divide the raw result to get the corresponding formatted result (based on determined unit) + RESULT=$(awk -v a="$RESULT" -v b="$DENOM" 'BEGIN { print a / b }') + # shorten the formatted result to two decimal places (i.e. x.x) + RESULT=$(echo $RESULT | awk -F. '{ printf "%0.1f",$1"."substr($2,1,2) }') + # concat formatted result value with units and return result + RESULT="$RESULT $UNIT" + echo $RESULT +} + +# gather basic system information (inc. CPU, AES-NI/virt status, RAM + swap + disk size) +echo -e +echo -e "Basic System Information:" +echo -e "---------------------------------" +UPTIME=$(uptime | awk -F'( |,|:)+' '{d=h=m=0; if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes"}') +echo -e "Uptime : $UPTIME" +if [[ $ARCH = *aarch64* || $ARCH = *arm* ]]; then + CPU_PROC=$(lscpu | grep "Model name" | sed 's/Model name: *//g') +else + CPU_PROC=$(awk -F: '/model name/ {name=$2} END {print name}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//') +fi +echo -e "Processor : $CPU_PROC" +if [[ $ARCH = *aarch64* || $ARCH = *arm* ]]; then + CPU_CORES=$(lscpu | grep "^[[:blank:]]*CPU(s):" | sed 's/CPU(s): *//g') + CPU_FREQ=$(lscpu | grep "CPU max MHz" | sed 's/CPU max MHz: *//g') + [[ -z "$CPU_FREQ" ]] && CPU_FREQ="???" + CPU_FREQ="${CPU_FREQ} MHz" +else + CPU_CORES=$(awk -F: '/model name/ {core++} END {print core}' /proc/cpuinfo) + CPU_FREQ=$(awk -F: ' /cpu MHz/ {freq=$2} END {print freq " MHz"}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//') +fi +echo -e "CPU cores : $CPU_CORES @ $CPU_FREQ" +CPU_AES=$(cat /proc/cpuinfo | grep aes) +[[ -z "$CPU_AES" ]] && CPU_AES="\xE2\x9D\x8C Disabled" || CPU_AES="\xE2\x9C\x94 Enabled" +echo -e "AES-NI : $CPU_AES" +CPU_VIRT=$(cat /proc/cpuinfo | grep 'vmx\|svm') +[[ -z "$CPU_VIRT" ]] && CPU_VIRT="\xE2\x9D\x8C Disabled" || CPU_VIRT="\xE2\x9C\x94 Enabled" +echo -e "VM-x/AMD-V : $CPU_VIRT" +TOTAL_RAM_RAW=$(free | awk 'NR==2 {print $2}') +TOTAL_RAM=$(format_size $TOTAL_RAM_RAW) +echo -e "RAM : $TOTAL_RAM" +TOTAL_SWAP_RAW=$(free | grep Swap | awk '{ print $2 }') +TOTAL_SWAP=$(format_size $TOTAL_SWAP_RAW) +echo -e "Swap : $TOTAL_SWAP" +# total disk size is calculated by adding all partitions of the types listed below (after the -t flags) +TOTAL_DISK_RAW=$(df -t simfs -t ext2 -t ext3 -t ext4 -t btrfs -t xfs -t vfat -t ntfs -t swap --total 2>/dev/null | grep total | awk '{ print $2 }') +TOTAL_DISK=$(format_size $TOTAL_DISK_RAW) +echo -e "Disk : $TOTAL_DISK" +DISTRO=$(grep 'PRETTY_NAME' /etc/os-release | cut -d '"' -f 2 ) +echo -e "Distro : $DISTRO" +KERNEL=$(uname -r) +echo -e "Kernel : $KERNEL" + +if [ ! -z $JSON ]; then + UPTIME_S=$(awk '{print $1}' /proc/uptime) + IPV4=$([ ! -z $IPV4_CHECK ] && echo "true" || echo "false") + IPV6=$([ ! -z $IPV6_CHECK ] && echo "true" || echo "false") + AES=$([[ "$CPU_AES" = *Enabled* ]] && echo "true" || echo "false") + VIRT=$([[ "$CPU_VIRT" = *Enabled* ]] && echo "true" || echo "false") + JSON_RESULT='{"version":"'$MAIN_VERSION'","time":"'$TIME_START'","os":{"arch":"'$ARCH'","distro":"'$DISTRO'","kernel":"'$KERNEL'",' + JSON_RESULT+='"uptime":'$UPTIME_S'},"net":{"ipv4":'$IPV4',"ipv6":'$IPV6'},"cpu":{"model":"'$CPU_PROC'","cores":'$CPU_CORES',' + JSON_RESULT+='"freq":"'$CPU_FREQ'","aes":'$AES',"virt":'$VIRT'},"mem":{"ram":'$TOTAL_RAM_RAW',"swap":'$TOTAL_SWAP_RAW',"disk":'$TOTAL_DISK_RAW'}' +fi + +# create a directory in the same location that the script is being run to temporarily store RUN-related files +DATE=`date -Iseconds | sed -e "s/:/_/g"` +YYDS_PATH=./$DATE +touch $DATE.test 2> /dev/null +# test if the user has write permissions in the current directory and exit if not +if [ ! -f "$DATE.test" ]; then + echo -e + echo -e "You do not have write permission in this directory. Switch to an owned directory and re-run the script.\nExiting..." + exit 1 +fi +rm $DATE.test +mkdir -p $YYDS_PATH + +# trap CTRL+C signals to exit script cleanly +trap catch_abort INT + +# catch_abort +# Purpose: This method will catch CTRL+C signals in order to exit the script cleanly and remove +# RUN-related files. +function catch_abort() { + echo -e "\n** Aborting YYDS. Cleaning up files...\n" + rm -rf $YYDS_PATH + unset LC_ALL + exit 0 +} + +# format_speed +# Purpose: This method is a convenience function to format the output of the fio disk tests which +# always returns a result in KB/s. If result is >= 1 GB/s, use GB/s. If result is < 1 GB/s +# and >= 1 MB/s, then use MB/s. Otherwise, use KB/s. +# Parameters: +# 1. RAW - the raw disk speed result (in KB/s) +# Returns: +# Formatted disk speed in GB/s, MB/s, or KB/s +function format_speed { + RAW=$1 # disk speed in KB/s + RESULT=$RAW + local DENOM=1 + local UNIT="KB/s" + + # ensure raw value is not null, if it is, return blank + if [ -z "$RAW" ]; then + echo "" + return 0 + fi + + # check if disk speed >= 1 GB/s + if [ "$RAW" -ge 1000000 ]; then + DENOM=1000000 + UNIT="GB/s" + # check if disk speed < 1 GB/s && >= 1 MB/s + elif [ "$RAW" -ge 1000 ]; then + DENOM=1000 + UNIT="MB/s" + fi + + # divide the raw result to get the corresponding formatted result (based on determined unit) + RESULT=$(awk -v a="$RESULT" -v b="$DENOM" 'BEGIN { print a / b }') + # shorten the formatted result to two decimal places (i.e. x.xx) + RESULT=$(echo $RESULT | awk -F. '{ printf "%0.2f",$1"."substr($2,1,2) }') + # concat formatted result value with units and return result + RESULT="$RESULT $UNIT" + echo $RESULT +} + +# format_iops +# Purpose: This method is a convenience function to format the output of the raw IOPS result +# Parameters: +# 1. RAW - the raw IOPS result +# Returns: +# Formatted IOPS (i.e. 8, 123, 1.7k, 275.9k, etc.) +function format_iops { + RAW=$1 # iops + RESULT=$RAW + + # ensure raw value is not null, if it is, return blank + if [ -z "$RAW" ]; then + echo "" + return 0 + fi + + # check if IOPS speed > 1k + if [ "$RAW" -ge 1000 ]; then + # divide the raw result by 1k + RESULT=$(awk -v a="$RESULT" 'BEGIN { print a / 1000 }') + # shorten the formatted result to one decimal place (i.e. x.x) + RESULT=$(echo $RESULT | awk -F. '{ printf "%0.1f",$1"."substr($2,1,1) }') + RESULT="$RESULT"k + fi + + echo $RESULT +} + +# disk_test +# Purpose: This method is designed to test the disk performance of the host using the partition that the +# script is being run from using fio random read/write speed tests. +# Parameters: +# - (none) +function disk_test { + if [[ "$ARCH" = "aarch64" || "$ARCH" = "arm" ]]; then + FIO_SIZE=512M + else + FIO_SIZE=2G + fi + + # run a quick test to generate the fio test file to be used by the actual tests + echo -en "Generating fio test file..." + $FIO_CMD --name=setup --ioengine=libaio --rw=read --bs=64k --iodepth=64 --numjobs=2 --size=$FIO_SIZE --runtime=1 --gtod_reduce=1 --filename=$DISK_PATH/test.fio --direct=1 --minimal &> /dev/null + echo -en "\r\033[0K" + + # get array of block sizes to evaluate + BLOCK_SIZES=("$@") + + for BS in "${BLOCK_SIZES[@]}"; do + # run rand read/write mixed fio test with block size = $BS + echo -en "Running fio random mixed R+W disk test with $BS block size..." + DISK_TEST=$(timeout 35 $FIO_CMD --name=rand_rw_$BS --ioengine=libaio --rw=randrw --rwmixread=50 --bs=$BS --iodepth=64 --numjobs=2 --size=$FIO_SIZE --runtime=30 --gtod_reduce=1 --direct=1 --filename=$DISK_PATH/test.fio --group_reporting --minimal 2> /dev/null | grep rand_rw_$BS) + DISK_IOPS_R=$(echo $DISK_TEST | awk -F';' '{print $8}') + DISK_IOPS_W=$(echo $DISK_TEST | awk -F';' '{print $49}') + DISK_IOPS=$(awk -v a="$DISK_IOPS_R" -v b="$DISK_IOPS_W" 'BEGIN { print a + b }') + DISK_TEST_R=$(echo $DISK_TEST | awk -F';' '{print $7}') + DISK_TEST_W=$(echo $DISK_TEST | awk -F';' '{print $48}') + DISK_TEST=$(awk -v a="$DISK_TEST_R" -v b="$DISK_TEST_W" 'BEGIN { print a + b }') + DISK_RESULTS_RAW+=( "$DISK_TEST" "$DISK_TEST_R" "$DISK_TEST_W" "$DISK_IOPS" "$DISK_IOPS_R" "$DISK_IOPS_W" ) + + DISK_IOPS=$(format_iops $DISK_IOPS) + DISK_IOPS_R=$(format_iops $DISK_IOPS_R) + DISK_IOPS_W=$(format_iops $DISK_IOPS_W) + DISK_TEST=$(format_speed $DISK_TEST) + DISK_TEST_R=$(format_speed $DISK_TEST_R) + DISK_TEST_W=$(format_speed $DISK_TEST_W) + + DISK_RESULTS+=( "$DISK_TEST" "$DISK_TEST_R" "$DISK_TEST_W" "$DISK_IOPS" "$DISK_IOPS_R" "$DISK_IOPS_W" ) + echo -en "\r\033[0K" + done +} + +# dd_test +# Purpose: This method is invoked if the fio disk test failed. dd sequential speed tests are +# not indiciative or real-world results, however, some form of disk speed measure +# is better than nothing. +# Parameters: +# - (none) +function dd_test { + I=0 + DISK_WRITE_TEST_RES=() + DISK_READ_TEST_RES=() + DISK_WRITE_TEST_AVG=0 + DISK_READ_TEST_AVG=0 + + # run the disk speed tests (write and read) thrice over + while [ $I -lt 3 ] + do + # write test using dd, "direct" flag is used to test direct I/O for data being stored to disk + DISK_WRITE_TEST=$(dd if=/dev/zero of=$DISK_PATH/$DATE.test bs=64k count=16k oflag=direct |& grep copied | awk '{ print $(NF-1) " " $(NF)}') + VAL=$(echo $DISK_WRITE_TEST | cut -d " " -f 1) + [[ "$DISK_WRITE_TEST" == *"GB"* ]] && VAL=$(awk -v a="$VAL" 'BEGIN { print a * 1000 }') + DISK_WRITE_TEST_RES+=( "$DISK_WRITE_TEST" ) + DISK_WRITE_TEST_AVG=$(awk -v a="$DISK_WRITE_TEST_AVG" -v b="$VAL" 'BEGIN { print a + b }') + + # read test using dd using the 1G file written during the write test + DISK_READ_TEST=$(dd if=$DISK_PATH/$DATE.test of=/dev/null bs=8k |& grep copied | awk '{ print $(NF-1) " " $(NF)}') + VAL=$(echo $DISK_READ_TEST | cut -d " " -f 1) + [[ "$DISK_READ_TEST" == *"GB"* ]] && VAL=$(awk -v a="$VAL" 'BEGIN { print a * 1000 }') + DISK_READ_TEST_RES+=( "$DISK_READ_TEST" ) + DISK_READ_TEST_AVG=$(awk -v a="$DISK_READ_TEST_AVG" -v b="$VAL" 'BEGIN { print a + b }') + + I=$(( $I + 1 )) + done + # calculate the write and read speed averages using the results from the three runs + DISK_WRITE_TEST_AVG=$(awk -v a="$DISK_WRITE_TEST_AVG" 'BEGIN { print a / 3 }') + DISK_READ_TEST_AVG=$(awk -v a="$DISK_READ_TEST_AVG" 'BEGIN { print a / 3 }') +} + +# check if disk performance is being tested and the host has required space (2G) +AVAIL_SPACE=`df -k . | awk 'NR==2{print $4}'` +if [[ -z "$SKIP_FIO" && "$AVAIL_SPACE" -lt 2097152 && "$ARCH" != "aarch64" && "$ARCH" != "arm" ]]; then # 2GB = 2097152KB + echo -e "\nLess than 2GB of space available. Skipping disk test..." +elif [[ -z "$SKIP_FIO" && "$AVAIL_SPACE" -lt 524288 && ("$ARCH" = "aarch64" || "$ARCH" = "arm") ]]; then # 512MB = 524288KB + echo -e "\nLess than 512MB of space available. Skipping disk test..." +# if the skip disk flag was set, skip the disk performance test, otherwise test disk performance +elif [ -z "$SKIP_FIO" ]; then + # Perform ZFS filesystem detection and determine if we have enough free space according to spa_asize_inflation + ZFSCHECK="/sys/module/zfs/parameters/spa_asize_inflation" + if [[ -f "$ZFSCHECK" ]];then + mul_spa=$((($(cat /sys/module/zfs/parameters/spa_asize_inflation)*2))) + warning=0 + poss=() + + for pathls in $(df -Th | awk '{print $7}' | tail -n +2) + do + if [[ "${PWD##$pathls}" != "${PWD}" ]]; then + poss+=($pathls) + fi + done + + long="" + m=-1 + for x in ${poss[@]} + do + if [ ${#x} -gt $m ];then + m=${#x} + long=$x + fi + done + + size_b=$(df -Th | grep -w $long | grep -i zfs | awk '{print $5}' | tail -c 2 | head -c 1) + free_space=$(df -Th | grep -w $long | grep -i zfs | awk '{print $5}' | head -c -2) + + if [[ $size_b == 'T' ]]; then + free_space=$(bc <<< "$free_space*1024") + size_b='G' + fi + + if [[ $(df -Th | grep -w $long) == *"zfs"* ]];then + + if [[ $size_b == 'G' ]]; then + if [[ $(echo "$free_space < $mul_spa" | bc) -ne 0 ]];then + warning=1 + fi + else + warning=1 + fi + + fi + + if [[ $warning -eq 1 ]];then + echo -en "\nWarning! You are running YYDS on a ZFS Filesystem and your disk space is too low for the fio test. Your test results will be inaccurate. You need at least $mul_spa GB free in order to complete this test accurately. For more information, please see https://github.com/masonr/yet-another-bench-script/issues/13\n" + fi + fi + + echo -en "\nPreparing system for disk tests..." + + # create temp directory to store disk write/read test files + DISK_PATH=$YYDS_PATH/disk + mkdir -p $DISK_PATH + + if [[ -z "$PREFER_BIN" && ! -z "$LOCAL_FIO" ]]; then # local fio has been detected, use instead of pre-compiled binary + FIO_CMD=fio + else + # download fio binary + if [[ ! -z $LOCAL_CURL ]]; then + curl -s --connect-timeout 5 --retry 5 --retry-delay 0 https://raw.githubusercontent.com/masonr/yet-another-bench-script/master/bin/fio/fio_$ARCH -o $DISK_PATH/fio + else + wget -q -T 5 -t 5 -w 0 https://raw.githubusercontent.com/masonr/yet-another-bench-script/master/bin/fio/fio_$ARCH -O $DISK_PATH/fio + fi + + if [ ! -f "$DISK_PATH/fio" ]; then # ensure fio binary download successfully + echo -en "\r\033[0K" + echo -e "Fio binary download failed. Running dd test as fallback...." + DD_FALLBACK=True + else + chmod +x $DISK_PATH/fio + FIO_CMD=$DISK_PATH/fio + fi + fi + + if [ -z "$DD_FALLBACK" ]; then # if not falling back on dd tests, run fio test + echo -en "\r\033[0K" + + # init global array to store disk performance values + declare -a DISK_RESULTS DISK_RESULTS_RAW + # disk block sizes to evaluate + BLOCK_SIZES=( "4k" "64k" "512k" "1m" ) + + # execute disk performance test + disk_test "${BLOCK_SIZES[@]}" + fi + + if [[ ! -z "$DD_FALLBACK" || ${#DISK_RESULTS[@]} -eq 0 ]]; then # fio download failed or test was killed or returned an error, run dd test instead + if [ -z "$DD_FALLBACK" ]; then # print error notice if ended up here due to fio error + echo -e "fio disk speed tests failed. Run manually to determine cause.\nRunning dd test as fallback..." + fi + + dd_test + + # format the speed averages by converting to GB/s if > 1000 MB/s + if [ $(echo $DISK_WRITE_TEST_AVG | cut -d "." -f 1) -ge 1000 ]; then + DISK_WRITE_TEST_AVG=$(awk -v a="$DISK_WRITE_TEST_AVG" 'BEGIN { print a / 1000 }') + DISK_WRITE_TEST_UNIT="GB/s" + else + DISK_WRITE_TEST_UNIT="MB/s" + fi + if [ $(echo $DISK_READ_TEST_AVG | cut -d "." -f 1) -ge 1000 ]; then + DISK_READ_TEST_AVG=$(awk -v a="$DISK_READ_TEST_AVG" 'BEGIN { print a / 1000 }') + DISK_READ_TEST_UNIT="GB/s" + else + DISK_READ_TEST_UNIT="MB/s" + fi + + # print dd sequential disk speed test results + echo -e + echo -e "dd Sequential Disk Speed Tests:" + echo -e "---------------------------------" + printf "%-6s | %-6s %-4s | %-6s %-4s | %-6s %-4s | %-6s %-4s\n" "" "Test 1" "" "Test 2" "" "Test 3" "" "Avg" "" + printf "%-6s | %-6s %-4s | %-6s %-4s | %-6s %-4s | %-6s %-4s\n" + printf "%-6s | %-11s | %-11s | %-11s | %-6.2f %-4s\n" "Write" "${DISK_WRITE_TEST_RES[0]}" "${DISK_WRITE_TEST_RES[1]}" "${DISK_WRITE_TEST_RES[2]}" "${DISK_WRITE_TEST_AVG}" "${DISK_WRITE_TEST_UNIT}" + printf "%-6s | %-11s | %-11s | %-11s | %-6.2f %-4s\n" "Read" "${DISK_READ_TEST_RES[0]}" "${DISK_READ_TEST_RES[1]}" "${DISK_READ_TEST_RES[2]}" "${DISK_READ_TEST_AVG}" "${DISK_READ_TEST_UNIT}" + else # fio tests completed successfully, print results + [[ ! -z $JSON ]] && JSON_RESULT+=',"fio":[' + DISK_RESULTS_NUM=$(expr ${#DISK_RESULTS[@]} / 6) + DISK_COUNT=0 + + # print disk speed test results + echo -e "fio Disk Speed Tests (Mixed R/W 50/50):" + echo -e "---------------------------------" + + while [ $DISK_COUNT -lt $DISK_RESULTS_NUM ] ; do + if [ $DISK_COUNT -gt 0 ]; then printf "%-10s | %-20s | %-20s\n"; fi + printf "%-10s | %-11s %8s | %-11s %8s\n" "Block Size" "${BLOCK_SIZES[DISK_COUNT]}" "(IOPS)" "${BLOCK_SIZES[DISK_COUNT+1]}" "(IOPS)" + printf "%-10s | %-11s %8s | %-11s %8s\n" " ------" "---" "---- " "----" "---- " + printf "%-10s | %-11s %8s | %-11s %8s\n" "Read" "${DISK_RESULTS[DISK_COUNT*6+1]}" "(${DISK_RESULTS[DISK_COUNT*6+4]})" "${DISK_RESULTS[(DISK_COUNT+1)*6+1]}" "(${DISK_RESULTS[(DISK_COUNT+1)*6+4]})" + printf "%-10s | %-11s %8s | %-11s %8s\n" "Write" "${DISK_RESULTS[DISK_COUNT*6+2]}" "(${DISK_RESULTS[DISK_COUNT*6+5]})" "${DISK_RESULTS[(DISK_COUNT+1)*6+2]}" "(${DISK_RESULTS[(DISK_COUNT+1)*6+5]})" + printf "%-10s | %-11s %8s | %-11s %8s\n" "Total" "${DISK_RESULTS[DISK_COUNT*6]}" "(${DISK_RESULTS[DISK_COUNT*6+3]})" "${DISK_RESULTS[(DISK_COUNT+1)*6]}" "(${DISK_RESULTS[(DISK_COUNT+1)*6+3]})" + if [ ! -z $JSON ]; then + JSON_RESULT+='{"bs":"'${BLOCK_SIZES[DISK_COUNT]}'","speed_r":'${DISK_RESULTS_RAW[DISK_COUNT*6+1]}',"iops_r":'${DISK_RESULTS_RAW[DISK_COUNT*6+4]} + JSON_RESULT+=',"speed_w":'${DISK_RESULTS_RAW[DISK_COUNT*6+2]}',"iops_w":'${DISK_RESULTS_RAW[DISK_COUNT*6+5]}',"speed_rw":'${DISK_RESULTS_RAW[DISK_COUNT*6]} + JSON_RESULT+=',"iops_rw":'${DISK_RESULTS_RAW[DISK_COUNT*6+3]}'},' + JSON_RESULT+='{"bs":"'${BLOCK_SIZES[DISK_COUNT+1]}'","speed_r":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6+1]}',"iops_r":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6+4]} + JSON_RESULT+=',"speed_w":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6+2]}',"iops_w":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6+5]}',"speed_rw":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6]} + JSON_RESULT+=',"iops_rw":'${DISK_RESULTS_RAW[(DISK_COUNT+1)*6+3]}'},' + fi + DISK_COUNT=$(expr $DISK_COUNT + 2) + done + [[ ! -z $JSON ]] && JSON_RESULT=${JSON_RESULT::${#JSON_RESULT}-1} && JSON_RESULT+=']' + fi +fi + +# iperf_test +# Purpose: This method is designed to test the network performance of the host by executing an +# iperf3 test to/from the public iperf server passed to the function. Both directions +# (send and receive) are tested. +# Parameters: +# 1. URL - URL/domain name of the iperf server +# 2. PORTS - the range of ports on which the iperf server operates +# 3. HOST - the friendly name of the iperf server host/owner +# 4. FLAGS - any flags that should be passed to the iperf command +function iperf_test { + URL=$1 + PORTS=$2 + HOST=$3 + FLAGS=$4 + + # attempt the iperf send test 3 times, allowing for a slot to become available on the + # server or to throw out any bad/error results + I=1 + while [ $I -le 3 ] + do + echo -en "Performing $MODE iperf3 send test to $HOST (Attempt #$I of 3)..." + # select a random iperf port from the range provided + PORT=`shuf -i $PORTS -n 1` + # run the iperf test sending data from the host to the iperf server; includes + # a timeout of 15s in case the iperf server is not responding; uses 8 parallel + # threads for the network test + IPERF_RUN_SEND="$(timeout 15 $IPERF_CMD $FLAGS -c $URL -p $PORT -P 8 2> /dev/null)" + # check if iperf exited cleanly and did not return an error + if [[ "$IPERF_RUN_SEND" == *"receiver"* && "$IPERF_RUN_SEND" != *"error"* ]]; then + # test did not result in an error, parse speed result + SPEED=$(echo "${IPERF_RUN_SEND}" | grep SUM | grep receiver | awk '{ print $6 }') + # if speed result is blank or bad (0.00), rerun, otherwise set counter to exit loop + [[ -z $SPEED || "$SPEED" == "0.00" ]] && I=$(( $I + 1 )) || I=11 + else + # if iperf server is not responding, set counter to exit, otherwise increment, sleep, and rerun + [[ "$IPERF_RUN_SEND" == *"unable to connect"* ]] && I=11 || I=$(( $I + 1 )) && sleep 2 + fi + echo -en "\r\033[0K" + done + + # small sleep necessary to give iperf server a breather to get ready for a new test + sleep 1 + + # attempt the iperf receive test 3 times, allowing for a slot to become available on + # the server or to throw out any bad/error results + J=1 + while [ $J -le 3 ] + do + echo -n "Performing $MODE iperf3 recv test from $HOST (Attempt #$J of 3)..." + # select a random iperf port from the range provided + PORT=`shuf -i $PORTS -n 1` + # run the iperf test receiving data from the iperf server to the host; includes + # a timeout of 15s in case the iperf server is not responding; uses 8 parallel + # threads for the network test + IPERF_RUN_RECV="$(timeout 15 $IPERF_CMD $FLAGS -c $URL -p $PORT -P 8 -R 2> /dev/null)" + # check if iperf exited cleanly and did not return an error + if [[ "$IPERF_RUN_RECV" == *"receiver"* && "$IPERF_RUN_RECV" != *"error"* ]]; then + # test did not result in an error, parse speed result + SPEED=$(echo "${IPERF_RUN_RECV}" | grep SUM | grep receiver | awk '{ print $6 }') + # if speed result is blank or bad (0.00), rerun, otherwise set counter to exit loop + [[ -z $SPEED || "$SPEED" == "0.00" ]] && J=$(( $J + 1 )) || J=11 + else + # if iperf server is not responding, set counter to exit, otherwise increment, sleep, and rerun + [[ "$IPERF_RUN_RECV" == *"unable to connect"* ]] && J=11 || J=$(( $J + 1 )) && sleep 2 + fi + echo -en "\r\033[0K" + done + + # parse the resulting send and receive speed results + IPERF_SENDRESULT="$(echo "${IPERF_RUN_SEND}" | grep SUM | grep receiver)" + IPERF_RECVRESULT="$(echo "${IPERF_RUN_RECV}" | grep SUM | grep receiver)" +} + +# launch_iperf +# Purpose: This method is designed to facilitate the execution of iperf network speed tests to +# each public iperf server in the iperf server locations array. +# Parameters: +# 1. MODE - indicates the type of iperf tests to run (IPv4 or IPv6) +function launch_iperf { + MODE=$1 + [[ "$MODE" == *"IPv6"* ]] && IPERF_FLAGS="-6" || IPERF_FLAGS="-4" + + # print iperf3 network speed results as they are completed + echo -e + echo -e "iperf3 Network Speed Tests ($MODE):" + echo -e "---------------------------------" + printf "%-15s | %-25s | %-15s | %-15s\n" "Provider" "Location (Link)" "Send Speed" "Recv Speed" + printf "%-15s | %-25s | %-15s | %-15s\n" + + # loop through iperf locations array to run iperf test using each public iperf server + for (( i = 0; i < IPERF_LOCS_NUM; i++ )); do + # test if the current iperf location supports the network mode being tested (IPv4/IPv6) + if [[ "${IPERF_LOCS[i*5+4]}" == *"$MODE"* ]]; then + # call the iperf_test function passing the required parameters + iperf_test "${IPERF_LOCS[i*5]}" "${IPERF_LOCS[i*5+1]}" "${IPERF_LOCS[i*5+2]}" "$IPERF_FLAGS" + # parse the send and receive speed results + IPERF_SENDRESULT_VAL=$(echo $IPERF_SENDRESULT | awk '{ print $6 }') + IPERF_SENDRESULT_UNIT=$(echo $IPERF_SENDRESULT | awk '{ print $7 }') + IPERF_RECVRESULT_VAL=$(echo $IPERF_RECVRESULT | awk '{ print $6 }') + IPERF_RECVRESULT_UNIT=$(echo $IPERF_RECVRESULT | awk '{ print $7 }') + # if the results are blank, then the server is "busy" and being overutilized + [[ -z $IPERF_SENDRESULT_VAL || "$IPERF_SENDRESULT_VAL" == *"0.00"* ]] && IPERF_SENDRESULT_VAL="busy" && IPERF_SENDRESULT_UNIT="" + [[ -z $IPERF_RECVRESULT_VAL || "$IPERF_RECVRESULT_VAL" == *"0.00"* ]] && IPERF_RECVRESULT_VAL="busy" && IPERF_RECVRESULT_UNIT="" + # print the speed results for the iperf location currently being evaluated + printf "%-15s | %-25s | %-15s | %-15s\n" "${IPERF_LOCS[i*5+2]}" "${IPERF_LOCS[i*5+3]}" "$IPERF_SENDRESULT_VAL $IPERF_SENDRESULT_UNIT" "$IPERF_RECVRESULT_VAL $IPERF_RECVRESULT_UNIT" + if [ ! -z $JSON ]; then + JSON_RESULT+='{"mode":"'$MODE'","provider":"'${IPERF_LOCS[i*5+2]}'","loc":"'${IPERF_LOCS[i*5+3]} + JSON_RESULT+='","send":"'$IPERF_SENDRESULT_VAL' '$IPERF_SENDRESULT_UNIT'","recv":"'$IPERF_RECVRESULT_VAL' '$IPERF_RECVRESULT_UNIT'"},' + fi + fi + done +} + +# if the skip iperf flag was set, skip the network performance test, otherwise test network performance +if [ -z "$SKIP_IPERF" ]; then + + if [[ -z "$PREFER_BIN" && ! -z "$LOCAL_IPERF" ]]; then # local iperf has been detected, use instead of pre-compiled binary + IPERF_CMD=iperf3 + else + # create a temp directory to house the required iperf binary and library + IPERF_PATH=$YYDS_PATH/iperf + mkdir -p $IPERF_PATH + + # download iperf3 binary + if [[ ! -z $LOCAL_CURL ]]; then + curl -s --connect-timeout 5 --retry 5 --retry-delay 0 https://raw.githubusercontent.com/masonr/yet-another-bench-script/master/bin/iperf/iperf3_$ARCH -o $IPERF_PATH/iperf3 + else + wget -q -T 5 -t 5 -w 0 https://raw.githubusercontent.com/masonr/yet-another-bench-script/master/bin/iperf/iperf3_$ARCH -O $IPERF_PATH/iperf3 + fi + + if [ ! -f "$IPERF_PATH/iperf3" ]; then # ensure iperf3 binary downloaded successfully + IPERF_DL_FAIL=True + else + chmod +x $IPERF_PATH/iperf3 + IPERF_CMD=$IPERF_PATH/iperf3 + fi + fi + + # array containing all currently available iperf3 public servers to use for the network test + # format: "1" "2" "3" "4" "5" \ + # 1. domain name of the iperf server + # 2. range of ports that the iperf server is running on (lowest-highest) + # 3. friendly name of the host/owner of the iperf server + # 4. location and advertised speed link of the iperf server + # 5. network modes supported by the iperf server (IPv4 = IPv4-only, IPv4|IPv6 = IPv4 + IPv6, etc.) + IPERF_LOCS=( \ + "${BENCHMARK_HOST}" "5201-5201" "Local" "Offline (Local)" "IPv4" + ) + + # if the "REDUCE_NET" flag is activated, then do a shorter iperf test with only three locations + # (Clouvider London, Clouvider NYC, and Online.net France) + # if [ ! -z "$REDUCE_NET" ]; then + # IPERF_LOCS=( \ + # "lon.speedtest.clouvider.net" "5200-5209" "Clouvider" "London, UK (10G)" "IPv4|IPv6" \ + # "ping.online.net" "5200-5209" "Online.net" "Paris, FR (10G)" "IPv4" \ + # "ping6.online.net" "5200-5209" "Online.net" "Paris, FR (10G)" "IPv6" \ + # "nyc.speedtest.clouvider.net" "5200-5209" "Clouvider" "NYC, NY, US (10G)" "IPv4|IPv6" \ + # ) + # fi + + # get the total number of iperf locations (total array size divided by 5 since each location has 5 elements) + IPERF_LOCS_NUM=${#IPERF_LOCS[@]} + IPERF_LOCS_NUM=$((IPERF_LOCS_NUM / 5)) + + if [ -z "$IPERF_DL_FAIL" ]; then + [[ ! -z $JSON ]] && JSON_RESULT+=',"iperf":[' + # check if the host has IPv4 connectivity, if so, run iperf3 IPv4 tests + [ ! -z "$IPV4_CHECK" ] && launch_iperf "IPv4" + # check if the host has IPv6 connectivity, if so, run iperf3 IPv6 tests + [ ! -z "$IPV6_CHECK" ] && launch_iperf "IPv6" + [[ ! -z $JSON ]] && JSON_RESULT=${JSON_RESULT::${#JSON_RESULT}-1} && JSON_RESULT+=']' + else + echo -e "\niperf3 binary download failed. Skipping iperf network tests..." + fi +fi + +# launch_geekbench +# Purpose: This method is designed to run the Primate Labs' Geekbench 4/5 Cross-Platform Benchmark utility +# Parameters: +# 1. VERSION - indicates which Geekbench version to run +function launch_geekbench { + VERSION=$1 + + # create a temp directory to house all geekbench files + GEEKBENCH_PATH=tools/geekbench_$VERSION + mkdir -p $GEEKBENCH_PATH + + # check for curl vs wget + [[ ! -z $LOCAL_CURL ]] && DL_CMD="curl -s" || DL_CMD="wget -qO-" + + if [[ $VERSION == *4* && ($ARCH = *aarch64* || $ARCH = *arm*) ]]; then + echo -e "\nARM architecture not supported by Geekbench 4, use Geekbench 5." + elif [[ $VERSION == *4* && $ARCH != *aarch64* && $ARCH != *arm* ]]; then # Geekbench v4 + echo -en "\nRunning GB4 benchmark test... *cue elevator music*" + # download the latest Geekbench 4 tarball and extract to geekbench temp directory + # $DL_CMD https://cdn.geekbench.com/Geekbench-4.4.4-Linux.tar.gz | tar xz --strip-components=1 -C $GEEKBENCH_PATH &>/dev/null + + if [[ "$ARCH" == *"x86"* ]]; then + # check if geekbench file exists + # if test -f "geekbench.license"; then + # $GEEKBENCH_PATH/geekbench_x86_32 --unlock `cat geekbench.license` > /dev/null 2>&1 + # fi + $GEEKBENCH_PATH/geekbench_x86_32 --unlock ${GEEKBENCH_LICENSE} > /dev/null 2>&1 + + # run the Geekbench 4 test and grep the test results URL given at the end of the test + GEEKBENCH_TEST=$($GEEKBENCH_PATH/geekbench_x86_32 --no-upload 2>/dev/null | grep "https://browser") + else + # check if geekbench file exists + # if test -f "geekbench.license"; then + # $GEEKBENCH_PATH/geekbench4 --unlock `cat geekbench.license` > /dev/null 2>&1 + # fi + $GEEKBENCH_PATH/geekbench4 --unlock ${GEEKBENCH_LICENSE} > /dev/null 2>&1 + + # run the Geekbench 4 test and grep the test results URL given at the end of the test + GEEKBENCH_TEST=$($GEEKBENCH_PATH/geekbench4 --upload 2>/dev/null | grep "https://browser") + fi + fi + + if [[ $VERSION == *5* ]]; then # Geekbench v5 + if [[ $ARCH = *x86* && $GEEKBENCH_4 == *False* ]]; then # don't run Geekbench 5 if on 32-bit arch + echo -e "\nGeekbench 5 cannot run on 32-bit architectures. Re-run with -4 flag to use" + echo -e "Geekbench 4, which can support 32-bit architectures. Skipping Geekbench 5." + elif [[ $ARCH = *x86* && $GEEKBENCH_4 == *True* ]]; then + echo -e "\nGeekbench 5 cannot run on 32-bit architectures. Skipping test." + else + echo -en "\nRunning GB5 benchmark test... *cue elevator music*" + # download the latest Geekbench 5 tarball and extract to geekbench temp directory + # if [[ $ARCH = *aarch64* || $ARCH = *arm* ]]; then + # $DL_CMD https://cdn.geekbench.com/Geekbench-5.4.4-LinuxARMPreview.tar.gz | tar xz --strip-components=1 -C $GEEKBENCH_PATH &>/dev/null + # else + # $DL_CMD https://cdn.geekbench.com/Geekbench-5.4.5-Linux.tar.gz | tar xz --strip-components=1 -C $GEEKBENCH_PATH &>/dev/null + # fi + + # check if geekbench file exists + # if test -f "geekbench.license"; then + # $GEEKBENCH_PATH/geekbench5 --unlock `cat geekbench.license` > /dev/null 2>&1 + # fi + $GEEKBENCH_PATH/geekbench5 --unlock ${GEEKBENCH_LICENSE} > /dev/null 2>&1 + + GEEKBENCH_TEST=$($GEEKBENCH_PATH/geekbench5 --no-upload 2>/dev/null) + fi + fi + + # ensure the test ran successfully + if [ -z "$GEEKBENCH_TEST" ]; then + if [[ -z "$IPV4_CHECK" ]]; then + # Geekbench test failed to download because host lacks IPv4 (cdn.geekbench.com = IPv4 only) + echo -e "\r\033[0KGeekbench releases can only be downloaded over IPv4. FTP the Geekbench files and run manually." + elif [[ $ARCH != *x86* ]]; then + # if the Geekbench test failed for any reason, exit cleanly and print error message + echo -e "\r\033[0KGeekbench $VERSION test failed. Run manually to determine cause." + fi + else + # if the Geekbench test succeeded, parse the test results URL + # GEEKBENCH_URL=$(echo -e $GEEKBENCH_TEST | head -1) + # GEEKBENCH_URL_CLAIM=$(echo $GEEKBENCH_URL | awk '{ print $2 }') + # GEEKBENCH_URL=$(echo $GEEKBENCH_URL | awk '{ print $1 }') + # sleep a bit to wait for results to be made available on the geekbench website + # sleep 20 + # parse the public results page for the single and multi core geekbench scores + # GEEKBENCH_SCORES_SINGLE=$(echo $GEEKBENCH_SCORES | awk -v FS="(>|<)" '{ print $3 }') + GEEKBENCH_SCORES_SINGLE=$(echo $GEEKBENCH_TEST|egrep -o "Single-Core Score [0-9]+"|awk '{print $3}') + GEEKBENCH_SCORES_MULTI=$(echo $GEEKBENCH_TEST|egrep -o "Multi-Core Score [0-9]+"|awk '{print $3}') + # GEEKBENCH_SCORES_MULTI=$(echo $GEEKBENCH_SCORES | awk -v FS="(>|<)" '{ print $7 }') + + # print the Geekbench results + echo -en "\r\033[0K" + echo -e "Geekbench $VERSION Benchmark Test:" + echo -e "---------------------------------" + printf "%-15s | %-30s\n" "Test" "Value" + printf "%-15s | %-30s\n" + printf "%-15s | %-30s\n" "Single Core" "$GEEKBENCH_SCORES_SINGLE" + printf "%-15s | %-30s\n" "Multi Core" "$GEEKBENCH_SCORES_MULTI" + # printf "%-15s | %-30s\n" "Full Test" "$GEEKBENCH_URL" + # if [ ! -z $JSON ]; then + # JSON_RESULT+='{"version":'$VERSION',"single":'$GEEKBENCH_SCORES_SINGLE',"multi":'$GEEKBENCH_SCORES_MULTI + # JSON_RESULT+=',"url":"'$GEEKBENCH_URL'"},' + # fi + # write the geekbench claim URL to a file so the user can add the results to their profile (if desired) + # [ ! -z "$GEEKBENCH_URL_CLAIM" ] && echo -e "$GEEKBENCH_URL_CLAIM" >> geekbench_claim.url 2> /dev/null + fi +} + +# if the skip geekbench flag was set, skip the system performance test, otherwise test system performance +if [ -z "$SKIP_GEEKBENCH" ]; then + [[ ! -z $JSON ]] && JSON_RESULT+=',"geekbench":[' + if [[ $GEEKBENCH_4 == *True* ]]; then + launch_geekbench 4 + fi + + if [[ $GEEKBENCH_5 == *True* ]]; then + launch_geekbench 5 + fi + [[ ! -z $JSON ]] && JSON_RESULT=${JSON_RESULT::${#JSON_RESULT}-1} && JSON_RESULT+=']' +fi + +# finished all tests, clean up all YYDS files and exit +echo -e +rm -rf $YYDS_PATH + +if [[ ! -z $JSON ]]; then + JSON_RESULT+='}' + + # write json results to file + if [[ $JSON = *w* ]]; then + echo $JSON_RESULT > $JSON_FILE + fi + + # send json results + if [[ $JSON = *s* ]]; then + IFS=',' read -r -a JSON_SITES <<< "$JSON_SEND" + for JSON_SITE in "${JSON_SITES[@]}" + do + if [[ ! -z $LOCAL_CURL ]]; then + curl -s -H "Content-Type:application/json" -X POST --data ''"$JSON_RESULT"'' $JSON_SITE + else + wget -qO- --post-data=''"$JSON_RESULT"'' --header='Content-Type:application/json' $JSON_SITE + fi + done + fi + + # print json result to screen + if [[ $JSON = *j* ]]; then + echo -e + echo $JSON_RESULT + fi +fi + +# reset locale settings +unset LC_ALL diff --git a/benchmark/tools/fio b/benchmark/tools/fio new file mode 100755 index 0000000..2cfcdd5 Binary files /dev/null and b/benchmark/tools/fio differ diff --git a/benchmark/tools/geekbench_5/geekbench.plar b/benchmark/tools/geekbench_5/geekbench.plar new file mode 100644 index 0000000..be92760 Binary files /dev/null and b/benchmark/tools/geekbench_5/geekbench.plar differ diff --git a/benchmark/tools/geekbench_5/geekbench5 b/benchmark/tools/geekbench_5/geekbench5 new file mode 100755 index 0000000..21741c6 Binary files /dev/null and b/benchmark/tools/geekbench_5/geekbench5 differ diff --git a/benchmark/tools/geekbench_5/geekbench_x86_64 b/benchmark/tools/geekbench_5/geekbench_x86_64 new file mode 100755 index 0000000..ce189ca Binary files /dev/null and b/benchmark/tools/geekbench_5/geekbench_x86_64 differ diff --git a/benchmark/tools/iperf3 b/benchmark/tools/iperf3 new file mode 100755 index 0000000..fb17a73 Binary files /dev/null and b/benchmark/tools/iperf3 differ diff --git a/config.json.template b/config.json.template new file mode 100644 index 0000000..504537f --- /dev/null +++ b/config.json.template @@ -0,0 +1,7 @@ +{ + "Host": "xxxx", + "Port": 22, + "Username": "xxx", + "Password": "xxx", + "Geekbench_License": "" +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0250a58 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module gitlab-ee.treesir.pub/go-projects/ssh-benchmark + +go 1.20 + +require ( + github.com/bitfield/script v0.22.0 + github.com/bramvdbogaerde/go-scp v1.2.1 + github.com/elulcao/progress-bar v0.1.1 + github.com/sirupsen/logrus v1.9.3 + golang.org/x/crypto v0.9.0 +) + +require ( + github.com/itchyny/gojq v0.12.12 // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect + golang.org/x/sys v0.8.0 // indirect + mvdan.cc/sh/v3 v3.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b83bf0f --- /dev/null +++ b/go.sum @@ -0,0 +1,66 @@ +github.com/bitfield/script v0.22.0 h1:LA7QHuEsXMPD52YLtxWrlqCCy+9FOpzNYfsRHC5Gsrc= +github.com/bitfield/script v0.22.0/go.mod h1:ms4w+9B8f2/W0mbsgWDVTtl7K94bYuZc3AunnJC4Ebs= +github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw= +github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elulcao/progress-bar v0.1.1 h1:yLVafDn4pDitkpqomzF5zixDRl0TySLX6unEUuYJ5wE= +github.com/elulcao/progress-bar v0.1.1/go.mod h1:ejyDVliG6R+ekjJY8BHPHFNJiIQV00zyWT7920D/Nls= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/itchyny/gojq v0.12.12 h1:x+xGI9BXqKoJQZkr95ibpe3cdrTbY8D9lonrK433rcA= +github.com/itchyny/gojq v0.12.12/go.mod h1:j+3sVkjxwd7A7Z5jrbKibgOLn0ZfLWkV+Awxr/pyzJE= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= +mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU= +mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA= diff --git a/images/README/2023-07-31 18.15.23 2.gif b/images/README/2023-07-31 18.15.23 2.gif new file mode 100644 index 0000000..7e83d3f Binary files /dev/null and b/images/README/2023-07-31 18.15.23 2.gif differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..2e58e48 --- /dev/null +++ b/main.go @@ -0,0 +1,276 @@ +package main + +import ( + "archive/zip" + "context" + "embed" + "encoding/json" + "fmt" + "io" + "log" + "math/rand" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/bitfield/script" + "github.com/bramvdbogaerde/go-scp" + pcmd "github.com/elulcao/progress-bar/cmd" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +type envConfig struct { + Host string `json:"Host"` + Port int `json:"Port"` + Username string `json:"Username"` + Password string `json:"Password"` + Geekbench_License string `json:"Geekbench_License"` +} + +// Start an iperf3 server locally. +func iperfServer() { + cmd := exec.Command("benchmark/tools/iperf3", "-s") + cmd.Run() +} + +func pBar(hsotNum int) { + pb := pcmd.NewPBar() + pb.SignalHandler() + pb.Total = uint16(150 * hsotNum) + + // pb.RenderPBar(i) + for i := 1; uint16(i) <= pb.Total; i++ { + pb.RenderPBar(i) + if uint16(i) == pb.Total-1 { + i -= 2 + } + time.Sleep(1 * time.Second) + } +} + +func Unzip(src string, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + fpath := filepath.Join(dest, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, os.ModePerm) + } else { + var dir string + if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 { + dir = fpath[:lastIndex] + } + err = os.MkdirAll(dir, os.ModePerm) + if err != nil { + log.Fatal(err) + return err + } + f, err := os.OpenFile( + fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + } + return nil +} + +func randomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +// Encapsulating Remote Execution of Shell Commands +func remoteShellExec(rClient ssh.Client, commands []string, logOut bool) error { + outputStr := "" + for _, command := range commands { + session, err := rClient.NewSession() + if err != nil { + logrus.Errorf("Failed to create session: %s", err.Error()) + } + defer session.Close() + output, err := session.CombinedOutput(command) + if err != nil { + logrus.Errorf("Failed to run: %s", err.Error()) + } + outputStr = string(output) + if logOut { + fmt.Println(" ") + fmt.Println(outputStr) + } + } + return nil +} + +// Test whether the SSH target host is reachable. +func testSSH(rClient ssh.Client) bool { + + session, err := rClient.NewSession() + if err != nil { + logrus.Error("Failed to dial: ", err) + return false + } + defer session.Close() + return true +} + +//go:embed benchmark.zip +var f embed.FS + +func main() { + srcDir := "benchmark" + dstDir := "/tmp/" + randomString(10) + "/" + + data, _ := f.ReadFile("benchmark.zip") + + // Write byte slices to temporary zip files. + tmpfile, err := os.CreateTemp("", "example") + if err != nil { + panic(err) + } + tmpfile.Write(data) + tmpfile.Close() + + // Extract files from embed. + Unzip(tmpfile.Name(), ".") + + file, err := os.Open("config.json") + if err != nil { + logrus.Error("Error opening file:", err) + return + } + defer file.Close() + envConfig := &envConfig{} + decoder := json.NewDecoder(file) + err = decoder.Decode(&envConfig) + if err != nil { + fmt.Println("Error decoding file:", err) + return + } + // Read the local config.json file and inject it as variables. + username := envConfig.Username + password := envConfig.Password + port := envConfig.Port + geekbenchLicense := envConfig.Geekbench_License + HostExecOut, _ := script.Exec("hostname -I").String() + hostIPS := strings.Split(HostExecOut, " ") + + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + // Print and start the motto. + logrus.Info("Benchmarking...") + + // Get current time. + start := time.Now() + + // Initialize bar cmd. + go pBar(len(strings.Split(envConfig.Host, ","))) + + // Create iperfServer coroutine. + go iperfServer() + + // Automatically close the iperfServer coroutine. + defer func() { + iperf3KillCmd := exec.Command("/bin/bash", "-c", "ps aux | awk '/iperf3/ {print $2}' | xargs kill -9") + iperf3KillCmd.Run() + cleanWorkspaceCmd := exec.Command("rm", "-rf", srcDir) + cleanWorkspaceCmd.Run() + }() + + // Use the testSSH function to test whether the target host is reachable. + for _, host := range strings.Split(envConfig.Host, ",") { + serverURL := fmt.Sprintf("%s:%d", host, port) + client, err := ssh.Dial("tcp", serverURL, config) + if err != nil { + logrus.Error("Failed to dial: ", err) + } + if testSSH(*client) { + logrus.Infof("Host: %s, SSH connection successful.", host) + } else { + logrus.Errorf("Host: %s, SSH connection failed.", host) + } + } + + // Split each host in config.Host by comma. + for _, host := range strings.Split(envConfig.Host, ",") { + serverURL := fmt.Sprintf("%s:%d", host, port) + + client, err := ssh.Dial("tcp", serverURL, config) + if err != nil { + logrus.Error("Failed to dial: ", err) + } + + _ = remoteShellExec(*client, []string{"mkdir -p " + dstDir}, false) + + filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + remoteShellExec(*client, []string{"mkdir -p " + dstDir + path}, false) + } else { + scpClient, err := scp.NewClientBySSH(client) + scpClient.Connect() + file, err := os.Open(path) + defer file.Close() + if err != nil { + logrus.Error("Failed to open file: ", err) + } + err = scpClient.CopyFile(context.Background(), file, dstDir+path, "0655") + if err != nil { + logrus.Error("Failed to copy file: ", err) + } + scpClient.Close() + + } + return nil + }) + + // Execute remote script. + logrus.Infof("Host: %s, Executing the remote script...", host) + + _ = remoteShellExec(*client, []string{ + "cd " + dstDir + srcDir + " && " + "./run.sh " + hostIPS[0] + " " + "'" + geekbenchLicense + "'", + }, true) + + // Delete remote file. + remoteShellExec(*client, []string{"rm -rf " + dstDir}, false) + } + + // Print execution time. + logrus.Infof("Total execution time for this run: %s", time.Since(start)) +} diff --git a/test/test.go b/test/test.go new file mode 100644 index 0000000..436b297 --- /dev/null +++ b/test/test.go @@ -0,0 +1,276 @@ +package main + +import ( + "archive/zip" + "context" + "embed" + "encoding/json" + "fmt" + "io" + "log" + "math/rand" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/bitfield/script" + "github.com/bramvdbogaerde/go-scp" + pcmd "github.com/elulcao/progress-bar/cmd" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +type envConfig struct { + Host string `json:"Host"` + Port int `json:"Port"` + Username string `json:"Username"` + Password string `json:"Password"` + Geekbench_License string `json:"Geekbench_License"` +} + +// Start an iperf3 server locally. +func iperfServer() { + cmd := exec.Command("benchmark/tools/iperf3", "-s") + cmd.Run() +} + +func pBar(hsotNum int) { + pb := pcmd.NewPBar() + pb.SignalHandler() + pb.Total = uint16(150 * hsotNum) + + // pb.RenderPBar(i) + for i := 1; uint16(i) <= pb.Total; i++ { + pb.RenderPBar(i) + if uint16(i) == pb.Total-1 { + i -= 2 + } + time.Sleep(1 * time.Second) + } +} + +func Unzip(src string, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + fpath := filepath.Join(dest, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, os.ModePerm) + } else { + var dir string + if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 { + dir = fpath[:lastIndex] + } + err = os.MkdirAll(dir, os.ModePerm) + if err != nil { + log.Fatal(err) + return err + } + f, err := os.OpenFile( + fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + } + return nil +} + +func randomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +// Encapsulating Remote Execution of Shell Commands +func remoteShellExec(rClient ssh.Client, commands []string, logOut bool) error { + outputStr := "" + for _, command := range commands { + session, err := rClient.NewSession() + if err != nil { + logrus.Errorf("Failed to create session: %s", err.Error()) + } + defer session.Close() + output, err := session.CombinedOutput(command) + if err != nil { + logrus.Errorf("Failed to run: %s", err.Error()) + } + outputStr = string(output) + if logOut { + fmt.Println(" ") + fmt.Println(outputStr) + } + } + return nil +} + +// Test whether the SSH target host is reachable. +func testSSH(rClient ssh.Client) bool { + + session, err := rClient.NewSession() + if err != nil { + logrus.Error("Failed to dial: ", err) + return false + } + defer session.Close() + return true +} + +//go:embed benchmark.zip +var f embed.FS + +func main() { + srcDir := "benchmark" + dstDir := "/tmp/" + randomString(10) + "/" + + data, _ := f.ReadFile("benchmark.zip") + + // Write byte slices to temporary zip files. + tmpfile, err := os.CreateTemp("", "example") + if err != nil { + panic(err) + } + tmpfile.Write(data) + tmpfile.Close() + + // Extract files from embed. + Unzip(tmpfile.Name(), ".") + + file, err := os.Open("config.json") + if err != nil { + logrus.Error("Error opening file:", err) + return + } + defer file.Close() + envConfig := &envConfig{} + decoder := json.NewDecoder(file) + err = decoder.Decode(&envConfig) + if err != nil { + fmt.Println("Error decoding file:", err) + return + } + // Read the local config.json file and inject it as variables. + username := envConfig.Username + password := envConfig.Password + port := envConfig.Port + geekbenchLicense := envConfig.Geekbench_License + HostExecOut, _ := script.Exec("hostname -I").String() + hostIPS := strings.Split(HostExecOut, " ") + + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + // Print and start the motto. + logrus.Info("Benchmarking...") + + // Get current time. + start := time.Now() + + // 初始化 bar cmd + go pBar(len(strings.Split(envConfig.Host, ","))) + + // 创建 iperfServer 协程 + go iperfServer() + + // Automatically close the iperfServer coroutine. + defer func() { + iperf3KillCmd := exec.Command("/bin/bash", "-c", "ps aux | awk '/iperf3/ {print $2}' | xargs kill -9") + iperf3KillCmd.Run() + cleanWorkspaceCmd := exec.Command("rm", "-rf", srcDir) + cleanWorkspaceCmd.Run() + }() + + // Use the testSSH function to test whether the target host is reachable. + for _, host := range strings.Split(envConfig.Host, ",") { + serverURL := fmt.Sprintf("%s:%d", host, port) + client, err := ssh.Dial("tcp", serverURL, config) + if err != nil { + logrus.Error("Failed to dial: ", err) + } + if testSSH(*client) { + logrus.Infof("Host: %s, SSH connection successful.", host) + } else { + logrus.Errorf("Host: %s, SSH connection failed.", host) + } + } + + // Split each host in config.Host by comma. + for _, host := range strings.Split(envConfig.Host, ",") { + serverURL := fmt.Sprintf("%s:%d", host, port) + + client, err := ssh.Dial("tcp", serverURL, config) + if err != nil { + logrus.Error("Failed to dial: ", err) + } + + _ = remoteShellExec(*client, []string{"mkdir -p " + dstDir}, false) + + filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + remoteShellExec(*client, []string{"mkdir -p " + dstDir + path}, false) + } else { + scpClient, err := scp.NewClientBySSH(client) + scpClient.Connect() + file, err := os.Open(path) + defer file.Close() + if err != nil { + logrus.Error("Failed to open file: ", err) + } + err = scpClient.CopyFile(context.Background(), file, dstDir+path, "0655") + if err != nil { + logrus.Error("Failed to copy file: ", err) + } + scpClient.Close() + + } + return nil + }) + + // Execute remote script. + logrus.Infof("Host: %s, Executing the remote script...", host) + + _ = remoteShellExec(*client, []string{ + "cd " + dstDir + srcDir + " && " + "./run.sh " + hostIPS[0] + " " + "'" + geekbenchLicense + "'", + }, true) + + // Delete remote file. + remoteShellExec(*client, []string{"rm -rf " + dstDir}, false) + } + + // Print execution time. + logrus.Infof("Total execution time for this run: %s", time.Since(start)) +}