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.
 
 
 

216 lines
5.5 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 implements load testing for pubsub,
  15. // following the interface defined in https://github.com/GoogleCloudPlatform/pubsub/tree/master/load-test-framework/ .
  16. //
  17. // This package is experimental.
  18. package loadtest
  19. import (
  20. "bytes"
  21. "errors"
  22. "log"
  23. "runtime"
  24. "strconv"
  25. "sync"
  26. "sync/atomic"
  27. "time"
  28. "golang.org/x/net/context"
  29. "golang.org/x/time/rate"
  30. "github.com/golang/protobuf/ptypes"
  31. "cloud.google.com/go/pubsub"
  32. pb "cloud.google.com/go/pubsub/loadtest/pb"
  33. )
  34. type pubServerConfig struct {
  35. topic *pubsub.Topic
  36. msgData []byte
  37. batchSize int32
  38. }
  39. type PubServer struct {
  40. ID string
  41. cfg atomic.Value
  42. seqNum int32
  43. }
  44. func (l *PubServer) Start(ctx context.Context, req *pb.StartRequest) (*pb.StartResponse, error) {
  45. log.Println("received start")
  46. c, err := pubsub.NewClient(ctx, req.Project)
  47. if err != nil {
  48. return nil, err
  49. }
  50. dur, err := ptypes.Duration(req.PublishBatchDuration)
  51. if err != nil {
  52. return nil, err
  53. }
  54. l.init(c, req.Topic, req.MessageSize, req.PublishBatchSize, dur)
  55. log.Println("started")
  56. return &pb.StartResponse{}, nil
  57. }
  58. func (l *PubServer) init(c *pubsub.Client, topicName string, msgSize, batchSize int32, batchDur time.Duration) {
  59. topic := c.Topic(topicName)
  60. topic.PublishSettings = pubsub.PublishSettings{
  61. DelayThreshold: batchDur,
  62. CountThreshold: 950,
  63. ByteThreshold: 9500000,
  64. }
  65. l.cfg.Store(pubServerConfig{
  66. topic: topic,
  67. msgData: bytes.Repeat([]byte{'A'}, int(msgSize)),
  68. batchSize: batchSize,
  69. })
  70. }
  71. func (l *PubServer) Execute(ctx context.Context, _ *pb.ExecuteRequest) (*pb.ExecuteResponse, error) {
  72. latencies, err := l.publishBatch()
  73. if err != nil {
  74. log.Printf("error: %v", err)
  75. return nil, err
  76. }
  77. return &pb.ExecuteResponse{Latencies: latencies}, nil
  78. }
  79. func (l *PubServer) publishBatch() ([]int64, error) {
  80. var cfg pubServerConfig
  81. if c, ok := l.cfg.Load().(pubServerConfig); ok {
  82. cfg = c
  83. } else {
  84. return nil, errors.New("config not loaded")
  85. }
  86. start := time.Now()
  87. latencies := make([]int64, cfg.batchSize)
  88. startStr := strconv.FormatInt(start.UnixNano()/1e6, 10)
  89. seqNum := atomic.AddInt32(&l.seqNum, cfg.batchSize) - cfg.batchSize
  90. rs := make([]*pubsub.PublishResult, cfg.batchSize)
  91. for i := int32(0); i < cfg.batchSize; i++ {
  92. rs[i] = cfg.topic.Publish(context.TODO(), &pubsub.Message{
  93. Data: cfg.msgData,
  94. Attributes: map[string]string{
  95. "sendTime": startStr,
  96. "clientId": l.ID,
  97. "sequenceNumber": strconv.Itoa(int(seqNum + i)),
  98. },
  99. })
  100. }
  101. for i, r := range rs {
  102. _, err := r.Get(context.Background())
  103. if err != nil {
  104. return nil, err
  105. }
  106. // TODO(jba,pongad): fix latencies
  107. // Later values will be skewed by earlier ones, since we wait for the
  108. // results in order. (On the other hand, it may not matter much, since
  109. // messages are added to bundles in order and bundles get sent more or
  110. // less in order.) If we want more accurate values, we can either start
  111. // a goroutine for each result (similar to the original code using a
  112. // callback), or call reflect.Select with the Ready channels of the
  113. // results.
  114. latencies[i] = time.Since(start).Nanoseconds() / 1e6
  115. }
  116. return latencies, nil
  117. }
  118. type SubServer struct {
  119. lim *rate.Limiter
  120. mu sync.Mutex
  121. idents []*pb.MessageIdentifier
  122. latencies []int64
  123. }
  124. func (s *SubServer) Start(ctx context.Context, req *pb.StartRequest) (*pb.StartResponse, error) {
  125. log.Println("received start")
  126. s.lim = rate.NewLimiter(rate.Every(time.Second), 1)
  127. c, err := pubsub.NewClient(ctx, req.Project)
  128. if err != nil {
  129. return nil, err
  130. }
  131. // Load test API doesn't define any way to stop right now.
  132. go func() {
  133. sub := c.Subscription(req.GetPubsubOptions().Subscription)
  134. sub.ReceiveSettings.NumGoroutines = 10 * runtime.GOMAXPROCS(0)
  135. err := sub.Receive(context.Background(), s.callback)
  136. log.Fatal(err)
  137. }()
  138. log.Println("started")
  139. return &pb.StartResponse{}, nil
  140. }
  141. func (s *SubServer) callback(_ context.Context, m *pubsub.Message) {
  142. id, err := strconv.ParseInt(m.Attributes["clientId"], 10, 64)
  143. if err != nil {
  144. log.Println(err)
  145. m.Nack()
  146. return
  147. }
  148. seqNum, err := strconv.ParseInt(m.Attributes["sequenceNumber"], 10, 32)
  149. if err != nil {
  150. log.Println(err)
  151. m.Nack()
  152. return
  153. }
  154. sendTimeMillis, err := strconv.ParseInt(m.Attributes["sendTime"], 10, 64)
  155. if err != nil {
  156. log.Println(err)
  157. m.Nack()
  158. return
  159. }
  160. latency := time.Now().UnixNano()/1e6 - sendTimeMillis
  161. ident := &pb.MessageIdentifier{
  162. PublisherClientId: id,
  163. SequenceNumber: int32(seqNum),
  164. }
  165. s.mu.Lock()
  166. s.idents = append(s.idents, ident)
  167. s.latencies = append(s.latencies, latency)
  168. s.mu.Unlock()
  169. m.Ack()
  170. }
  171. func (s *SubServer) Execute(ctx context.Context, _ *pb.ExecuteRequest) (*pb.ExecuteResponse, error) {
  172. // Throttle so the load tester doesn't spam us and consume all our CPU.
  173. if err := s.lim.Wait(ctx); err != nil {
  174. return nil, err
  175. }
  176. s.mu.Lock()
  177. idents := s.idents
  178. s.idents = nil
  179. latencies := s.latencies
  180. s.latencies = nil
  181. s.mu.Unlock()
  182. return &pb.ExecuteResponse{
  183. Latencies: latencies,
  184. ReceivedMessages: idents,
  185. }, nil
  186. }