mirror of
https://github.com/golang/go
synced 2024-11-18 11:04:42 -07:00
bf1340f18c
While investigating golang/go#36664, the entire output of a failure could get pretty long - past fifty lines of content. I was getting confused as to why the output was being cut off. I had to discard a number of theories (the test being buggy, my expected output being wrong, etc) before I realised that something was truncating the output for the sake of the reader. Since that's what stress is doing, make it add an ellipsis character too, to further hint to the user that the output is being truncated for readability. Separate it with a newline to make it easier to notice. Change-Id: Ie052c13bb48d6fd842d6054848161266d9ea17e6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/215597 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
// Copyright 2015 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.
|
|
|
|
// +build !plan9
|
|
|
|
// The stress utility is intended for catching sporadic failures.
|
|
// It runs a given process in parallel in a loop and collects any failures.
|
|
// Usage:
|
|
// $ stress ./fmt.test -test.run=TestSometing -test.cpu=10
|
|
// You can also specify a number of parallel processes with -p flag;
|
|
// instruct the utility to not kill hanged processes for gdb attach;
|
|
// or specify the failure output you are looking for (if you want to
|
|
// ignore some other sporadic failures).
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
flagP = flag.Int("p", runtime.NumCPU(), "run `N` processes in parallel")
|
|
flagTimeout = flag.Duration("timeout", 10*time.Minute, "timeout each process after `duration`")
|
|
flagKill = flag.Bool("kill", true, "kill timed out processes if true, otherwise just print pid (to attach with gdb)")
|
|
flagFailure = flag.String("failure", "", "fail only if output matches `regexp`")
|
|
flagIgnore = flag.String("ignore", "", "ignore failure if output matches `regexp`")
|
|
flagOutput = flag.String("o", defaultPrefix(), "output failure logs to `path` plus a unique suffix")
|
|
)
|
|
|
|
func init() {
|
|
flag.Usage = func() {
|
|
os.Stderr.WriteString(`The stress utility is intended for catching sporadic failures.
|
|
It runs a given process in parallel in a loop and collects any failures.
|
|
Usage:
|
|
|
|
$ stress ./fmt.test -test.run=TestSometing -test.cpu=10
|
|
|
|
`)
|
|
flag.PrintDefaults()
|
|
}
|
|
}
|
|
|
|
func defaultPrefix() string {
|
|
date := time.Now().Format("go-stress-20060102T150405-")
|
|
return filepath.Join(os.TempDir(), date)
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
if *flagP <= 0 || *flagTimeout <= 0 || len(flag.Args()) == 0 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
var failureRe, ignoreRe *regexp.Regexp
|
|
if *flagFailure != "" {
|
|
var err error
|
|
if failureRe, err = regexp.Compile(*flagFailure); err != nil {
|
|
fmt.Println("bad failure regexp:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
if *flagIgnore != "" {
|
|
var err error
|
|
if ignoreRe, err = regexp.Compile(*flagIgnore); err != nil {
|
|
fmt.Println("bad ignore regexp:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
res := make(chan []byte)
|
|
for i := 0; i < *flagP; i++ {
|
|
go func() {
|
|
for {
|
|
cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
|
|
done := make(chan bool)
|
|
if *flagTimeout > 0 {
|
|
go func() {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-time.After(*flagTimeout):
|
|
}
|
|
if !*flagKill {
|
|
fmt.Printf("process %v timed out\n", cmd.Process.Pid)
|
|
return
|
|
}
|
|
cmd.Process.Signal(syscall.SIGABRT)
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-time.After(10 * time.Second):
|
|
}
|
|
cmd.Process.Kill()
|
|
}()
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
close(done)
|
|
if err != nil && (failureRe == nil || failureRe.Match(out)) && (ignoreRe == nil || !ignoreRe.Match(out)) {
|
|
out = append(out, fmt.Sprintf("\n\nERROR: %v\n", err)...)
|
|
} else {
|
|
out = []byte{}
|
|
}
|
|
res <- out
|
|
}
|
|
}()
|
|
}
|
|
runs, fails := 0, 0
|
|
ticker := time.NewTicker(5 * time.Second).C
|
|
for {
|
|
select {
|
|
case out := <-res:
|
|
runs++
|
|
if len(out) == 0 {
|
|
continue
|
|
}
|
|
fails++
|
|
dir, path := filepath.Split(*flagOutput)
|
|
f, err := ioutil.TempFile(dir, path)
|
|
if err != nil {
|
|
fmt.Printf("failed to create temp file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
f.Write(out)
|
|
f.Close()
|
|
if len(out) > 2<<10 {
|
|
out := out[:2<<10]
|
|
fmt.Printf("\n%s\n%s\n…\n", f.Name(), out)
|
|
} else {
|
|
fmt.Printf("\n%s\n%s\n", f.Name(), out)
|
|
}
|
|
case <-ticker:
|
|
fmt.Printf("%v runs so far, %v failures\n", runs, fails)
|
|
}
|
|
}
|
|
}
|