get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/benchmark_publish_test.go (about)

     1  // Copyright 2022 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package server
    15  
    16  import (
    17  	"fmt"
    18  	"math/rand"
    19  	"sync/atomic"
    20  	"testing"
    21  
    22  	"github.com/nats-io/nats.go"
    23  )
    24  
    25  func BenchmarkPublish(b *testing.B) {
    26  
    27  	const (
    28  		verbose     = false
    29  		seed        = 12345
    30  		minMessages = 10_000
    31  		subject     = "S"
    32  		queue       = "Q"
    33  	)
    34  
    35  	const (
    36  		KB = 1024
    37  		MB = KB * KB
    38  	)
    39  
    40  	type SubscriberType string
    41  	const (
    42  		Async      SubscriberType = "Async"
    43  		QueueAsync SubscriberType = "AsyncQueue"
    44  		None       SubscriberType = "None"
    45  	)
    46  
    47  	benchmarksCases := []struct {
    48  		messageSize int
    49  	}{
    50  		{0},
    51  		{1},
    52  		{32},
    53  		{128},
    54  		{512},
    55  		{4 * KB},
    56  		{32 * KB},
    57  		{128 * KB},
    58  		{512 * KB},
    59  		{1 * MB},
    60  	}
    61  
    62  	// All the cases above are run for each of the subscriber cases below
    63  	subscribersCases := []struct {
    64  		numSubs int
    65  		subType SubscriberType
    66  	}{
    67  		{0, None},
    68  		{1, Async},
    69  		{1, QueueAsync},
    70  		{10, Async},
    71  		{10, QueueAsync},
    72  	}
    73  
    74  	for _, bc := range benchmarksCases {
    75  		bcName := fmt.Sprintf(
    76  			"MsgSz=%db",
    77  			bc.messageSize,
    78  		)
    79  
    80  		b.Run(
    81  			bcName,
    82  			func(b *testing.B) {
    83  
    84  				for _, sc := range subscribersCases {
    85  
    86  					scName := fmt.Sprintf("Subs=%dx%v", sc.numSubs, sc.subType)
    87  					if sc.subType == None {
    88  						scName = fmt.Sprintf("Subs=%v", sc.subType)
    89  					}
    90  
    91  					b.Run(
    92  						scName,
    93  						func(b *testing.B) {
    94  							// Skip short runs, benchmark gets re-executed with a larger N
    95  							if b.N < minMessages {
    96  								b.ResetTimer()
    97  								return
    98  							}
    99  
   100  							if verbose {
   101  								b.Logf("Running %s/%s with %d ops", bcName, scName, b.N)
   102  							}
   103  
   104  							subErrors := uint64(0)
   105  							handleSubError := func(_ *nats.Conn, _ *nats.Subscription, _ error) {
   106  								atomic.AddUint64(&subErrors, 1)
   107  							}
   108  
   109  							// Start single server (no JS)
   110  							opts := DefaultTestOptions
   111  							opts.Port = -1
   112  							s := RunServer(&opts)
   113  							defer s.Shutdown()
   114  
   115  							// Create subscribers
   116  							for i := 0; i < sc.numSubs; i++ {
   117  								subConn, connErr := nats.Connect(s.ClientURL(), nats.ErrorHandler(handleSubError))
   118  								if connErr != nil {
   119  									b.Fatalf("Failed to connect: %v", connErr)
   120  								}
   121  								defer subConn.Close()
   122  
   123  								var sub *nats.Subscription
   124  								var subErr error
   125  
   126  								switch sc.subType {
   127  								case None:
   128  									// No subscription
   129  								case Async:
   130  									sub, subErr = subConn.Subscribe(subject, func(*nats.Msg) {})
   131  								case QueueAsync:
   132  									sub, subErr = subConn.QueueSubscribe(subject, queue, func(*nats.Msg) {})
   133  								default:
   134  									b.Fatalf("Unknow subscribers type: %v", sc.subType)
   135  								}
   136  
   137  								if subErr != nil {
   138  									b.Fatalf("Failed to subscribe: %v", subErr)
   139  								}
   140  								defer sub.Unsubscribe()
   141  								// Do not drop messages due to slow subscribers:
   142  								sub.SetPendingLimits(-1, -1)
   143  							}
   144  
   145  							// Create publisher connection
   146  							nc, err := nats.Connect(s.ClientURL())
   147  							if err != nil {
   148  								b.Fatalf("Failed to connect: %v", err)
   149  							}
   150  							defer nc.Close()
   151  
   152  							rng := rand.New(rand.NewSource(int64(seed)))
   153  							message := make([]byte, bc.messageSize)
   154  							var published, errors int
   155  
   156  							// Benchmark starts here
   157  							b.ResetTimer()
   158  
   159  							for i := 0; i < b.N; i++ {
   160  								rng.Read(message)
   161  								pubErr := nc.Publish(subject, message)
   162  								if pubErr != nil {
   163  									errors++
   164  								} else {
   165  									published++
   166  									b.SetBytes(int64(bc.messageSize))
   167  								}
   168  							}
   169  
   170  							// Benchmark ends here
   171  							b.StopTimer()
   172  
   173  							if published+errors != b.N {
   174  								b.Fatalf("Something doesn't add up: %d + %d != %d", published, errors, b.N)
   175  							} else if subErrors > 0 {
   176  								b.Fatalf("Subscribers errors: %d", subErrors)
   177  							}
   178  
   179  							b.ReportMetric(float64(errors)*100/float64(b.N), "%error")
   180  						},
   181  					)
   182  				}
   183  			},
   184  		)
   185  	}
   186  }