Skip to content

Commit d9c6af3

Browse files
rscgopherbot
authored andcommitted
cmd/stress: add -count flag, and print number of active commands in status updates
Add -count flag to be able to say 'run 100 and stop'. This is useful when using stress to test for flakes in a git bisect loop or other scripts. Print the number of active commands during status updates. This is useful for knowing what's going on as the run reaches its conclusion (and earlier). Sort flag list. Change-Id: Iad4ef3069aeebbb58923ea3696909d2c1e9dbd91 Reviewed-on: https://go-review.googlesource.com/c/tools/+/576916 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Russ Cox <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent ef4d083 commit d9c6af3

File tree

1 file changed

+64
-27
lines changed

1 file changed

+64
-27
lines changed

cmd/stress/stress.go

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,27 @@
1818
package main
1919

2020
import (
21+
"bytes"
2122
"flag"
2223
"fmt"
2324
"os"
2425
"os/exec"
2526
"path/filepath"
2627
"regexp"
2728
"runtime"
29+
"sync/atomic"
2830
"syscall"
2931
"time"
3032
)
3133

3234
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)")
35+
flagCount = flag.Int("count", 0, "stop after `N` runs (default never stop)")
3636
flagFailure = flag.String("failure", "", "fail only if output matches `regexp`")
3737
flagIgnore = flag.String("ignore", "", "ignore failure if output matches `regexp`")
38+
flagKill = flag.Bool("kill", true, "kill timed out processes if true, otherwise just print pid (to attach with gdb)")
3839
flagOutput = flag.String("o", defaultPrefix(), "output failure logs to `path` plus a unique suffix")
40+
flagP = flag.Int("p", runtime.NumCPU(), "run `N` processes in parallel")
41+
flagTimeout = flag.Duration("timeout", 10*time.Minute, "timeout each process after `duration`")
3942
)
4043

4144
func init() {
@@ -78,12 +81,22 @@ func main() {
7881
}
7982
}
8083
res := make(chan []byte)
84+
var started atomic.Int64
8185
for i := 0; i < *flagP; i++ {
8286
go func() {
8387
for {
88+
// Note: Must started.Add(1) even if not using -count,
89+
// because it enables the '%d active' print below.
90+
if started.Add(1) > int64(*flagCount) && *flagCount > 0 {
91+
break
92+
}
8493
cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
94+
var buf bytes.Buffer
95+
cmd.Stdout = &buf
96+
cmd.Stderr = &buf
97+
err := cmd.Start() // make cmd.Process valid for timeout goroutine
8598
done := make(chan bool)
86-
if *flagTimeout > 0 {
99+
if err == nil && *flagTimeout > 0 {
87100
go func() {
88101
select {
89102
case <-done:
@@ -103,7 +116,10 @@ func main() {
103116
cmd.Process.Kill()
104117
}()
105118
}
106-
out, err := cmd.CombinedOutput()
119+
if err == nil {
120+
err = cmd.Wait()
121+
}
122+
out := buf.Bytes()
107123
close(done)
108124
if err != nil && (failureRe == nil || failureRe.Match(out)) && (ignoreRe == nil || !ignoreRe.Match(out)) {
109125
out = append(out, fmt.Sprintf("\n\nERROR: %v\n", err)...)
@@ -117,35 +133,56 @@ func main() {
117133
runs, fails := 0, 0
118134
start := time.Now()
119135
ticker := time.NewTicker(5 * time.Second).C
136+
status := func(context string) {
137+
elapsed := time.Since(start).Truncate(time.Second)
138+
var pct string
139+
if fails > 0 {
140+
pct = fmt.Sprintf(" (%0.2f%%)", 100.0*float64(fails)/float64(runs))
141+
}
142+
var active string
143+
n := started.Load() - int64(runs)
144+
if *flagCount > 0 {
145+
// started counts past *flagCount at end; do not count those
146+
// TODO: n = min(n, int64(*flagCount-runs))
147+
if x := int64(*flagCount - runs); n > x {
148+
n = x
149+
}
150+
}
151+
if n > 0 {
152+
active = fmt.Sprintf(", %d active", n)
153+
}
154+
fmt.Printf("%v: %v runs %s, %v failures%s%s\n", elapsed, runs, context, fails, pct, active)
155+
}
120156
for {
121157
select {
122158
case out := <-res:
123159
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)
160+
if len(out) > 0 {
161+
fails++
162+
dir, path := filepath.Split(*flagOutput)
163+
f, err := os.CreateTemp(dir, path)
164+
if err != nil {
165+
fmt.Printf("failed to create temp file: %v\n", err)
166+
os.Exit(1)
167+
}
168+
f.Write(out)
169+
f.Close()
170+
if len(out) > 2<<10 {
171+
out := out[:2<<10]
172+
fmt.Printf("\n%s\n%s\n\n", f.Name(), out)
173+
} else {
174+
fmt.Printf("\n%s\n%s\n", f.Name(), out)
175+
}
133176
}
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)
177+
if *flagCount > 0 && runs >= *flagCount {
178+
status("total")
179+
if fails > 0 {
180+
os.Exit(1)
181+
}
182+
os.Exit(0)
141183
}
142184
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)
185+
status("so far")
149186
}
150187
}
151188
}

0 commit comments

Comments
 (0)