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.
 
 
 

206 lines
5.6 KiB

  1. /*
  2. Copyright 2015 Google LLC
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. /*
  14. Loadtest does some load testing through the Go client library for Cloud Bigtable.
  15. */
  16. package main
  17. import (
  18. "bytes"
  19. "context"
  20. "flag"
  21. "fmt"
  22. "log"
  23. "math/rand"
  24. "os"
  25. "os/signal"
  26. "sync"
  27. "sync/atomic"
  28. "time"
  29. "cloud.google.com/go/bigtable"
  30. "cloud.google.com/go/bigtable/internal/cbtconfig"
  31. "cloud.google.com/go/bigtable/internal/stat"
  32. "google.golang.org/api/option"
  33. "google.golang.org/grpc"
  34. )
  35. var (
  36. runFor = flag.Duration("run_for", 5*time.Second,
  37. "how long to run the load test for; 0 to run forever until SIGTERM")
  38. scratchTable = flag.String("scratch_table", "loadtest-scratch", "name of table to use; should not already exist")
  39. csvOutput = flag.String("csv_output", "",
  40. "output path for statistics in .csv format. If this file already exists it will be overwritten.")
  41. poolSize = flag.Int("pool_size", 1, "size of the gRPC connection pool to use for the data client")
  42. reqCount = flag.Int("req_count", 100, "number of concurrent requests")
  43. config *cbtconfig.Config
  44. client *bigtable.Client
  45. adminClient *bigtable.AdminClient
  46. )
  47. func main() {
  48. var err error
  49. config, err = cbtconfig.Load()
  50. if err != nil {
  51. log.Fatal(err)
  52. }
  53. config.RegisterFlags()
  54. flag.Parse()
  55. if err := config.CheckFlags(cbtconfig.ProjectAndInstanceRequired); err != nil {
  56. log.Fatal(err)
  57. }
  58. if config.Creds != "" {
  59. os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", config.Creds)
  60. }
  61. if flag.NArg() != 0 {
  62. flag.Usage()
  63. os.Exit(1)
  64. }
  65. var options []option.ClientOption
  66. if *poolSize > 1 {
  67. options = append(options,
  68. option.WithGRPCConnectionPool(*poolSize),
  69. // TODO(grpc/grpc-go#1388) using connection pool without WithBlock
  70. // can cause RPCs to fail randomly. We can delete this after the issue is fixed.
  71. option.WithGRPCDialOption(grpc.WithBlock()))
  72. }
  73. var csvFile *os.File
  74. if *csvOutput != "" {
  75. csvFile, err = os.Create(*csvOutput)
  76. if err != nil {
  77. log.Fatalf("creating csv output file: %v", err)
  78. }
  79. defer csvFile.Close()
  80. log.Printf("Writing statistics to %q ...", *csvOutput)
  81. }
  82. log.Printf("Dialing connections...")
  83. client, err = bigtable.NewClient(context.Background(), config.Project, config.Instance, options...)
  84. if err != nil {
  85. log.Fatalf("Making bigtable.Client: %v", err)
  86. }
  87. defer client.Close()
  88. adminClient, err = bigtable.NewAdminClient(context.Background(), config.Project, config.Instance)
  89. if err != nil {
  90. log.Fatalf("Making bigtable.AdminClient: %v", err)
  91. }
  92. defer adminClient.Close()
  93. // Create a scratch table.
  94. log.Printf("Setting up scratch table...")
  95. tblConf := bigtable.TableConf{
  96. TableID: *scratchTable,
  97. Families: map[string]bigtable.GCPolicy{"f": bigtable.MaxVersionsPolicy(1)},
  98. }
  99. if err := adminClient.CreateTableFromConf(context.Background(), &tblConf); err != nil {
  100. log.Fatalf("Making scratch table %q: %v", *scratchTable, err)
  101. }
  102. // Upon a successful run, delete the table. Don't bother checking for errors.
  103. defer adminClient.DeleteTable(context.Background(), *scratchTable)
  104. // Also delete the table on SIGTERM.
  105. c := make(chan os.Signal, 1)
  106. signal.Notify(c, os.Interrupt)
  107. go func() {
  108. s := <-c
  109. log.Printf("Caught %v, cleaning scratch table.", s)
  110. _ = adminClient.DeleteTable(context.Background(), *scratchTable)
  111. os.Exit(1)
  112. }()
  113. log.Printf("Starting load test... (run for %v)", *runFor)
  114. tbl := client.Open(*scratchTable)
  115. sem := make(chan int, *reqCount) // limit the number of requests happening at once
  116. var reads, writes stats
  117. stopTime := time.Now().Add(*runFor)
  118. var wg sync.WaitGroup
  119. for time.Now().Before(stopTime) || *runFor == 0 {
  120. sem <- 1
  121. wg.Add(1)
  122. go func() {
  123. defer wg.Done()
  124. defer func() { <-sem }()
  125. ok := true
  126. opStart := time.Now()
  127. var stats *stats
  128. defer func() {
  129. stats.Record(ok, time.Since(opStart))
  130. }()
  131. row := fmt.Sprintf("row%d", rand.Intn(100)) // operate on 1 of 100 rows
  132. switch rand.Intn(10) {
  133. default:
  134. // read
  135. stats = &reads
  136. _, err := tbl.ReadRow(context.Background(), row, bigtable.RowFilter(bigtable.LatestNFilter(1)))
  137. if err != nil {
  138. log.Printf("Error doing read: %v", err)
  139. ok = false
  140. }
  141. case 0, 1, 2, 3, 4:
  142. // write
  143. stats = &writes
  144. mut := bigtable.NewMutation()
  145. mut.Set("f", "col", bigtable.Now(), bytes.Repeat([]byte("0"), 1<<10)) // 1 KB write
  146. if err := tbl.Apply(context.Background(), row, mut); err != nil {
  147. log.Printf("Error doing mutation: %v", err)
  148. ok = false
  149. }
  150. }
  151. }()
  152. }
  153. wg.Wait()
  154. readsAgg := stat.NewAggregate("reads", reads.ds, reads.tries-reads.ok)
  155. writesAgg := stat.NewAggregate("writes", writes.ds, writes.tries-writes.ok)
  156. log.Printf("Reads (%d ok / %d tries):\n%v", reads.ok, reads.tries, readsAgg)
  157. log.Printf("Writes (%d ok / %d tries):\n%v", writes.ok, writes.tries, writesAgg)
  158. if csvFile != nil {
  159. stat.WriteCSV([]*stat.Aggregate{readsAgg, writesAgg}, csvFile)
  160. }
  161. }
  162. var allStats int64 // atomic
  163. type stats struct {
  164. mu sync.Mutex
  165. tries, ok int
  166. ds []time.Duration
  167. }
  168. func (s *stats) Record(ok bool, d time.Duration) {
  169. s.mu.Lock()
  170. s.tries++
  171. if ok {
  172. s.ok++
  173. }
  174. s.ds = append(s.ds, d)
  175. s.mu.Unlock()
  176. if n := atomic.AddInt64(&allStats, 1); n%1000 == 0 {
  177. log.Printf("Progress: done %d ops", n)
  178. }
  179. }