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 }