Skip to content

Commit

Permalink
Improving benchmark tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thrawn01 committed May 20, 2024
1 parent 97602ef commit bacf10c
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 22 deletions.
66 changes: 51 additions & 15 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package gubernator_test
import (
"context"
"fmt"
"math/rand"
"runtime"
"testing"

Expand Down Expand Up @@ -59,7 +60,7 @@ func BenchmarkServer(b *testing.B) {
createdAt := epochMillis(clock.Now())
d := cluster.GetRandomDaemon(cluster.DataCenterNone)

b.Run("GetPeerRateLimit", func(b *testing.B) {
b.Run("Forward", func(b *testing.B) {
client := d.MustClient().(guber.PeerClient)
b.ResetTimer()

Expand All @@ -84,9 +85,9 @@ func BenchmarkServer(b *testing.B) {
}
})

b.Run("GetRateLimits batching", func(b *testing.B) {
b.Run("CheckRateLimits batching", func(b *testing.B) {
client := cluster.GetRandomDaemon(cluster.DataCenterNone).MustClient()
require.NoError(b, err, "Error in guber.DialV1Server")
require.NoError(b, err)
b.ResetTimer()

for n := 0; n < b.N; n++ {
Expand All @@ -108,9 +109,9 @@ func BenchmarkServer(b *testing.B) {
}
})

b.Run("GetRateLimits global", func(b *testing.B) {
b.Run("CheckRateLimits global", func(b *testing.B) {
client := cluster.GetRandomDaemon(cluster.DataCenterNone).MustClient()
require.NoError(b, err, "Error in guber.DialV1Server")
require.NoError(b, err)
b.ResetTimer()

for n := 0; n < b.N; n++ {
Expand All @@ -128,14 +129,14 @@ func BenchmarkServer(b *testing.B) {
},
}, &resp)
if err != nil {
b.Errorf("Error in client.GetRateLimits: %s", err)
b.Errorf("Error in client.CheckRateLimits: %s", err)
}
}
})

b.Run("HealthCheck", func(b *testing.B) {
client := cluster.GetRandomDaemon(cluster.DataCenterNone).MustClient()
require.NoError(b, err, "Error in guber.DialV1Server")
require.NoError(b, err)
b.ResetTimer()

for n := 0; n < b.N; n++ {
Expand All @@ -146,8 +147,7 @@ func BenchmarkServer(b *testing.B) {
}
})

b.Run("Thundering herd", func(b *testing.B) {
require.NoError(b, err, "Error in guber.DialV1Server")
b.Run("Concurrency CheckRateLimits", func(b *testing.B) {
var clients []guber.Client

// Create a client for each CPU on the system. This should allow us to simulate the
Expand All @@ -157,23 +157,28 @@ func BenchmarkServer(b *testing.B) {
require.NoError(b, err)
clients = append(clients, client)
}
b.ResetTimer()
mask := len(clients) - 1

keys := GenerateRandomKeys(8_000)
keyMask := len(keys) - 1
clientMask := len(clients) - 1
var idx int

b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
client := clients[idx&mask]
client := clients[idx&clientMask]
idx++

for pb.Next() {
keyIdx := int(rand.Uint32() & uint32(clientMask))
var resp guber.CheckRateLimitsResponse
err = client.CheckRateLimits(ctx, &guber.CheckRateLimitsRequest{
Requests: []*guber.RateLimitRequest{
{
Name: b.Name(),
UniqueKey: guber.RandomString(10),
Limit: 10,
Duration: guber.Second * 5,
UniqueKey: keys[keyIdx&keyMask],
Behavior: guber.Behavior_GLOBAL,
Duration: guber.Minute,
Limit: 100,
Hits: 1,
},
},
Expand All @@ -183,5 +188,36 @@ func BenchmarkServer(b *testing.B) {
}
}
})

for _, client := range clients {
_ = client.Close(context.Background())
}
})

b.Run("Concurrency HealthCheck", func(b *testing.B) {
var clients []guber.Client

// Create a client for each CPU on the system. This should allow us to simulate the
// maximum contention possible for this system.
for i := 0; i < runtime.NumCPU(); i++ {
client, err := guber.NewClient(guber.WithNoTLS(d.Listener.Addr().String()))
require.NoError(b, err)
clients = append(clients, client)
}
mask := len(clients) - 1
var idx int

b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
client := clients[idx&mask]
idx++

for pb.Next() {
var resp guber.HealthCheckResponse
if err := client.HealthCheck(ctx, &resp); err != nil {
b.Errorf("Error in client.HealthCheck: %s", err)
}
}
})
})
}
27 changes: 22 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/mailgun/holster/v4/setter"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/propagation"
"golang.org/x/net/http2"
"google.golang.org/protobuf/proto"
)

Expand Down Expand Up @@ -67,7 +66,14 @@ type client struct {

// NewClient creates a new instance of the Gubernator user client
func NewClient(opts ClientOptions) (Client, error) {
setter.SetDefault(&opts.Client, &http.Client{})
setter.SetDefault(&opts.Client, &http.Client{
Transport: &http.Transport{
MaxConnsPerHost: 2_000,
MaxIdleConns: 2_000,
MaxIdleConnsPerHost: 2_000,
IdleConnTimeout: 60 * time.Second,
},
})

if len(opts.Endpoint) == 0 {
return nil, errors.New("opts.Endpoint is empty; must provide an address")
Expand Down Expand Up @@ -163,7 +169,14 @@ func (c *client) Close(_ context.Context) error {
func WithNoTLS(address string) ClientOptions {
return ClientOptions{
Endpoint: fmt.Sprintf("http://%s", address),
Client: &http.Client{},
Client: &http.Client{
Transport: &http.Transport{
MaxConnsPerHost: 2_000,
MaxIdleConns: 2_000,
MaxIdleConnsPerHost: 2_000,
IdleConnTimeout: 60 * time.Second,
},
},
}
}

Expand All @@ -172,8 +185,12 @@ func WithTLS(tls *tls.Config, address string) ClientOptions {
return ClientOptions{
Endpoint: fmt.Sprintf("https://%s", address),
Client: &http.Client{
Transport: &http2.Transport{
TLSClientConfig: tls,
Transport: &http.Transport{
TLSClientConfig: tls,
MaxConnsPerHost: 2_000,
MaxIdleConns: 2_000,
MaxIdleConnsPerHost: 2_000,
IdleConnTimeout: 60 * time.Second,
},
},
}
Expand Down
2 changes: 2 additions & 0 deletions functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,8 @@ func TestGlobalBehavior(t *testing.T) {
// Assert PeerCheckRateLimits endpoint called on owner
// for each non-owner that received hits.
// Used by global hits update.
// TODO(thrawn01): It is more important to verify the counts exist on each peer instead of how they got there. As the method of
// how they got there is an implementation detail and can/will change. Also, this test flaps occasionally.
gprlCounters2 := getPeerCounters(t, cluster.GetDaemons(), "gubernator_http_handler_duration_count{path=\"/v1/peer.forward\"}")
for _, peer := range cluster.GetDaemons() {
expected := gprlCounters[peer.InstanceID]
Expand Down
3 changes: 1 addition & 2 deletions tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)

func spawnDaemon(t *testing.T, conf gubernator.DaemonConfig) *gubernator.Daemon {
Expand Down Expand Up @@ -268,7 +267,7 @@ func TestTLSClusterWithClientAuthentication(t *testing.T) {

config := d2.Config()
client := &http.Client{
Transport: &http2.Transport{
Transport: &http.Transport{
TLSClientConfig: config.ClientTLS(),
},
}
Expand Down

0 comments on commit bacf10c

Please sign in to comment.