diff --git a/cmd/main.go b/cmd/main.go index 7466c18..2589936 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ func main() { disktestFlag.StringVar(&testMethod, "m", "", "Specific Test Method (dd or fio)") disktestFlag.StringVar(&multiDisk, "d", "", "Enable multi disk check parameter (single or multi, default is single)") disktestFlag.StringVar(&testPath, "p", "", "Specific Test Disk Path (default is /root or C:)") + disktestFlag.BoolVar(&disk.EnableLoger, "log", false, "Enable logging") disktestFlag.Parse(os.Args[1:]) if help { fmt.Printf("Usage: %s [options]\n", os.Args[0]) diff --git a/disk/disktest.go b/disk/disktest.go index 8c3fd0f..8e45190 100644 --- a/disk/disktest.go +++ b/disk/disktest.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" + . "github.com/oneclickvirt/defaultset" "github.com/shirou/gopsutil/disk" ) @@ -42,44 +43,72 @@ func WinsatTest(language string, enableMultiCheck bool, testPath string) string // execDDTest 执行dd命令测试硬盘IO,并回传结果和测试错误 func execDDTest(ifKey, ofKey, bs, blockCount string) (string, error) { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } var tempText string cmd2 := exec.Command("sudo", "dd", "if="+ifKey, "of="+ofKey, "bs="+bs, "count="+blockCount, "oflag=direct") stderr2, err := cmd2.StderrPipe() - if err == nil { - if err := cmd2.Start(); err == nil { - outputBytes, err := io.ReadAll(stderr2) - if err == nil { - tempText = string(outputBytes) - } else { - return "", err - } - } else { - return "", err + if err != nil { + if EnableLoger { + Logger.Info("failed to get StderrPipe: " + err.Error()) } - } else { return "", err } + if err := cmd2.Start(); err != nil { + if EnableLoger { + Logger.Info("failed to start command: " + err.Error()) + } + return "", err + } + outputBytes, err := io.ReadAll(stderr2) + if err != nil { + if EnableLoger { + Logger.Info("failed to read stderr: " + err.Error()) + } + return "", err + } + + tempText = string(outputBytes) return tempText, nil } // ddTest1 无重试机制 func ddTest1(path, deviceName, blockFile, blockName, blockCount, bs string) string { var result string + if EnableLoger { + InitLogger() + defer Logger.Sync() + } // 写入测试 - // dd if=/dev/zero of=/tmp/100MB.test bs=4k count=25600 oflag=direct tempText, err := execDDTest("/dev/zero", path+blockFile, bs, blockCount) defer os.Remove(path + blockFile) - if err == nil { + if err != nil { + if EnableLoger { + Logger.Info("Write test error: " + err.Error()) + } + } else { result += fmt.Sprintf("%-10s", strings.TrimSpace(deviceName)) + " " + fmt.Sprintf("%-15s", blockName) + " " result += parseResultDD(tempText, blockCount) } // 读取测试 - // dd if=/tmp/100MB.test of=/dev/null bs=4k count=25600 oflag=direct tempText, err = execDDTest(path+blockFile, "/dev/null", bs, blockCount) defer os.Remove(path + blockFile) + if err != nil { + if EnableLoger { + Logger.Info("Read test error: " + err.Error()) + } + } if err != nil || strings.Contains(tempText, "Invalid argument") || strings.Contains(tempText, "Permission denied") { - tempText, _ = execDDTest(path+blockFile, path+"/read"+blockFile, bs, blockCount) + if err != nil && EnableLoger { + Logger.Info("Read test (first attempt) error: " + err.Error()) + } + tempText, err = execDDTest(path+blockFile, path+"/read"+blockFile, bs, blockCount) defer os.Remove(path + "/read" + blockFile) + if err != nil && EnableLoger { + Logger.Info("Read test (second attempt) error: " + err.Error()) + } } result += parseResultDD(tempText, blockCount) result += "\n" @@ -89,13 +118,27 @@ func ddTest1(path, deviceName, blockFile, blockName, blockCount, bs string) stri // ddTest2 有重试机制,重试至于 /tmp 目录 func ddTest2(blockFile, blockName, blockCount, bs string) string { var result string - // 写入测试 var testFilePath string + if EnableLoger { + InitLogger() + defer Logger.Sync() + } + // 写入测试 tempText, err := execDDTest("/dev/zero", "/root/"+blockFile, bs, blockCount) defer os.Remove("/root/" + blockFile) + if err != nil { + if EnableLoger { + Logger.Info("execDDTest error for /root/ path: " + err.Error()) + } + } if err != nil || strings.Contains(tempText, "Invalid argument") || strings.Contains(tempText, "Permission denied") { - tempText, _ = execDDTest("/dev/zero", "/tmp/"+blockFile, bs, blockCount) + tempText, err = execDDTest("/dev/zero", "/tmp/"+blockFile, bs, blockCount) defer os.Remove("/tmp/" + blockFile) + if err != nil { + if EnableLoger { + Logger.Info("execDDTest error for /tmp/ path: " + err.Error()) + } + } testFilePath = "/tmp/" result += fmt.Sprintf("%-10s", "/tmp") + " " + fmt.Sprintf("%-15s", blockName) + " " } else { @@ -106,10 +149,20 @@ func ddTest2(blockFile, blockName, blockCount, bs string) string { // 读取测试 tempText, err = execDDTest("/root/"+blockFile, "/dev/null", bs, blockCount) defer os.Remove("/root/" + blockFile) + if err != nil { + if EnableLoger { + Logger.Info("execDDTest read error for /root/ path: " + err.Error()) + } + } if err != nil || strings.Contains(tempText, "Invalid argument") || strings.Contains(tempText, "Permission denied") { - tempText, _ = execDDTest(testFilePath+blockFile, "/tmp/read"+blockFile, bs, blockCount) + tempText, err = execDDTest(testFilePath+blockFile, "/tmp/read"+blockFile, bs, blockCount) defer os.Remove(testFilePath + blockFile) defer os.Remove("/tmp/read" + blockFile) + if err != nil { + if EnableLoger { + Logger.Info("execDDTest read error for /tmp/ path: " + err.Error()) + } + } } result += parseResultDD(tempText, blockCount) result += "\n" @@ -161,31 +214,45 @@ func DDTest(language string, enableMultiCheck bool, testPath string) string { // buildFioFile 生成对应文件 func buildFioFile(path, fioSize string) (string, error) { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } // https://github.com/masonr/yet-another-bench-script/blob/0ad4c4e85694dbcf0958d8045c2399dbd0f9298c/yabs.sh#L435 // fio --name=setup --ioengine=libaio --rw=read --bs=64k --iodepth=64 --numjobs=2 --size=512MB --runtime=1 --gtod_reduce=1 --filename="/tmp/test.fio" --direct=1 --minimal var tempText string cmd1 := exec.Command("sudo", "fio", "--name=setup", "--ioengine=libaio", "--rw=read", "--bs=64k", "--iodepth=64", "--numjobs=2", "--size="+fioSize, "--runtime=1", "--gtod_reduce=1", "--filename=\""+path+"/test.fio\"", "--direct=1", "--minimal") stderr1, err := cmd1.StderrPipe() - if err == nil { - if err := cmd1.Start(); err == nil { - outputBytes, err := io.ReadAll(stderr1) - if err == nil { - tempText = string(outputBytes) - return tempText, nil - } else { - return "", err - } - } else { - return "", err + if err != nil { + if EnableLoger { + Logger.Info("failed to get stderr pipe: " + err.Error()) + } + return "", err + } + if err := cmd1.Start(); err != nil { + if EnableLoger { + Logger.Info("failed to start fio command: " + err.Error()) } - } else { return "", err } + outputBytes, err := io.ReadAll(stderr1) + if err != nil { + if EnableLoger { + Logger.Info("failed to read stderr: " + err.Error()) + } + return "", err + } + tempText = string(outputBytes) + return tempText, nil } // execFioTest 使用fio测试文件进行测试 func execFioTest(path, devicename, fioSize string) (string, error) { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } var result string // 测试 blockSizes := []string{"4k", "64k", "512k", "1m"} @@ -193,7 +260,12 @@ func execFioTest(path, devicename, fioSize string) (string, error) { // timeout 35 fio --name=rand_rw_4k --ioengine=libaio --rw=randrw --rwmixread=50 --bs=4k --iodepth=64 --numjobs=2 --size=512MB --runtime=30 --gtod_reduce=1 --direct=1 --filename="/tmp/test.fio" --group_reporting --minimal cmd2 := exec.Command("timeout", "35", "sudo", "fio", "--name=rand_rw_"+BS, "--ioengine=libaio", "--rw=randrw", "--rwmixread=50", "--bs="+BS, "--iodepth=64", "--numjobs=2", "--size="+fioSize, "--runtime=30", "--gtod_reduce=1", "--direct=1", "--filename=\""+path+"/test.fio\"", "--group_reporting", "--minimal") output, err := cmd2.Output() - if err == nil { + if err != nil { + if EnableLoger { + Logger.Info("failed to execute fio command: " + err.Error()) + } + return "", err + } else { tempText := string(output) tempList := strings.Split(tempText, "\n") for _, l := range tempList { @@ -220,8 +292,6 @@ func execFioTest(path, devicename, fioSize string) (string, error) { result += "\n" } } - } else { - return "", err } } return result, nil @@ -229,9 +299,16 @@ func execFioTest(path, devicename, fioSize string) (string, error) { // FioTest 通过fio测试硬盘 func FioTest(language string, enableMultiCheck bool, testPath string) string { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } cmd := exec.Command("fio", "-v") _, err := cmd.CombinedOutput() if err != nil { + if EnableLoger { + Logger.Info("failed to match fio version: " + err.Error()) + } return "" } var ( @@ -306,15 +383,3 @@ func FioTest(language string, enableMultiCheck bool, testPath string) string { } return result } - -// func Sysbench(language string) string { -// var result string -// comCheck := exec.Command("sysbench", "--version") -// output, err := comCheck.CombinedOutput() -// if err == nil { - -// } else { -// return "" -// } -// return result -// } diff --git a/disk/utils.go b/disk/utils.go index f16418b..f4b69e4 100644 --- a/disk/utils.go +++ b/disk/utils.go @@ -6,13 +6,22 @@ import ( "os/exec" "strconv" "strings" + + . "github.com/oneclickvirt/defaultset" ) // 获取硬盘性能数据 func getDiskPerformance(device string) string { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } cmd := exec.Command("winsat", "disk", "-drive", device) output, err := cmd.Output() if err != nil { + if EnableLoger { + Logger.Info("cannot match winsat command: " + err.Error()) + } return "" } var result string @@ -47,19 +56,40 @@ func getDiskPerformance(device string) string { // isWritableMountpoint 检测挂载点是否为文件夹且可写入文件 func isWritableMountpoint(path string) bool { + if EnableLoger { + InitLogger() + defer Logger.Sync() + } // 检测 mountpoint 是否是一个文件夹 info, err := os.Stat(path) - if err != nil || !info.IsDir() { + if err != nil { + if EnableLoger { + Logger.Info("cannot stat path: " + err.Error()) + } + return false + } + if !info.IsDir() { + if EnableLoger { + Logger.Info("path is not a directory: " + path) + } return false } // 尝试打开文件进行写入 file, err := os.OpenFile(path+"/.temp_write_check", os.O_WRONLY|os.O_CREATE, 0666) if err != nil { + if EnableLoger { + Logger.Info("cannot open file for writing: " + err.Error()) + } return false } defer file.Close() // 删除临时文件 - os.Remove(path + "/.temp_write_check") + err = os.Remove(path + "/.temp_write_check") + if err != nil { + if EnableLoger { + Logger.Info("cannot remove temporary file: " + err.Error()) + } + } return true } @@ -97,7 +127,6 @@ func formatIOPS(raw interface{}, rawType string) string { // 确保 raw 值不为空,如果为空则返回空字符串 var iops int var err error - switch v := raw.(type) { case string: if v == "" { @@ -132,40 +161,40 @@ func formatIOPS(raw interface{}, rawType string) string { // formatSpeed 转换fio的测试中的TEST的值 // rawType 支持 string 或 float64 func formatSpeed(raw interface{}, rawType string) string { - var rawFloat float64 - var err error - // 根据 rawType 确定如何处理 raw 的类型 - switch v := raw.(type) { - case string: - if v == "" { - return "" - } - // 将 raw 字符串转换为 float64 - rawFloat, err = strconv.ParseFloat(v, 64) - if err != nil { - return "" - } - case float64: - rawFloat = v - default: - return "" - } - // 初始化结果相关变量 - var resultFloat float64 = rawFloat - var denom float64 = 1 - unit := "KB/s" - // 根据速度大小确定单位 - if rawFloat >= 1000000 { - denom = 1000000 - unit = "GB/s" - } else if rawFloat >= 1000 { - denom = 1000 - unit = "MB/s" - } - // 根据单位除以相应的分母以得到格式化后的结果 - resultFloat /= denom - // 将格式化结果保留两位小数 - result := fmt.Sprintf("%.2f", resultFloat) - // 将格式化结果值与单位拼接并返回结果 - return strings.Join([]string{result, unit}, " ") + var rawFloat float64 + var err error + // 根据 rawType 确定如何处理 raw 的类型 + switch v := raw.(type) { + case string: + if v == "" { + return "" + } + // 将 raw 字符串转换为 float64 + rawFloat, err = strconv.ParseFloat(v, 64) + if err != nil { + return "" + } + case float64: + rawFloat = v + default: + return "" + } + // 初始化结果相关变量 + var resultFloat float64 = rawFloat + var denom float64 = 1 + unit := "KB/s" + // 根据速度大小确定单位 + if rawFloat >= 1000000 { + denom = 1000000 + unit = "GB/s" + } else if rawFloat >= 1000 { + denom = 1000 + unit = "MB/s" + } + // 根据单位除以相应的分母以得到格式化后的结果 + resultFloat /= denom + // 将格式化结果保留两位小数 + result := fmt.Sprintf("%.2f", resultFloat) + // 将格式化结果值与单位拼接并返回结果 + return strings.Join([]string{result, unit}, " ") } diff --git a/disk/version.go b/disk/version.go index d70c587..cf47d33 100644 --- a/disk/version.go +++ b/disk/version.go @@ -1,3 +1,5 @@ package disk const DiskTestVersion = "v0.0.4" + +var EnableLoger = false diff --git a/go.mod b/go.mod index 05444ea..3dba128 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ require github.com/shirou/gopsutil v3.21.11+incompatible require ( github.com/go-ole/go-ole v1.2.6 // indirect + github.com/oneclickvirt/defaultset v0.0.2-20240624082446 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/sys v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 1753c58..5d372ed 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/oneclickvirt/defaultset v0.0.2-20240624082446 h1:5Pg3mK/u/vQvSz7anu0nxzrNdELi/AcDAU1mMsmPzyc= +github.com/oneclickvirt/defaultset v0.0.2-20240624082446/go.mod h1:e9Jt4tf2sbemCtc84/XgKcHy9EZ2jkc5x2sW1NiJS+E= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=