+176
@@ -0,0 +1,176 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package loadtest
|
||||
|
||||
// Performance benchmarks for pubsub.
|
||||
// Run with
|
||||
// go test -bench . -cpu 1
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"cloud.google.com/go/pubsub"
|
||||
gtransport "google.golang.org/api/transport/grpc"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
// These constants are designed to match the "throughput" test in
|
||||
// https://github.com/GoogleCloudPlatform/pubsub/blob/master/load-test-framework/run.py
|
||||
// and
|
||||
// https://github.com/GoogleCloudPlatform/pubsub/blob/master/load-test-framework/src/main/java/com/google/pubsub/clients/experimental/CPSPublisherTask.java
|
||||
|
||||
const (
|
||||
nMessages = 1e5
|
||||
messageSize = 10000 // size of msg data in bytes
|
||||
batchSize = 10
|
||||
batchDuration = 50 * time.Millisecond
|
||||
serverDelay = 200 * time.Millisecond
|
||||
maxOutstandingPublishes = 1600 // max_outstanding_messages in run.py
|
||||
)
|
||||
|
||||
func BenchmarkPublishThroughput(b *testing.B) {
|
||||
b.SetBytes(nMessages * messageSize)
|
||||
client := perfClient(serverDelay, 1, b)
|
||||
|
||||
lts := &PubServer{ID: "xxx"}
|
||||
lts.init(client, "t", messageSize, batchSize, batchDuration)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
runOnce(lts)
|
||||
}
|
||||
}
|
||||
|
||||
func runOnce(lts *PubServer) {
|
||||
nRequests := int64(nMessages / batchSize)
|
||||
var nPublished int64
|
||||
var wg sync.WaitGroup
|
||||
// The Java loadtest framework is rate-limited to 1 billion Execute calls a
|
||||
// second (each Execute call corresponding to a publishBatch call here),
|
||||
// but we can ignore this because of the following.
|
||||
// The framework runs 10,000 threads, each calling Execute in a loop, but
|
||||
// we can ignore this too.
|
||||
// The framework caps the number of outstanding calls to Execute at
|
||||
// maxOutstandingPublishes. That is what we simulate here.
|
||||
for i := 0; i < maxOutstandingPublishes; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for atomic.AddInt64(&nRequests, -1) >= 0 {
|
||||
latencies, err := lts.publishBatch()
|
||||
if err != nil {
|
||||
log.Fatalf("publishBatch: %v", err)
|
||||
}
|
||||
atomic.AddInt64(&nPublished, int64(len(latencies)))
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
sent := atomic.LoadInt64(&nPublished)
|
||||
if sent != nMessages {
|
||||
log.Fatalf("sent %d messages, expected %d", sent, int(nMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func perfClient(pubDelay time.Duration, nConns int, f interface {
|
||||
Fatal(...interface{})
|
||||
}) *pubsub.Client {
|
||||
ctx := context.Background()
|
||||
srv, err := newPerfServer(pubDelay)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
conn, err := gtransport.DialInsecure(ctx,
|
||||
option.WithEndpoint(srv.Addr),
|
||||
option.WithGRPCConnectionPool(nConns),
|
||||
|
||||
// TODO(grpc/grpc-go#1388) using connection pool without WithBlock
|
||||
// can cause RPCs to fail randomly. We can delete this after the issue is fixed.
|
||||
option.WithGRPCDialOption(grpc.WithBlock()))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
client, err := pubsub.NewClient(ctx, "projectID", option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
type perfServer struct {
|
||||
pb.PublisherServer
|
||||
pb.SubscriberServer
|
||||
|
||||
Addr string
|
||||
pubDelay time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
activePubs int
|
||||
maxActivePubs int
|
||||
}
|
||||
|
||||
func newPerfServer(pubDelay time.Duration) (*perfServer, error) {
|
||||
srv, err := testutil.NewServer(grpc.MaxMsgSize(pubsub.MaxPublishRequestBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
perf := &perfServer{Addr: srv.Addr, pubDelay: pubDelay}
|
||||
pb.RegisterPublisherServer(srv.Gsrv, perf)
|
||||
pb.RegisterSubscriberServer(srv.Gsrv, perf)
|
||||
srv.Start()
|
||||
return perf, nil
|
||||
}
|
||||
|
||||
var doLog = false
|
||||
|
||||
func (p *perfServer) incActivePubs(n int) (int, bool) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.activePubs += n
|
||||
newMax := false
|
||||
if p.activePubs > p.maxActivePubs {
|
||||
p.maxActivePubs = p.activePubs
|
||||
newMax = true
|
||||
}
|
||||
return p.activePubs, newMax
|
||||
}
|
||||
|
||||
func (p *perfServer) Publish(ctx context.Context, req *pb.PublishRequest) (*pb.PublishResponse, error) {
|
||||
a, newMax := p.incActivePubs(1)
|
||||
defer p.incActivePubs(-1)
|
||||
if newMax && doLog {
|
||||
log.Printf("max %d active publish calls", a)
|
||||
}
|
||||
if doLog {
|
||||
log.Printf("%p -> Publish %d", p, len(req.Messages))
|
||||
}
|
||||
res := &pb.PublishResponse{MessageIds: make([]string, len(req.Messages))}
|
||||
for i := range res.MessageIds {
|
||||
res.MessageIds[i] = "x"
|
||||
}
|
||||
time.Sleep(p.pubDelay)
|
||||
if doLog {
|
||||
log.Printf("%p <- Publish %d", p, len(req.Messages))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user