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  }