gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/benchmarks/tools/sysbench.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tools 16 17 import ( 18 "fmt" 19 "regexp" 20 "strconv" 21 "testing" 22 ) 23 24 // Sysbench represents a 'sysbench' command. 25 type Sysbench interface { 26 // MakeCmd constructs the relevant command line. 27 MakeCmd(*testing.B) []string 28 29 // Report reports relevant custom metrics. 30 Report(*testing.B, string) 31 } 32 33 // SysbenchBase is the top level struct for sysbench and holds top-level arguments 34 // for sysbench. See: 'sysbench --help' 35 type SysbenchBase struct { 36 // Threads is the number of threads for the test. 37 Threads int 38 } 39 40 // baseFlags returns top level flags. 41 func (s *SysbenchBase) baseFlags(b *testing.B, useEvents bool) []string { 42 var ret []string 43 if s.Threads > 0 { 44 ret = append(ret, fmt.Sprintf("--threads=%d", s.Threads)) 45 } 46 ret = append(ret, "--time=0") // Ensure other mechanism is used. 47 if useEvents { 48 ret = append(ret, fmt.Sprintf("--events=%d", b.N)) 49 } 50 return ret 51 } 52 53 // SysbenchCPU is for 'sysbench [flags] cpu run' and holds CPU specific arguments. 54 type SysbenchCPU struct { 55 SysbenchBase 56 } 57 58 // MakeCmd makes commands for SysbenchCPU. 59 func (s *SysbenchCPU) MakeCmd(b *testing.B) []string { 60 cmd := []string{"sysbench"} 61 cmd = append(cmd, s.baseFlags(b, true /* useEvents */)...) 62 cmd = append(cmd, "cpu", "run") 63 return cmd 64 } 65 66 // Report reports the relevant metrics for SysbenchCPU. 67 func (s *SysbenchCPU) Report(b *testing.B, output string) { 68 b.Helper() 69 result, err := s.parseEvents(output) 70 if err != nil { 71 b.Fatalf("parsing CPU events from %s failed: %v", output, err) 72 } 73 ReportCustomMetric(b, result, "cpu_events" /*metric name*/, "events_per_second" /*unit*/) 74 } 75 76 var cpuEventsPerSecondRE = regexp.MustCompile(`events per second:\s*(\d*.?\d*)\n`) 77 78 // parseEvents parses cpu events per second. 79 func (s *SysbenchCPU) parseEvents(data string) (float64, error) { 80 match := cpuEventsPerSecondRE.FindStringSubmatch(data) 81 if len(match) < 2 { 82 return 0.0, fmt.Errorf("could not find events per second: %s", data) 83 } 84 return strconv.ParseFloat(match[1], 64) 85 } 86 87 // SysbenchMemory is for 'sysbench [FLAGS] memory run' and holds Memory specific arguments. 88 type SysbenchMemory struct { 89 SysbenchBase 90 BlockSize int // size of test memory block in megabytes [1]. 91 Scope string // memory access scope {global, local} [global]. 92 HugeTLB bool // allocate memory from HugeTLB [off]. 93 OperationType string // type of memory ops {read, write, none} [write]. 94 AccessMode string // access mode {seq, rnd} [seq]. 95 } 96 97 // MakeCmd makes commands for SysbenchMemory. 98 func (s *SysbenchMemory) MakeCmd(b *testing.B) []string { 99 cmd := []string{"sysbench"} 100 cmd = append(cmd, s.flags(b)...) 101 cmd = append(cmd, "memory", "run") 102 return cmd 103 } 104 105 // flags makes flags for SysbenchMemory cmds. 106 func (s *SysbenchMemory) flags(b *testing.B) []string { 107 cmd := s.baseFlags(b, false /* useEvents */) 108 if s.BlockSize != 0 { 109 cmd = append(cmd, fmt.Sprintf("--memory-block-size=%dM", s.BlockSize)) 110 } 111 if s.Scope != "" { 112 cmd = append(cmd, fmt.Sprintf("--memory-scope=%s", s.Scope)) 113 } 114 if s.HugeTLB { 115 cmd = append(cmd, "--memory-hugetlb=on") 116 } 117 if s.OperationType != "" { 118 cmd = append(cmd, fmt.Sprintf("--memory-oper=%s", s.OperationType)) 119 } 120 if s.AccessMode != "" { 121 cmd = append(cmd, fmt.Sprintf("--memory-access-mode=%s", s.AccessMode)) 122 } 123 // Sysbench ignores events for memory tests, and uses the total 124 // size parameter to determine when the test is done. We scale 125 // with this instead. 126 cmd = append(cmd, fmt.Sprintf("--memory-total-size=%dG", b.N)) 127 return cmd 128 } 129 130 // Report reports the relevant metrics for SysbenchMemory. 131 func (s *SysbenchMemory) Report(b *testing.B, output string) { 132 b.Helper() 133 result, err := s.parseOperations(output) 134 if err != nil { 135 b.Fatalf("parsing result %s failed with err: %v", output, err) 136 } 137 ReportCustomMetric(b, result, "memory_operations" /*metric name*/, "ops_per_second" /*unit*/) 138 } 139 140 var memoryOperationsRE = regexp.MustCompile(`Total\s+operations:\s+\d+\s+\((\s*\d+\.\d+\s*)\s+per\s+second\)`) 141 142 // parseOperations parses memory operations per second form sysbench memory ouput. 143 func (s *SysbenchMemory) parseOperations(data string) (float64, error) { 144 match := memoryOperationsRE.FindStringSubmatch(data) 145 if len(match) < 2 { 146 return 0.0, fmt.Errorf("couldn't find memory operations per second: %s", data) 147 } 148 return strconv.ParseFloat(match[1], 64) 149 } 150 151 // SysbenchMutex is for 'sysbench [FLAGS] mutex run' and holds Mutex specific arguments. 152 type SysbenchMutex struct { 153 SysbenchBase 154 Num int // total size of mutex array [4096]. 155 Loops int // number of loops to do outside mutex lock [10000]. 156 } 157 158 // MakeCmd makes commands for SysbenchMutex. 159 func (s *SysbenchMutex) MakeCmd(b *testing.B) []string { 160 cmd := []string{"sysbench"} 161 cmd = append(cmd, s.flags(b)...) 162 cmd = append(cmd, "mutex", "run") 163 return cmd 164 } 165 166 // flags makes flags for SysbenchMutex commands. 167 func (s *SysbenchMutex) flags(b *testing.B) []string { 168 var cmd []string 169 cmd = append(cmd, s.baseFlags(b, false /* useEvents */)...) 170 if s.Num > 0 { 171 cmd = append(cmd, fmt.Sprintf("--mutex-num=%d", s.Num)) 172 } 173 if s.Loops > 0 { 174 cmd = append(cmd, fmt.Sprintf("--mutex-loops=%d", s.Loops)) 175 } 176 // Sysbench does not respect --events for mutex tests. From [1]: 177 // "Here --time or --events are completely ignored. Sysbench always 178 // runs one event per thread." 179 // [1] https://tomfern.com/posts/sysbench-guide-1 180 cmd = append(cmd, fmt.Sprintf("--mutex-locks=%d", b.N)) 181 return cmd 182 } 183 184 // Report parses and reports relevant sysbench mutex metrics. 185 func (s *SysbenchMutex) Report(b *testing.B, output string) { 186 b.Helper() 187 188 result, err := s.parseExecutionTime(output) 189 if err != nil { 190 b.Fatalf("parsing result %s failed with err: %v", output, err) 191 } 192 ReportCustomMetric(b, result, "average_execution_time" /*metric name*/, "s" /*unit*/) 193 194 result, err = s.parseDeviation(output) 195 if err != nil { 196 b.Fatalf("parsing result %s failed with err: %v", output, err) 197 } 198 ReportCustomMetric(b, result, "stddev_execution_time" /*metric name*/, "s" /*unit*/) 199 200 result, err = s.parseLatency(output) 201 if err != nil { 202 b.Fatalf("parsing result %s failed with err: %v", output, err) 203 } 204 ReportCustomMetric(b, result/1000, "average_latency" /*metric name*/, "s" /*unit*/) 205 } 206 207 var executionTimeRE = regexp.MustCompile(`execution time \(avg/stddev\):\s*(\d*.?\d*)/(\d*.?\d*)`) 208 209 // parseExecutionTime parses threads fairness average execution time from sysbench output. 210 func (s *SysbenchMutex) parseExecutionTime(data string) (float64, error) { 211 match := executionTimeRE.FindStringSubmatch(data) 212 if len(match) < 2 { 213 return 0.0, fmt.Errorf("could not find execution time average: %s", data) 214 } 215 return strconv.ParseFloat(match[1], 64) 216 } 217 218 // parseDeviation parses threads fairness stddev time from sysbench output. 219 func (s *SysbenchMutex) parseDeviation(data string) (float64, error) { 220 match := executionTimeRE.FindStringSubmatch(data) 221 if len(match) < 3 { 222 return 0.0, fmt.Errorf("could not find execution time deviation: %s", data) 223 } 224 return strconv.ParseFloat(match[2], 64) 225 } 226 227 var averageLatencyRE = regexp.MustCompile(`avg:[^\n^\d]*(\d*\.?\d*)`) 228 229 // parseLatency parses latency from sysbench output. 230 func (s *SysbenchMutex) parseLatency(data string) (float64, error) { 231 match := averageLatencyRE.FindStringSubmatch(data) 232 if len(match) < 2 { 233 return 0.0, fmt.Errorf("could not find average latency: %s", data) 234 } 235 return strconv.ParseFloat(match[1], 64) 236 }