Init: project - initial commit.

Author:    cdryzun <yangzun@treesir.pub>
Date:      Mon Jul 31 18:49:41 2023 +0800
This commit is contained in:
zun.yang 2023-07-31 18:49:41 +08:00 committed by cdryzun
commit 458d87e5ed
16 changed files with 1712 additions and 0 deletions

21
LICENSE Normal file
View File

@ -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.

48
README-CN.md Normal file
View File

@ -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的许可证可以通过电子邮件联系我<yangzun@treesir.pub>)。我愿意与您分享。
> 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)

51
README.md Normal file
View File

@ -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 (<yangzun@treesir.pub>). 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)

48
Taskfile.yaml Normal file
View File

@ -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

901
benchmark/run.sh Executable file
View File

@ -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 <filename> : write jsonified YYDS results to disk using file name provided"
echo -e " -s <url> : 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

BIN
benchmark/tools/fio Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
benchmark/tools/iperf3 Executable file

Binary file not shown.

7
config.json.template Normal file
View File

@ -0,0 +1,7 @@
{
"Host": "xxxx",
"Port": 22,
"Username": "xxx",
"Password": "xxx",
"Geekbench_License": ""
}

18
go.mod Normal file
View File

@ -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
)

66
go.sum Normal file
View File

@ -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=

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

276
main.go Normal file
View File

@ -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))
}

276
test/test.go Normal file
View File

@ -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))
}