github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/cmd/stress/stress.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !plan9 6 // +build !plan9 7 8 // The stress utility is intended for catching sporadic failures. 9 // It runs a given process in parallel in a loop and collects any failures. 10 // Usage: 11 // $ stress ./fmt.test -test.run=TestSometing -test.cpu=10 12 // You can also specify a number of parallel processes with -p flag; 13 // instruct the utility to not kill hanged processes for gdb attach; 14 // or specify the failure output you are looking for (if you want to 15 // ignore some other sporadic failures). 16 package main 17 18 import ( 19 "flag" 20 "fmt" 21 exec "golang.org/x/sys/execabs" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "regexp" 26 "runtime" 27 "syscall" 28 "time" 29 ) 30 31 var ( 32 flagP = flag.Int("p", runtime.NumCPU(), "run `N` processes in parallel") 33 flagTimeout = flag.Duration("timeout", 10*time.Minute, "timeout each process after `duration`") 34 flagKill = flag.Bool("kill", true, "kill timed out processes if true, otherwise just print pid (to attach with gdb)") 35 flagFailure = flag.String("failure", "", "fail only if output matches `regexp`") 36 flagIgnore = flag.String("ignore", "", "ignore failure if output matches `regexp`") 37 flagOutput = flag.String("o", defaultPrefix(), "output failure logs to `path` plus a unique suffix") 38 ) 39 40 func init() { 41 flag.Usage = func() { 42 os.Stderr.WriteString(`The stress utility is intended for catching sporadic failures. 43 It runs a given process in parallel in a loop and collects any failures. 44 Usage: 45 46 $ stress ./fmt.test -test.run=TestSometing -test.cpu=10 47 48 `) 49 flag.PrintDefaults() 50 } 51 } 52 53 func defaultPrefix() string { 54 date := time.Now().Format("go-stress-20060102T150405-") 55 return filepath.Join(os.TempDir(), date) 56 } 57 58 func main() { 59 flag.Parse() 60 if *flagP <= 0 || *flagTimeout <= 0 || len(flag.Args()) == 0 { 61 flag.Usage() 62 os.Exit(1) 63 } 64 var failureRe, ignoreRe *regexp.Regexp 65 if *flagFailure != "" { 66 var err error 67 if failureRe, err = regexp.Compile(*flagFailure); err != nil { 68 fmt.Println("bad failure regexp:", err) 69 os.Exit(1) 70 } 71 } 72 if *flagIgnore != "" { 73 var err error 74 if ignoreRe, err = regexp.Compile(*flagIgnore); err != nil { 75 fmt.Println("bad ignore regexp:", err) 76 os.Exit(1) 77 } 78 } 79 res := make(chan []byte) 80 for i := 0; i < *flagP; i++ { 81 go func() { 82 for { 83 cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...) 84 done := make(chan bool) 85 if *flagTimeout > 0 { 86 go func() { 87 select { 88 case <-done: 89 return 90 case <-time.After(*flagTimeout): 91 } 92 if !*flagKill { 93 fmt.Printf("process %v timed out\n", cmd.Process.Pid) 94 return 95 } 96 cmd.Process.Signal(syscall.SIGABRT) 97 select { 98 case <-done: 99 return 100 case <-time.After(10 * time.Second): 101 } 102 cmd.Process.Kill() 103 }() 104 } 105 out, err := cmd.CombinedOutput() 106 close(done) 107 if err != nil && (failureRe == nil || failureRe.Match(out)) && (ignoreRe == nil || !ignoreRe.Match(out)) { 108 out = append(out, fmt.Sprintf("\n\nERROR: %v\n", err)...) 109 } else { 110 out = []byte{} 111 } 112 res <- out 113 } 114 }() 115 } 116 runs, fails := 0, 0 117 start := time.Now() 118 ticker := time.NewTicker(5 * time.Second).C 119 for { 120 select { 121 case out := <-res: 122 runs++ 123 if len(out) == 0 { 124 continue 125 } 126 fails++ 127 dir, path := filepath.Split(*flagOutput) 128 f, err := ioutil.TempFile(dir, path) 129 if err != nil { 130 fmt.Printf("failed to create temp file: %v\n", err) 131 os.Exit(1) 132 } 133 f.Write(out) 134 f.Close() 135 if len(out) > 2<<10 { 136 out := out[:2<<10] 137 fmt.Printf("\n%s\n%s\n…\n", f.Name(), out) 138 } else { 139 fmt.Printf("\n%s\n%s\n", f.Name(), out) 140 } 141 case <-ticker: 142 elapsed := time.Since(start).Truncate(time.Second) 143 var pct string 144 if fails > 0 { 145 pct = fmt.Sprintf(" (%0.2f%%)", 100.0*float64(fails)/float64(runs)) 146 } 147 fmt.Printf("%v: %v runs so far, %v failures%s\n", elapsed, runs, fails, pct) 148 } 149 } 150 }