You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

176 lines
4.9 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package loadtest
  15. // Performance benchmarks for pubsub.
  16. // Run with
  17. // go test -bench . -cpu 1
  18. import (
  19. "context"
  20. "log"
  21. "sync"
  22. "sync/atomic"
  23. "testing"
  24. "time"
  25. "cloud.google.com/go/internal/testutil"
  26. "cloud.google.com/go/pubsub"
  27. "google.golang.org/api/option"
  28. gtransport "google.golang.org/api/transport/grpc"
  29. pb "google.golang.org/genproto/googleapis/pubsub/v1"
  30. "google.golang.org/grpc"
  31. )
  32. // These constants are designed to match the "throughput" test in
  33. // https://github.com/GoogleCloudPlatform/pubsub/blob/master/load-test-framework/run.py
  34. // and
  35. // https://github.com/GoogleCloudPlatform/pubsub/blob/master/load-test-framework/src/main/java/com/google/pubsub/clients/experimental/CPSPublisherTask.java
  36. const (
  37. nMessages = 1e5
  38. messageSize = 10000 // size of msg data in bytes
  39. batchSize = 10
  40. batchDuration = 50 * time.Millisecond
  41. serverDelay = 200 * time.Millisecond
  42. maxOutstandingPublishes = 1600 // max_outstanding_messages in run.py
  43. )
  44. func BenchmarkPublishThroughput(b *testing.B) {
  45. b.SetBytes(nMessages * messageSize)
  46. client := perfClient(serverDelay, 1, b)
  47. lts := &PubServer{ID: "xxx"}
  48. lts.init(client, "t", messageSize, batchSize, batchDuration)
  49. b.ResetTimer()
  50. for i := 0; i < b.N; i++ {
  51. runOnce(lts)
  52. }
  53. }
  54. func runOnce(lts *PubServer) {
  55. nRequests := int64(nMessages / batchSize)
  56. var nPublished int64
  57. var wg sync.WaitGroup
  58. // The Java loadtest framework is rate-limited to 1 billion Execute calls a
  59. // second (each Execute call corresponding to a publishBatch call here),
  60. // but we can ignore this because of the following.
  61. // The framework runs 10,000 threads, each calling Execute in a loop, but
  62. // we can ignore this too.
  63. // The framework caps the number of outstanding calls to Execute at
  64. // maxOutstandingPublishes. That is what we simulate here.
  65. for i := 0; i < maxOutstandingPublishes; i++ {
  66. wg.Add(1)
  67. go func() {
  68. defer wg.Done()
  69. for atomic.AddInt64(&nRequests, -1) >= 0 {
  70. latencies, err := lts.publishBatch()
  71. if err != nil {
  72. log.Fatalf("publishBatch: %v", err)
  73. }
  74. atomic.AddInt64(&nPublished, int64(len(latencies)))
  75. }
  76. }()
  77. }
  78. wg.Wait()
  79. sent := atomic.LoadInt64(&nPublished)
  80. if sent != nMessages {
  81. log.Fatalf("sent %d messages, expected %d", sent, int(nMessages))
  82. }
  83. }
  84. func perfClient(pubDelay time.Duration, nConns int, f interface {
  85. Fatal(...interface{})
  86. }) *pubsub.Client {
  87. ctx := context.Background()
  88. srv, err := newPerfServer(pubDelay)
  89. if err != nil {
  90. f.Fatal(err)
  91. }
  92. conn, err := gtransport.DialInsecure(ctx,
  93. option.WithEndpoint(srv.Addr),
  94. option.WithGRPCConnectionPool(nConns),
  95. // TODO(grpc/grpc-go#1388) using connection pool without WithBlock
  96. // can cause RPCs to fail randomly. We can delete this after the issue is fixed.
  97. option.WithGRPCDialOption(grpc.WithBlock()))
  98. if err != nil {
  99. f.Fatal(err)
  100. }
  101. client, err := pubsub.NewClient(ctx, "projectID", option.WithGRPCConn(conn))
  102. if err != nil {
  103. f.Fatal(err)
  104. }
  105. return client
  106. }
  107. type perfServer struct {
  108. pb.PublisherServer
  109. pb.SubscriberServer
  110. Addr string
  111. pubDelay time.Duration
  112. mu sync.Mutex
  113. activePubs int
  114. maxActivePubs int
  115. }
  116. func newPerfServer(pubDelay time.Duration) (*perfServer, error) {
  117. srv, err := testutil.NewServer(grpc.MaxMsgSize(pubsub.MaxPublishRequestBytes))
  118. if err != nil {
  119. return nil, err
  120. }
  121. perf := &perfServer{Addr: srv.Addr, pubDelay: pubDelay}
  122. pb.RegisterPublisherServer(srv.Gsrv, perf)
  123. pb.RegisterSubscriberServer(srv.Gsrv, perf)
  124. srv.Start()
  125. return perf, nil
  126. }
  127. var doLog = false
  128. func (p *perfServer) incActivePubs(n int) (int, bool) {
  129. p.mu.Lock()
  130. defer p.mu.Unlock()
  131. p.activePubs += n
  132. newMax := false
  133. if p.activePubs > p.maxActivePubs {
  134. p.maxActivePubs = p.activePubs
  135. newMax = true
  136. }
  137. return p.activePubs, newMax
  138. }
  139. func (p *perfServer) Publish(ctx context.Context, req *pb.PublishRequest) (*pb.PublishResponse, error) {
  140. a, newMax := p.incActivePubs(1)
  141. defer p.incActivePubs(-1)
  142. if newMax && doLog {
  143. log.Printf("max %d active publish calls", a)
  144. }
  145. if doLog {
  146. log.Printf("%p -> Publish %d", p, len(req.Messages))
  147. }
  148. res := &pb.PublishResponse{MessageIds: make([]string, len(req.Messages))}
  149. for i := range res.MessageIds {
  150. res.MessageIds[i] = "x"
  151. }
  152. time.Sleep(p.pubDelay)
  153. if doLog {
  154. log.Printf("%p <- Publish %d", p, len(req.Messages))
  155. }
  156. return res, nil
  157. }