Skip to content

Commit 40ecc69

Browse files
authored
Merge pull request #1 from oneclickvirt/copilot/fix-c078e406-1598-4fa9-b5bc-b525db8b3b03
feat: Add STREAM as highest priority memory test method with automatic fallback
2 parents 9fb37ca + b8d7245 commit 40ecc69

5 files changed

Lines changed: 231 additions & 14 deletions

File tree

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Build artifacts
2+
memorytest
3+
/cmd/cmd
4+
*.exe
5+
6+
# Temporary files
7+
/tmp/
8+
9+
# IDE files
10+
.vscode/
11+
.idea/
12+
13+
# OS files
14+
.DS_Store
15+
Thumbs.db

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@
88

99
# 功能(Features)
1010

11-
- [x] 支持使用```sysbench```测试内存的顺序读写IO
12-
- [x] 支持使用```dd```测试内存的读写IO
11+
- [x] 支持使用```stream```进行高性能内存带宽测试 (最高优先级)
12+
- [x] 支持使用```dd```测试内存的读写IO (可选方式)
13+
- [x] 支持使用```sysbench```测试内存的顺序读写IO (兜底实现)
1314
- [x] 支持使用```winsat```测试内存的读写性能
1415
- [x] 支持Go自身静态依赖注入[dd](https://github.com/oneclickvirt/dd),使用时无额外环境依赖需求
1516
- [x]```-l```指定输出的语言类型,可指定```zh``````en```,默认不指定时使用中文输出
16-
- [x]```-m```指定测试的方法,可指定```sysbench``````dd``````winsat```,默认不指定时使用```sysbench```进行测试
17+
- [x]```-m```指定测试的方法,可指定```stream``````dd``````sysbench``````winsat```,默认不指定时按优先级自动选择测试方法
1718
- [x] 全平台编译支持,支持无权限测试时使用C重构或自编译的mbw程序模拟大内存COPY块测试内存性能
1819

20+
## 测试方法优先级
21+
当不指定`-m`参数时,程序按以下优先级自动选择测试方法:
22+
1. **STREAM** - 如果检测到stream二进制文件,优先使用
23+
2. **DD** - 如果STREAM不可用,使用DD测试
24+
3. **Sysbench** - 作为最终的兜底实现
25+
1926
注意:默认不自动安装```sysbench```组件,如需使用请自行安装后再使用本项目,如```apt update && apt install sysbench -y```
2027

2128
# 使用(Usage)
@@ -48,7 +55,7 @@ Usage: memorytest [options]
4855
-log
4956
Enable logging
5057
-m string
51-
Specific Test Method (sysbench or dd)
58+
Specific Test Method (stream or dd or sysbench or winsat)
5259
-v show version
5360
```
5461

cmd/main.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func main() {
2323
memorytestFlag.BoolVar(&help, "h", false, "Show help information")
2424
memorytestFlag.BoolVar(&showVersion, "v", false, "show version")
2525
memorytestFlag.StringVar(&language, "l", "", "Language parameter (en or zh)")
26-
memorytestFlag.StringVar(&testMethod, "m", "", "Specific Test Method (sysbench or dd or winsat)")
26+
memorytestFlag.StringVar(&testMethod, "m", "", "Specific Test Method (stream or dd or sysbench or winsat)")
2727
memorytestFlag.BoolVar(&memory.EnableLoger, "log", false, "Enable logging")
2828
memorytestFlag.Parse(os.Args[1:])
2929
if help {
@@ -41,13 +41,21 @@ func main() {
4141
} else {
4242
language = strings.ToLower(language)
4343
}
44-
if testMethod == "" || strings.ToLower(testMethod) == "sysbench" {
45-
testMethod = "sysbench"
46-
} else if strings.ToLower(testMethod) == "dd" {
47-
testMethod = "dd"
44+
// Parse and normalize test method
45+
testMethod = strings.ToLower(testMethod)
46+
if testMethod == "" {
47+
// Use automatic priority: stream > dd > sysbench (with mbw fallback)
48+
testMethod = "auto"
4849
}
4950
if runtime.GOOS == "windows" {
5051
switch testMethod {
52+
case "stream":
53+
if language == "en" {
54+
res = "STREAM is not supported on Windows, using Winsat for testing.\n"
55+
} else {
56+
res = "Windows下不支持STREAM,使用Winsat进行测试。\n"
57+
}
58+
res += memory.WinsatTest(language)
5159
case "dd":
5260
res = memory.WindowsDDTest(language)
5361
if res == "" || strings.TrimSpace(res) == "" {
@@ -66,7 +74,8 @@ func main() {
6674
}
6775
res += memory.WinsatTest(language)
6876
default:
69-
if testMethod != "winsat" && testMethod != "" {
77+
// For auto or winsat or any other method
78+
if testMethod != "winsat" && testMethod != "auto" {
7079
if language == "en" {
7180
res = "Detected host is Windows, using Winsat for testing.\n"
7281
} else {
@@ -77,14 +86,39 @@ func main() {
7786
}
7887
} else {
7988
switch testMethod {
80-
case "sysbench":
81-
res = memory.SysBenchTest(language)
82-
if res == "" {
83-
res = "sysbench test failed, switch to use dd test.\n"
89+
case "stream":
90+
res = memory.StreamTest(language)
91+
if res == "" || strings.TrimSpace(res) == "" {
92+
if language == "en" {
93+
res = "STREAM test failed, switching to DD for testing.\n"
94+
} else {
95+
res = "STREAM测试失败,切换使用DD进行测试。\n"
96+
}
8497
res += memory.DDTest(language)
8598
}
8699
case "dd":
87100
res = memory.DDTest(language)
101+
case "sysbench":
102+
res = memory.SysBenchTest(language)
103+
if res == "" || strings.TrimSpace(res) == "" {
104+
if language == "en" {
105+
res = "Sysbench test failed, switching to DD for testing.\n"
106+
} else {
107+
res = "Sysbench测试失败,切换使用DD进行测试。\n"
108+
}
109+
res += memory.DDTest(language)
110+
}
111+
case "auto":
112+
// Priority: stream > dd > sysbench (with mbw fallback built into each)
113+
res = memory.StreamTest(language)
114+
if res == "" || strings.TrimSpace(res) == "" {
115+
// Stream failed or not available, try DD
116+
res = memory.DDTest(language)
117+
if res == "" || strings.TrimSpace(res) == "" {
118+
// DD failed, try sysbench as final fallback
119+
res = memory.SysBenchTest(language)
120+
}
121+
}
88122
default:
89123
res = "Unsupported test method"
90124
}

memory/macos_linux_mem.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,97 @@ func DDTest(language string) string {
484484
}
485485
return result
486486
}
487+
488+
// StreamTest 使用 stream 进行内存测试
489+
func StreamTest(language string) string {
490+
if EnableLoger {
491+
InitLogger()
492+
defer Logger.Sync()
493+
Logger.Info("Running STREAM memory test")
494+
}
495+
496+
// Try different stream binary names based on architecture
497+
streamBinaries := []string{
498+
"./stream-linux-amd64",
499+
"./stream",
500+
"stream-linux-amd64",
501+
"stream",
502+
}
503+
504+
var streamCmd string
505+
for _, binary := range streamBinaries {
506+
if _, err := os.Stat(binary); err == nil {
507+
streamCmd = binary
508+
break
509+
}
510+
// Also check if it's available in PATH
511+
if _, err := exec.LookPath(binary); err == nil {
512+
streamCmd = binary
513+
break
514+
}
515+
}
516+
517+
if streamCmd == "" {
518+
if EnableLoger {
519+
Logger.Warn("STREAM binary not found, falling back to DD test")
520+
}
521+
return "" // Return empty to indicate fallback needed
522+
}
523+
524+
// Execute STREAM test
525+
cmd := exec.Command(streamCmd)
526+
output, err := cmd.CombinedOutput()
527+
if err != nil {
528+
if EnableLoger {
529+
Logger.Error(fmt.Sprintf("STREAM test failed: %v", err))
530+
}
531+
return "" // Return empty to indicate fallback needed
532+
}
533+
534+
// Parse STREAM output to extract the Function section
535+
return parseStreamOutput(string(output), language)
536+
}
537+
538+
// parseStreamOutput 解析 STREAM 输出,提取 Function 部分
539+
func parseStreamOutput(output, language string) string {
540+
lines := strings.Split(output, "\n")
541+
var result strings.Builder
542+
543+
// Find the start and end of the Function section
544+
inFunctionSection := false
545+
functionHeaderFound := false
546+
547+
for _, line := range lines {
548+
// Look for the Function header line
549+
if strings.Contains(line, "Function") && strings.Contains(line, "Best Rate MB/s") {
550+
functionHeaderFound = true
551+
inFunctionSection = true
552+
result.WriteString(line)
553+
result.WriteString("\n")
554+
continue
555+
}
556+
557+
// If we found the header, keep collecting lines until we hit a line of dashes or empty line
558+
if functionHeaderFound && inFunctionSection {
559+
trimmedLine := strings.TrimSpace(line)
560+
// Stop when we encounter the ending dashes or validation line
561+
if strings.HasPrefix(trimmedLine, "---") || strings.Contains(trimmedLine, "Solution Validates") {
562+
break
563+
}
564+
// Skip empty lines at the beginning, but include data lines
565+
if trimmedLine != "" {
566+
result.WriteString(line)
567+
result.WriteString("\n")
568+
}
569+
}
570+
}
571+
572+
if !functionHeaderFound {
573+
if EnableLoger {
574+
Logger.Error("Could not parse STREAM output - Function section not found")
575+
}
576+
return "" // Return empty to indicate parsing failed
577+
}
578+
579+
return result.String()
580+
}

memory/stream_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseStreamOutput(t *testing.T) {
8+
// Sample STREAM output based on the problem statement
9+
sampleOutput := `-------------------------------------------------------------
10+
STREAM version $Revision: 5.10 $
11+
-------------------------------------------------------------
12+
This system uses 8 bytes per array element.
13+
-------------------------------------------------------------
14+
Array size = 10000000 (elements), Offset = 0 (elements)
15+
Memory per array = 76.3 MiB (= 0.1 GiB).
16+
Total memory required = 228.9 MiB (= 0.2 GiB).
17+
Each kernel will be executed 10 times.
18+
The *best* time for each kernel (excluding the first iteration)
19+
will be used to compute the reported bandwidth.
20+
-------------------------------------------------------------
21+
Your clock granularity/precision appears to be 1 microseconds.
22+
Each test below will take on the order of 1008115 microseconds.
23+
(= 1008115 clock ticks)
24+
Increase the size of the arrays if this shows that
25+
you are not getting at least 20 clock ticks per test.
26+
-------------------------------------------------------------
27+
WARNING -- The above is only a rough guideline.
28+
For best results, please be sure you know the
29+
precision of your system timer.
30+
-------------------------------------------------------------
31+
Function Best Rate MB/s Avg time Min time Max time
32+
Copy: 21792.8 0.011733 0.007342 0.031549
33+
Scale: 14821.8 0.019170 0.010795 0.051002
34+
Add: 16917.9 0.026095 0.014186 0.058414
35+
Triad: 17097.8 0.024922 0.014037 0.049033
36+
-------------------------------------------------------------
37+
Solution Validates: avg error less than 1.000000e-13 on all three arrays
38+
-------------------------------------------------------------`
39+
40+
expected := `Function Best Rate MB/s Avg time Min time Max time
41+
Copy: 21792.8 0.011733 0.007342 0.031549
42+
Scale: 14821.8 0.019170 0.010795 0.051002
43+
Add: 16917.9 0.026095 0.014186 0.058414
44+
Triad: 17097.8 0.024922 0.014037 0.049033
45+
`
46+
47+
result := parseStreamOutput(sampleOutput, "en")
48+
49+
if result != expected {
50+
t.Errorf("parseStreamOutput failed.\nExpected:\n%s\nGot:\n%s", expected, result)
51+
}
52+
}
53+
54+
func TestParseStreamOutputNoFunction(t *testing.T) {
55+
// Test with output that doesn't contain Function section
56+
sampleOutput := `-------------------------------------------------------------
57+
STREAM version $Revision: 5.10 $
58+
-------------------------------------------------------------
59+
Some other output without Function section
60+
-------------------------------------------------------------`
61+
62+
result := parseStreamOutput(sampleOutput, "en")
63+
64+
if result != "" {
65+
t.Errorf("parseStreamOutput should return empty string for invalid output, got: %s", result)
66+
}
67+
}

0 commit comments

Comments
 (0)