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