github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/tools/loadtest/loadtest.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"math/rand"
    14  	"net"
    15  	"os"
    16  	"os/exec"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  // This script runs random noms commands against random datasets on a database.
    22  //
    23  // Example usage:
    24  // > go run path/to/loadtest.go http://demo.noms.io/cli-tour
    25  //
    26  // Imports should be Go builtin libraries only, so that this can be run with "go run".
    27  
    28  type runnerFn func(db, ds string)
    29  
    30  type runner struct {
    31  	name string
    32  	fn   runnerFn
    33  }
    34  
    35  func main() {
    36  	rand.Seed(time.Now().UnixNano() + bestEffortGetIP())
    37  
    38  	if len(os.Args) != 2 {
    39  		fmt.Println("Usage: loadtest <database>")
    40  		os.Exit(-1)
    41  	}
    42  
    43  	db := os.Args[1]
    44  
    45  	rs := []runner{
    46  		{"diff", runDiff},
    47  		{"log diff", runLogDiff},
    48  		{"log show", runLogShow},
    49  		{"show", runShow},
    50  		{"sync", runSync},
    51  	}
    52  
    53  	for ds := range streamDs(db) {
    54  		start := time.Now()
    55  		r := rs[rand.Intn(len(rs))]
    56  		fmt.Println(time.Now().Format(time.Stamp), r.name, db, ds)
    57  		r.fn(db, fmt.Sprintf("%s::%s", db, ds))
    58  		fmt.Println("  took", time.Since(start).String())
    59  	}
    60  }
    61  
    62  func bestEffortGetIP() (asNum int64) {
    63  	addrs, err := net.InterfaceAddrs()
    64  	if err != nil {
    65  		return
    66  	}
    67  
    68  	for _, a := range addrs {
    69  		if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
    70  			if ipnet.IP.To4() != nil {
    71  				asNum = int64(binary.BigEndian.Uint32([]byte(ipnet.IP.To4())))
    72  				break
    73  			}
    74  		}
    75  	}
    76  	return
    77  }
    78  
    79  func runDiff(db, ds string) {
    80  	if parent := getParent(db, ds); parent != "" {
    81  		call(nil, "noms", "diff", ds, parent)
    82  	} else {
    83  		fmt.Println("    (no parent, cannot diff)")
    84  	}
    85  }
    86  
    87  func runLogDiff(db, ds string) {
    88  	call(nil, "noms", "log", ds)
    89  }
    90  
    91  func runLogShow(db, ds string) {
    92  	call(nil, "noms", "log", "--show-value", ds)
    93  }
    94  
    95  func runShow(db, ds string) {
    96  	if strings.HasSuffix(ds, "/raw") {
    97  		fmt.Println("    (skipping raw file, blobs are too slow)")
    98  	} else {
    99  		call(nil, "noms", "show", ds)
   100  	}
   101  }
   102  
   103  func runSync(db, ds string) {
   104  	dir, err := ioutil.TempDir("", "loadtest")
   105  	if err != nil {
   106  		fmt.Fprintln(os.Stderr, "  ERROR: failed to create temp directory:", err.Error())
   107  		return
   108  	}
   109  
   110  	defer os.RemoveAll(dir)
   111  	// Try to sync to parent, then from parent to head.
   112  	// If there isn't a parent then just sync head.
   113  	syncDs := fmt.Sprintf("ldb:%s::sync", dir)
   114  	if parent := getParent(db, ds); parent != "" {
   115  		call(nil, "noms", "sync", parent, syncDs)
   116  	}
   117  	call(nil, "noms", "sync", ds, syncDs)
   118  }
   119  
   120  func getParent(db, ds string) string {
   121  	buf := &bytes.Buffer{}
   122  	call(buf, "noms", "log", "-n", "2", "--oneline", ds)
   123  	// Output will look like:
   124  	// abc (Parent def)
   125  	// def (Parent None)
   126  	// We could use the first line and grab the Parent value from there, but it could also be Merge,
   127  	// and it might be None, so easier to just get the 2nd row.
   128  	lines := strings.SplitN(buf.String(), "\n", 2)
   129  	if len(lines) != 2 {
   130  		return ""
   131  	}
   132  	hsh := strings.SplitN(lines[0], " ", 2)[0]
   133  	return fmt.Sprintf("%s::#%s", db, hsh)
   134  }
   135  
   136  func call(stdout io.Writer, name string, arg ...string) error {
   137  	cmd := exec.Command(name, arg...)
   138  	fmt.Println("    >", name, strings.Join(arg, " "))
   139  	cmd.Stdout = stdout
   140  	cmd.Stderr = os.Stderr
   141  	err := cmd.Run()
   142  	if err != nil {
   143  		fmt.Fprintf(os.Stderr, "    ERROR: %s\n", err.Error())
   144  	}
   145  	return err
   146  }
   147  
   148  func streamDs(db string) <-chan string {
   149  	buf := &bytes.Buffer{}
   150  	err := call(buf, "noms", "ds", db)
   151  	if err != nil {
   152  		fmt.Fprintln(os.Stderr, "    ERROR: failed to get datasets")
   153  		os.Exit(-1)
   154  	}
   155  
   156  	out := strings.Trim(buf.String(), " \n")
   157  	if out == "" {
   158  		fmt.Fprintln(os.Stderr, "    ERROR: no datasets at", db)
   159  		os.Exit(-1)
   160  	}
   161  
   162  	datasets := strings.Split(out, "\n")
   163  
   164  	ch := make(chan string)
   165  	go func() {
   166  		for {
   167  			ch <- datasets[rand.Intn(len(datasets))]
   168  		}
   169  	}()
   170  	return ch
   171  }