github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/benchmark_test.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package grid 19 20 import ( 21 "context" 22 "fmt" 23 "math/rand" 24 "runtime" 25 "strconv" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 "github.com/minio/minio/internal/logger/target/testlogger" 31 ) 32 33 func BenchmarkRequests(b *testing.B) { 34 for n := 2; n <= 32; n *= 2 { 35 b.Run("servers="+strconv.Itoa(n), func(b *testing.B) { 36 benchmarkGridRequests(b, n) 37 }) 38 } 39 } 40 41 func benchmarkGridRequests(b *testing.B, n int) { 42 defer testlogger.T.SetErrorTB(b)() 43 errFatal := func(err error) { 44 b.Helper() 45 if err != nil { 46 b.Fatal(err) 47 } 48 } 49 rpc := NewSingleHandler[*testRequest, *testResponse](handlerTest2, newTestRequest, newTestResponse) 50 grid, err := SetupTestGrid(n) 51 errFatal(err) 52 b.Cleanup(grid.Cleanup) 53 // Create n managers. 54 for _, remote := range grid.Managers { 55 // Register a single handler which echos the payload. 56 errFatal(remote.RegisterSingleHandler(handlerTest, func(payload []byte) ([]byte, *RemoteErr) { 57 defer PutByteBuffer(payload) 58 return append(GetByteBuffer()[:0], payload...), nil 59 })) 60 errFatal(rpc.Register(remote, func(req *testRequest) (resp *testResponse, err *RemoteErr) { 61 return &testResponse{ 62 OrgNum: req.Num, 63 OrgString: req.String, 64 Embedded: *req, 65 }, nil 66 })) 67 errFatal(err) 68 } 69 const payloadSize = 512 70 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 71 payload := make([]byte, payloadSize) 72 _, err = rng.Read(payload) 73 errFatal(err) 74 75 // Wait for all to connect 76 // Parallel writes per server. 77 b.Run("bytes", func(b *testing.B) { 78 for par := 1; par <= 32; par *= 2 { 79 b.Run("par="+strconv.Itoa(par*runtime.GOMAXPROCS(0)), func(b *testing.B) { 80 defer timeout(60 * time.Second)() 81 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 82 defer cancel() 83 b.ReportAllocs() 84 b.SetBytes(int64(len(payload) * 2)) 85 b.ResetTimer() 86 t := time.Now() 87 var ops int64 88 var lat int64 89 b.SetParallelism(par) 90 b.RunParallel(func(pb *testing.PB) { 91 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 92 n := 0 93 var latency int64 94 managers := grid.Managers 95 hosts := grid.Hosts 96 for pb.Next() { 97 // Pick a random manager. 98 src, dst := rng.Intn(len(managers)), rng.Intn(len(managers)) 99 if src == dst { 100 dst = (dst + 1) % len(managers) 101 } 102 local := managers[src] 103 conn := local.Connection(hosts[dst]) 104 if conn == nil { 105 b.Fatal("No connection") 106 } 107 // Send the payload. 108 t := time.Now() 109 resp, err := conn.Request(ctx, handlerTest, payload) 110 latency += time.Since(t).Nanoseconds() 111 if err != nil { 112 if debugReqs { 113 fmt.Println(err.Error()) 114 } 115 b.Fatal(err.Error()) 116 } 117 PutByteBuffer(resp) 118 n++ 119 } 120 atomic.AddInt64(&ops, int64(n)) 121 atomic.AddInt64(&lat, latency) 122 }) 123 spent := time.Since(t) 124 if spent > 0 && n > 0 { 125 // Since we are benchmarking n parallel servers we need to multiply by n. 126 // This will give an estimate of the total ops/s. 127 latency := float64(atomic.LoadInt64(&lat)) / float64(time.Millisecond) 128 b.ReportMetric(float64(n)*float64(ops)/spent.Seconds(), "vops/s") 129 b.ReportMetric(latency/float64(ops), "ms/op") 130 } 131 }) 132 } 133 }) 134 b.Run("rpc", func(b *testing.B) { 135 for par := 1; par <= 32; par *= 2 { 136 b.Run("par="+strconv.Itoa(par*runtime.GOMAXPROCS(0)), func(b *testing.B) { 137 defer timeout(60 * time.Second)() 138 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 139 defer cancel() 140 b.ReportAllocs() 141 b.ResetTimer() 142 t := time.Now() 143 var ops int64 144 var lat int64 145 b.SetParallelism(par) 146 b.RunParallel(func(pb *testing.PB) { 147 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 148 n := 0 149 var latency int64 150 managers := grid.Managers 151 hosts := grid.Hosts 152 req := testRequest{ 153 Num: rng.Int(), 154 String: "hello", 155 } 156 for pb.Next() { 157 // Pick a random manager. 158 src, dst := rng.Intn(len(managers)), rng.Intn(len(managers)) 159 if src == dst { 160 dst = (dst + 1) % len(managers) 161 } 162 local := managers[src] 163 conn := local.Connection(hosts[dst]) 164 if conn == nil { 165 b.Fatal("No connection") 166 } 167 // Send the payload. 168 t := time.Now() 169 resp, err := rpc.Call(ctx, conn, &req) 170 latency += time.Since(t).Nanoseconds() 171 if err != nil { 172 if debugReqs { 173 fmt.Println(err.Error()) 174 } 175 b.Fatal(err.Error()) 176 } 177 rpc.PutResponse(resp) 178 n++ 179 } 180 atomic.AddInt64(&ops, int64(n)) 181 atomic.AddInt64(&lat, latency) 182 }) 183 spent := time.Since(t) 184 if spent > 0 && n > 0 { 185 // Since we are benchmarking n parallel servers we need to multiply by n. 186 // This will give an estimate of the total ops/s. 187 latency := float64(atomic.LoadInt64(&lat)) / float64(time.Millisecond) 188 b.ReportMetric(float64(n)*float64(ops)/spent.Seconds(), "vops/s") 189 b.ReportMetric(latency/float64(ops), "ms/op") 190 } 191 }) 192 } 193 }) 194 } 195 196 func BenchmarkStream(b *testing.B) { 197 tests := []struct { 198 name string 199 fn func(b *testing.B, n int) 200 }{ 201 {name: "request", fn: benchmarkGridStreamReqOnly}, 202 {name: "responses", fn: benchmarkGridStreamRespOnly}, 203 } 204 for _, test := range tests { 205 b.Run(test.name, func(b *testing.B) { 206 for n := 2; n <= 32; n *= 2 { 207 b.Run("servers="+strconv.Itoa(n), func(b *testing.B) { 208 test.fn(b, n) 209 }) 210 } 211 }) 212 } 213 } 214 215 func benchmarkGridStreamRespOnly(b *testing.B, n int) { 216 defer testlogger.T.SetErrorTB(b)() 217 errFatal := func(err error) { 218 b.Helper() 219 if err != nil { 220 b.Fatal(err) 221 } 222 } 223 grid, err := SetupTestGrid(n) 224 errFatal(err) 225 b.Cleanup(grid.Cleanup) 226 const responses = 10 227 // Create n managers. 228 for _, remote := range grid.Managers { 229 // Register a single handler which echos the payload. 230 errFatal(remote.RegisterStreamingHandler(handlerTest, StreamHandler{ 231 // Send 10x response. 232 Handle: func(ctx context.Context, payload []byte, _ <-chan []byte, out chan<- []byte) *RemoteErr { 233 for i := 0; i < responses; i++ { 234 toSend := GetByteBuffer()[:0] 235 toSend = append(toSend, byte(i)) 236 toSend = append(toSend, payload...) 237 select { 238 case <-ctx.Done(): 239 return nil 240 case out <- toSend: 241 } 242 } 243 return nil 244 }, 245 246 Subroute: "some-subroute", 247 OutCapacity: 1, // Only one message buffered. 248 InCapacity: 0, 249 })) 250 errFatal(err) 251 } 252 const payloadSize = 512 253 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 254 payload := make([]byte, payloadSize) 255 _, err = rng.Read(payload) 256 errFatal(err) 257 258 // Wait for all to connect 259 // Parallel writes per server. 260 for par := 1; par <= 32; par *= 2 { 261 b.Run("par="+strconv.Itoa(par*runtime.GOMAXPROCS(0)), func(b *testing.B) { 262 defer timeout(30 * time.Second)() 263 b.ReportAllocs() 264 b.SetBytes(int64(len(payload) * (responses + 1))) 265 b.ResetTimer() 266 t := time.Now() 267 var ops int64 268 var lat int64 269 b.SetParallelism(par) 270 b.RunParallel(func(pb *testing.PB) { 271 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 272 n := 0 273 var latency int64 274 managers := grid.Managers 275 hosts := grid.Hosts 276 for pb.Next() { 277 // Pick a random manager. 278 src, dst := rng.Intn(len(managers)), rng.Intn(len(managers)) 279 if src == dst { 280 dst = (dst + 1) % len(managers) 281 } 282 local := managers[src] 283 conn := local.Connection(hosts[dst]).Subroute("some-subroute") 284 if conn == nil { 285 b.Fatal("No connection") 286 } 287 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 288 // Send the payload. 289 t := time.Now() 290 st, err := conn.NewStream(ctx, handlerTest, payload) 291 if err != nil { 292 if debugReqs { 293 fmt.Println(err.Error()) 294 } 295 b.Fatal(err.Error()) 296 } 297 got := 0 298 err = st.Results(func(b []byte) error { 299 got++ 300 PutByteBuffer(b) 301 return nil 302 }) 303 if err != nil { 304 if debugReqs { 305 fmt.Println(err.Error()) 306 } 307 b.Fatal(err.Error()) 308 } 309 latency += time.Since(t).Nanoseconds() 310 cancel() 311 n += got 312 } 313 atomic.AddInt64(&ops, int64(n)) 314 atomic.AddInt64(&lat, latency) 315 }) 316 spent := time.Since(t) 317 if spent > 0 && n > 0 { 318 // Since we are benchmarking n parallel servers we need to multiply by n. 319 // This will give an estimate of the total ops/s. 320 latency := float64(atomic.LoadInt64(&lat)) / float64(time.Millisecond) 321 b.ReportMetric(float64(n)*float64(ops)/spent.Seconds(), "vops/s") 322 b.ReportMetric(latency/float64(ops), "ms/op") 323 } 324 }) 325 } 326 } 327 328 func benchmarkGridStreamReqOnly(b *testing.B, n int) { 329 defer testlogger.T.SetErrorTB(b)() 330 errFatal := func(err error) { 331 b.Helper() 332 if err != nil { 333 b.Fatal(err) 334 } 335 } 336 grid, err := SetupTestGrid(n) 337 errFatal(err) 338 b.Cleanup(grid.Cleanup) 339 const requests = 10 340 // Create n managers. 341 for _, remote := range grid.Managers { 342 // Register a single handler which echos the payload. 343 errFatal(remote.RegisterStreamingHandler(handlerTest, StreamHandler{ 344 // Send 10x requests. 345 Handle: func(ctx context.Context, payload []byte, in <-chan []byte, out chan<- []byte) *RemoteErr { 346 got := 0 347 for b := range in { 348 PutByteBuffer(b) 349 got++ 350 } 351 if got != requests { 352 return NewRemoteErrf("wrong number of requests. want %d, got %d", requests, got) 353 } 354 return nil 355 }, 356 357 Subroute: "some-subroute", 358 OutCapacity: 1, 359 InCapacity: 1, // Only one message buffered. 360 })) 361 errFatal(err) 362 } 363 const payloadSize = 512 364 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 365 payload := make([]byte, payloadSize) 366 _, err = rng.Read(payload) 367 errFatal(err) 368 369 // Wait for all to connect 370 // Parallel writes per server. 371 for par := 1; par <= 32; par *= 2 { 372 b.Run("par="+strconv.Itoa(par*runtime.GOMAXPROCS(0)), func(b *testing.B) { 373 defer timeout(30 * time.Second)() 374 b.ReportAllocs() 375 b.SetBytes(int64(len(payload) * (requests + 1))) 376 b.ResetTimer() 377 t := time.Now() 378 var ops int64 379 var lat int64 380 b.SetParallelism(par) 381 b.RunParallel(func(pb *testing.PB) { 382 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 383 n := 0 384 var latency int64 385 managers := grid.Managers 386 hosts := grid.Hosts 387 for pb.Next() { 388 // Pick a random manager. 389 src, dst := rng.Intn(len(managers)), rng.Intn(len(managers)) 390 if src == dst { 391 dst = (dst + 1) % len(managers) 392 } 393 local := managers[src] 394 conn := local.Connection(hosts[dst]).Subroute("some-subroute") 395 if conn == nil { 396 b.Fatal("No connection") 397 } 398 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 399 // Send the payload. 400 t := time.Now() 401 st, err := conn.NewStream(ctx, handlerTest, payload) 402 if err != nil { 403 if debugReqs { 404 fmt.Println(err.Error()) 405 } 406 b.Fatal(err.Error()) 407 } 408 got := 0 409 for i := 0; i < requests; i++ { 410 got++ 411 st.Requests <- append(GetByteBuffer()[:0], payload...) 412 } 413 close(st.Requests) 414 err = st.Results(func(b []byte) error { 415 return nil 416 }) 417 if err != nil { 418 if debugReqs { 419 fmt.Println(err.Error()) 420 } 421 b.Fatal(err.Error()) 422 } 423 latency += time.Since(t).Nanoseconds() 424 cancel() 425 n += got 426 } 427 atomic.AddInt64(&ops, int64(n)) 428 atomic.AddInt64(&lat, latency) 429 }) 430 spent := time.Since(t) 431 if spent > 0 && n > 0 { 432 // Since we are benchmarking n parallel servers we need to multiply by n. 433 // This will give an estimate of the total ops/s. 434 latency := float64(atomic.LoadInt64(&lat)) / float64(time.Millisecond) 435 b.ReportMetric(float64(n)*float64(ops)/spent.Seconds(), "vops/s") 436 b.ReportMetric(latency/float64(ops), "ms/op") 437 } 438 }) 439 } 440 }