]> Cypherpunks repositories - gostls13.git/commitdiff
test/stress: start of a runtime stress program
authorBrad Fitzpatrick <bradfitz@golang.org>
Mon, 15 Apr 2013 18:50:14 +0000 (11:50 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 15 Apr 2013 18:50:14 +0000 (11:50 -0700)
Runs forever, stressing the runtime in various ways.

It should never terminate.

R=golang-dev, r, minux.ma
CC=golang-dev
https://golang.org/cl/8583047

test/stress/maps.go [new file with mode: 0644]
test/stress/parsego.go [new file with mode: 0644]
test/stress/runstress.go [new file with mode: 0644]

diff --git a/test/stress/maps.go b/test/stress/maps.go
new file mode 100644 (file)
index 0000000..d022e19
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "math/rand"
+       "runtime"
+       "sync"
+)
+
+func mapTypes() []MapType {
+       // TODO(bradfitz): bunch more map types of all different key and value types.
+       // Use reflect.MapOf and a program to generate lots of types & struct types.
+       // For now, just one:
+       return []MapType{intMapType{}}
+}
+
+type MapType interface {
+       NewMap() Map
+}
+
+type Map interface {
+       AddItem()
+       DelItem()
+       Len() int
+       GetItem()
+       RangeAll()
+}
+
+func stressMapType(mt MapType, done func()) {
+       defer done()
+       m := mt.NewMap()
+       for m.Len() < 10000 {
+               Println("map at ", m.Len())
+               if m.Len()%100 == 0 {
+                       runtime.Gosched()
+               }
+               m.AddItem()
+               m.AddItem()
+               m.DelItem()
+               var wg sync.WaitGroup
+               const numGets = 10
+               wg.Add(numGets)
+               for i := 0; i < numGets; i++ {
+                       go func(i int) {
+                               if i&1 == 0 {
+                                       m.GetItem()
+                               } else {
+                                       m.RangeAll()
+                               }
+                               wg.Done()
+                       }(i)
+               }
+               wg.Wait()
+       }
+       for m.Len() > 0 {
+               m.DelItem()
+       }
+}
+
+type intMapType struct{}
+
+func (intMapType) NewMap() Map {
+       return make(intMap)
+}
+
+var deadcafe = []byte("\xDE\xAD\xCA\xFE")
+
+type intMap map[int][]byte
+
+func (m intMap) AddItem() {
+       s0 := len(m)
+       for len(m) == s0 {
+               key := rand.Intn(s0 + 1)
+               m[key] = make([]byte, rand.Intn(64<<10))
+       }
+}
+
+func (m intMap) DelItem() {
+       for k := range m {
+               delete(m, k)
+               return
+       }
+}
+
+func (m intMap) GetItem() {
+       key := rand.Intn(len(m))
+       if s, ok := m[key]; ok {
+               copy(s, deadcafe)
+       }
+}
+
+func (m intMap) Len() int { return len(m) }
+
+func (m intMap) RangeAll() {
+       for _ = range m {
+       }
+}
+
+func stressMaps() {
+       for {
+               var wg sync.WaitGroup
+               for _, mt := range mapTypes() {
+                       wg.Add(1)
+                       go stressMapType(mt, wg.Done)
+               }
+               wg.Wait()
+       }
+}
diff --git a/test/stress/parsego.go b/test/stress/parsego.go
new file mode 100644 (file)
index 0000000..a781f19
--- /dev/null
@@ -0,0 +1,220 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "go/ast"
+       "go/parser"
+       "go/token"
+       "os"
+       "path"
+       "runtime"
+       "strings"
+)
+
+func isGoFile(dir os.FileInfo) bool {
+       return !dir.IsDir() &&
+               !strings.HasPrefix(dir.Name(), ".") && // ignore .files
+               path.Ext(dir.Name()) == ".go"
+}
+
+func isPkgFile(dir os.FileInfo) bool {
+       return isGoFile(dir) &&
+               !strings.HasSuffix(dir.Name(), "_test.go") // ignore test files
+}
+
+func pkgName(filename string) string {
+       file, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.PackageClauseOnly)
+       if err != nil || file == nil {
+               return ""
+       }
+       return file.Name.Name
+}
+
+func parseDir(dirpath string) map[string]*ast.Package {
+       // the package name is the directory name within its parent.
+       // (use dirname instead of path because dirname is clean; it
+       // has no trailing '/')
+       _, pkgname := path.Split(dirpath)
+
+       // filter function to select the desired .go files
+       filter := func(d os.FileInfo) bool {
+               if isPkgFile(d) {
+                       // Some directories contain main packages: Only accept
+                       // files that belong to the expected package so that
+                       // parser.ParsePackage doesn't return "multiple packages
+                       // found" errors.
+                       // Additionally, accept the special package name
+                       // fakePkgName if we are looking at cmd documentation.
+                       name := pkgName(dirpath + "/" + d.Name())
+                       return name == pkgname
+               }
+               return false
+       }
+
+       // get package AST
+       pkgs, err := parser.ParseDir(token.NewFileSet(), dirpath, filter, parser.ParseComments)
+       if err != nil {
+               println("parse", dirpath, err.Error())
+               panic("go ParseDir fail: " + err.Error())
+       }
+       return pkgs
+}
+
+func stressParseGo() {
+       pkgroot := runtime.GOROOT() + "/src/pkg/"
+       for {
+               m := make(map[string]map[string]*ast.Package)
+               for _, pkg := range packages {
+                       m[pkg] = parseDir(pkgroot + pkg)
+                       Println("parsed go package", pkg)
+               }
+       }
+}
+
+// find . -type d -not -path "./exp" -not -path "./exp/*" -printf "\t\"%p\",\n" | sort | sed "s/\.\///" | grep -v testdata
+var packages = []string{
+       "archive",
+       "archive/tar",
+       "archive/zip",
+       "bufio",
+       "builtin",
+       "bytes",
+       "compress",
+       "compress/bzip2",
+       "compress/flate",
+       "compress/gzip",
+       "compress/lzw",
+       "compress/zlib",
+       "container",
+       "container/heap",
+       "container/list",
+       "container/ring",
+       "crypto",
+       "crypto/aes",
+       "crypto/cipher",
+       "crypto/des",
+       "crypto/dsa",
+       "crypto/ecdsa",
+       "crypto/elliptic",
+       "crypto/hmac",
+       "crypto/md5",
+       "crypto/rand",
+       "crypto/rc4",
+       "crypto/rsa",
+       "crypto/sha1",
+       "crypto/sha256",
+       "crypto/sha512",
+       "crypto/subtle",
+       "crypto/tls",
+       "crypto/x509",
+       "crypto/x509/pkix",
+       "database",
+       "database/sql",
+       "database/sql/driver",
+       "debug",
+       "debug/dwarf",
+       "debug/elf",
+       "debug/gosym",
+       "debug/macho",
+       "debug/pe",
+       "encoding",
+       "encoding/ascii85",
+       "encoding/asn1",
+       "encoding/base32",
+       "encoding/base64",
+       "encoding/binary",
+       "encoding/csv",
+       "encoding/gob",
+       "encoding/hex",
+       "encoding/json",
+       "encoding/pem",
+       "encoding/xml",
+       "errors",
+       "expvar",
+       "flag",
+       "fmt",
+       "go",
+       "go/ast",
+       "go/build",
+       "go/doc",
+       "go/format",
+       "go/parser",
+       "go/printer",
+       "go/scanner",
+       "go/token",
+       "hash",
+       "hash/adler32",
+       "hash/crc32",
+       "hash/crc64",
+       "hash/fnv",
+       "html",
+       "html/template",
+       "image",
+       "image/color",
+       "image/draw",
+       "image/gif",
+       "image/jpeg",
+       "image/png",
+       "index",
+       "index/suffixarray",
+       "io",
+       "io/ioutil",
+       "log",
+       "log/syslog",
+       "math",
+       "math/big",
+       "math/cmplx",
+       "math/rand",
+       "mime",
+       "mime/multipart",
+       "net",
+       "net/http",
+       "net/http/cgi",
+       "net/http/cookiejar",
+       "net/http/fcgi",
+       "net/http/httptest",
+       "net/http/httputil",
+       "net/http/pprof",
+       "net/mail",
+       "net/rpc",
+       "net/rpc/jsonrpc",
+       "net/smtp",
+       "net/textproto",
+       "net/url",
+       "os",
+       "os/exec",
+       "os/signal",
+       "os/user",
+       "path",
+       "path/filepath",
+       "reflect",
+       "regexp",
+       "regexp/syntax",
+       "runtime",
+       "runtime/cgo",
+       "runtime/debug",
+       "runtime/pprof",
+       "runtime/race",
+       "sort",
+       "strconv",
+       "strings",
+       "sync",
+       "sync/atomic",
+       "syscall",
+       "testing",
+       "testing/iotest",
+       "testing/quick",
+       "text",
+       "text/scanner",
+       "text/tabwriter",
+       "text/template",
+       "text/template/parse",
+       "time",
+       "unicode",
+       "unicode/utf16",
+       "unicode/utf8",
+       "unsafe",
+}
diff --git a/test/stress/runstress.go b/test/stress/runstress.go
new file mode 100644 (file)
index 0000000..b5adf6a
--- /dev/null
@@ -0,0 +1,164 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The runstress tool stresses the runtime.
+//
+// It runs forever and should never fail. It tries to stress the garbage collector,
+// maps, channels, the network, and everything else provided by the runtime.
+package main
+
+import (
+       "flag"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "math/rand"
+       "net"
+       "net/http"
+       "net/http/httptest"
+       "os/exec"
+       "strconv"
+       "time"
+)
+
+var (
+       v         = flag.Bool("v", false, "verbose")
+       doMaps    = flag.Bool("maps", true, "stress maps")
+       doExec    = flag.Bool("exec", true, "stress exec")
+       doChan    = flag.Bool("chan", true, "stress channels")
+       doNet     = flag.Bool("net", true, "stress networking")
+       doParseGo = flag.Bool("parsego", true, "stress parsing Go (generates garbage)")
+)
+
+func Println(a ...interface{}) {
+       if *v {
+               log.Println(a...)
+       }
+}
+
+func dialStress(a net.Addr) {
+       for {
+               d := net.Dialer{Timeout: time.Duration(rand.Intn(1e9))}
+               c, err := d.Dial("tcp", a.String())
+               if err == nil {
+                       Println("did dial")
+                       go func() {
+                               time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
+                               c.Close()
+                               Println("closed dial")
+                       }()
+               }
+               // Don't run out of ephermeral ports too quickly:
+               time.Sleep(250 * time.Millisecond)
+       }
+}
+
+func stressNet() {
+       ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               size, _ := strconv.Atoi(r.FormValue("size"))
+               w.Write(make([]byte, size))
+       }))
+       go dialStress(ts.Listener.Addr())
+       for {
+               size := rand.Intn(128 << 10)
+               res, err := http.Get(fmt.Sprintf("%s/?size=%d", ts.URL, size))
+               if err != nil {
+                       log.Fatalf("stressNet: http Get error: %v", err)
+               }
+               if res.StatusCode != 200 {
+                       log.Fatalf("stressNet: Status code = %d", res.StatusCode)
+               }
+               n, err := io.Copy(ioutil.Discard, res.Body)
+               if err != nil {
+                       log.Fatalf("stressNet: io.Copy: %v", err)
+               }
+               if n != int64(size) {
+                       log.Fatalf("stressNet: copied = %d; want %d", n, size)
+               }
+               res.Body.Close()
+               Println("did http", size)
+       }
+}
+
+func doAnExec() {
+       exit := rand.Intn(2)
+       wantOutput := fmt.Sprintf("output-%d", rand.Intn(1e9))
+       cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("echo %s; exit %d", wantOutput, exit))
+       out, err := cmd.CombinedOutput()
+       if exit == 1 {
+               if err == nil {
+                       log.Fatal("stressExec: unexpected exec success")
+               }
+               return
+       }
+       if err != nil {
+               log.Fatalf("stressExec: exec failure: %v: %s", err, out)
+       }
+       wantOutput += "\n"
+       if string(out) != wantOutput {
+               log.Fatalf("stressExec: exec output = %q; want %q", out, wantOutput)
+       }
+       Println("did exec")
+}
+
+func stressExec() {
+       gate := make(chan bool, 10) // max execs at once
+       for {
+               gate <- true
+               go func() {
+                       doAnExec()
+                       <-gate
+               }()
+       }
+}
+
+func ringf(in <-chan int, out chan<- int, donec chan<- bool) {
+       for {
+               n := <-in
+               if n == 0 {
+                       donec <- true
+                       return
+               }
+               out <- n - 1
+       }
+}
+
+func threadRing(bufsize int) {
+       const N = 100
+       donec := make(chan bool)
+       one := make(chan int, bufsize) // will be input to thread 1
+       var in, out chan int = nil, one
+       for i := 1; i <= N-1; i++ {
+               in, out = out, make(chan int, bufsize)
+               go ringf(in, out, donec)
+       }
+       go ringf(out, one, donec)
+       one <- N
+       <-donec
+       Println("did threadring of", bufsize)
+}
+
+func stressChannels() {
+       for {
+               threadRing(0)
+               threadRing(1)
+       }
+}
+
+func main() {
+       flag.Parse()
+       for want, f := range map[*bool]func(){
+               doMaps:    stressMaps,
+               doNet:     stressNet,
+               doExec:    stressExec,
+               doChan:    stressChannels,
+               doParseGo: stressParseGo,
+       } {
+               if *want {
+                       go f()
+               }
+       }
+       select {}
+}