github.com/m3db/m3@v1.5.0/src/cmd/tools/carbon_load/main/main.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // carbon_load is a tool for load testing carbon ingestion. 22 package main 23 24 import ( 25 "flag" 26 "fmt" 27 "math/rand" 28 "net" 29 "os" 30 "sync" 31 "time" 32 ) 33 34 const ( 35 metricFmt = "%s %.4f %v\n" 36 ) 37 38 func startWorker( 39 target string, 40 metrics []string, 41 targetQPS int, 42 closeCh chan struct{}, 43 ) (numSuccess, numError int) { 44 var ( 45 rng = rand.New(rand.NewSource(time.Now().UnixNano())) 46 idealTimeBetweenSend = time.Duration(int(time.Second) / targetQPS) 47 ) 48 49 for { 50 select { 51 case <-closeCh: 52 return numSuccess, numError 53 default: 54 } 55 56 // Establish initial connection and reestablish if we get disconnected. 57 conn, err := net.Dial("tcp", target) 58 if err != nil { 59 fmt.Printf("dial error: %v, reconnecting in one second\n", err) 60 time.Sleep(1 * time.Second) 61 continue 62 } 63 64 for err == nil { 65 select { 66 case <-closeCh: 67 return numSuccess, numError 68 default: 69 } 70 71 var ( 72 now = time.Now() 73 randIdx = rng.Int63n(int64(len(metrics))) 74 metric = metrics[randIdx] 75 ) 76 77 _, err = fmt.Fprintf(conn, metricFmt, metric, rng.Float32(), now.Unix()) 78 numSuccess++ 79 80 timeSinceSend := time.Since(now) 81 if timeSinceSend < idealTimeBetweenSend { 82 time.Sleep(idealTimeBetweenSend - timeSinceSend) 83 } 84 } 85 86 numError++ 87 } 88 } 89 90 func main() { 91 var ( 92 target = flag.String("target", "0.0.0.0:7204", "Target host port") 93 numWorkers = flag.Int("numWorkers", 20, "Number of concurrent connections") 94 numMetrics = flag.Int("cardinality", 1000, "Cardinality of metrics") 95 metric = flag.String("name", "local.random", "The metric you send will be [name].[0..1024]") 96 targetQPS = flag.Int("qps", 1000, "Target QPS") 97 duration = flag.Duration("duration", 10*time.Second, "Duration of test") 98 ) 99 100 flag.Parse() 101 if len(*target) == 0 { 102 flag.Usage() 103 os.Exit(-1) 104 } 105 106 metrics := make([]string, 0, *numMetrics) 107 for i := 0; i < *numMetrics; i++ { 108 metrics = append(metrics, fmt.Sprintf("%s.%d", *metric, i)) 109 } 110 111 var ( 112 targetQPSPerWorker = *targetQPS / *numWorkers 113 wg sync.WaitGroup 114 lock sync.Mutex 115 numSuccess int 116 numError int 117 closeCh = make(chan struct{}) 118 ) 119 for n := 0; n < *numWorkers; n++ { 120 wg.Add(1) 121 go func() { 122 nSuccess, nError := startWorker( 123 *target, metrics, targetQPSPerWorker, closeCh) 124 125 lock.Lock() 126 numSuccess += nSuccess 127 numError += nError 128 lock.Unlock() 129 130 wg.Done() 131 }() 132 } 133 134 start := time.Now() 135 go func() { 136 time.Sleep(*duration) 137 138 fmt.Println("beginning shutdown...") 139 close(closeCh) 140 }() 141 142 wg.Wait() 143 144 durationOfBench := time.Since(start).Seconds() 145 fmt.Println("average QPS: ", float64(numSuccess)/durationOfBench) 146 }