diff --git a/tools/loadtest/loadtest.go b/tools/loadtest/loadtest.go new file mode 100644 index 0000000000..c15a1ad049 --- /dev/null +++ b/tools/loadtest/loadtest.go @@ -0,0 +1,152 @@ +// Copyright 2016 Attic Labs, Inc. All rights reserved. +// Licensed under the Apache License, version 2.0: +// http://www.apache.org/licenses/LICENSE-2.0 + +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "strings" + "time" +) + +// This script runs random noms commands against random datasets on a database. +// +// Example usage: +// > go run path/to/loadtest.go http://demo.noms.io/cli-tour +// +// Imports should be Go builtin libraries only, so that this can be run with "go run". + +type runnerFn func(db, ds string) + +type runner struct { + name string + fn runnerFn +} + +func main() { + rand.Seed(time.Now().Unix()) + + if len(os.Args) != 2 { + fmt.Println("Usage: loadtest ") + os.Exit(-1) + } + + db := os.Args[1] + + rs := []runner{ + runner{"diff", runDiff}, + runner{"log diff", runLogDiff}, + runner{"log show", runLogShow}, + runner{"show", runShow}, + runner{"sync", runSync}, + } + + for ds := range streamDs(db) { + start := time.Now() + r := rs[rand.Intn(len(rs))] + fmt.Println(time.Now().Format(time.Stamp), r.name, db, ds) + r.fn(db, fmt.Sprintf("%s::%s", db, ds)) + fmt.Println(" took", time.Since(start).String()) + } +} + +func runDiff(db, ds string) { + if parent := getParent(db, ds); parent != "" { + call(nil, "noms", "diff", ds, parent) + } else { + fmt.Println(" (no parent, cannot diff)") + } +} + +func runLogDiff(db, ds string) { + call(nil, "noms", "log", ds) +} + +func runLogShow(db, ds string) { + call(nil, "noms", "log", "-show-value", ds) +} + +func runShow(db, ds string) { + if strings.HasSuffix(ds, "/raw") { + fmt.Println(" (skipping raw file, blobs are too slow)") + } else { + call(nil, "noms", "show", ds) + } +} + +func runSync(db, ds string) { + dir, err := ioutil.TempDir("", "loadtest") + if err != nil { + fmt.Fprintln(os.Stderr, " ERROR: failed to create temp directory:", err.Error()) + return + } + + defer os.RemoveAll(dir) + // Try to sync to parent, then from parent to head. + // If there isn't a parent then just sync head. + syncDs := fmt.Sprintf("ldb:%s::sync", dir) + if parent := getParent(db, ds); parent != "" { + call(nil, "noms", "sync", parent, syncDs) + } + call(nil, "noms", "sync", ds, syncDs) +} + +func getParent(db, ds string) string { + buf := &bytes.Buffer{} + call(buf, "noms", "log", "-n", "2", "-oneline", ds) + // Output will look like: + // abc (Parent def) + // def (Parent None) + // We could use the first line and grab the Parent value from there, but it could also be Merge, + // and it might be None, so easier to just get the 2nd row. + lines := strings.SplitN(buf.String(), "\n", 2) + if len(lines) != 2 { + return "" + } + hsh := strings.SplitN(lines[0], " ", 2)[0] + return fmt.Sprintf("%s::#%s", db, hsh) +} + +func call(stdout io.Writer, name string, arg ...string) error { + cmd := exec.Command(name, arg...) + fmt.Println(" >", name, strings.Join(arg, " ")) + cmd.Stdout = stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, " ERROR: %s\n", err.Error()) + } + return err +} + +func streamDs(db string) <-chan string { + buf := &bytes.Buffer{} + err := call(buf, "noms", "ds", db) + if err != nil { + fmt.Fprintln(os.Stderr, " ERROR: failed to get datasets") + os.Exit(-1) + } + + out := strings.Trim(buf.String(), " \n") + if out == "" { + fmt.Fprintln(os.Stderr, " ERROR: no datasets at", db) + os.Exit(-1) + } + + datasets := strings.Split(out, "\n") + + ch := make(chan string) + go func() { + for { + ch <- datasets[rand.Intn(len(datasets))] + } + }() + return ch +}