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  }