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  }