/
stackdemo.go
154 lines (131 loc) · 3.45 KB
/
stackdemo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"flag"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"runtime/pprof"
"time"
)
// syncEvent blocks waiting threads until release is called
type syncEvent struct {
done chan struct{}
}
func newSyncEvent() syncEvent {
return syncEvent{make(chan struct{})}
}
func (s *syncEvent) wait() {
<-s.done
}
func main() {
aStacks := flag.Int("numAStacks", 100, "Number of A stacks")
bStacks := flag.Int("numBStacks", 50, "Number of B stacks")
pprofAddr := flag.String("pprofAddr", "localhost:8080", "Address to listen for /debug/pprof; ''=disabled")
oom := flag.Bool("oom", false, "If true will use memory until it gets killed/runs out, which should dump stacks")
oomTouch := flag.Bool("oomTouch", true, "If true, will touch each page of memory that it allocates")
writeStacks := flag.String("writeStacks", "", "Path to write stacks using pprof.Profile.WriteTo")
writeStacksDebug := flag.Int("writeStacksDebug", 2, "pprof.Profile.WriteTo; 0=binary; 1=comments; 2=text")
exit := flag.Bool("exit", false, "true: Exit immediately at end; false: block forever")
panicAtEnd := flag.Bool("panic", false, "true: Panic at end of main()")
oomChunkSizeMiB := flag.Int("oomChunkSizeMiB", 1, "Size of allocations when trying to run out of memory (MiB)")
runningGoroutines := flag.Int("runningGoroutines", 0, "Goroutines that will be running; causes them to not be written in stacks")
flag.Parse()
if *pprofAddr != "" {
log.Printf("listening on addr http://%s ...", *pprofAddr)
go func() {
err := http.ListenAndServe(*pprofAddr, nil)
if err != nil {
panic(err)
}
}()
}
// start the stacks, but stop all of them from returning
blockAllStacks := newSyncEvent()
for i := 0; i < *aStacks; i++ {
go a1(&blockAllStacks)
}
for i := 0; i < *bStacks; i++ {
go b1(&blockAllStacks)
}
for i := 0; i < *runningGoroutines; i++ {
go running()
}
if *writeStacks != "" {
log.Printf("writing stacks to %s ...", *writeStacks)
f, err := os.OpenFile(*writeStacks, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
panic(err)
}
err = pprof.Lookup("goroutine").WriteTo(f, *writeStacksDebug)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}
if *oom {
log.Printf("allocating memory of size=%d MiB; touch=%t ...", *oomChunkSizeMiB, *oomTouch)
for {
go useMemory(*oomChunkSizeMiB, *oomTouch, &blockAllStacks)
time.Sleep(time.Millisecond)
}
}
if *panicAtEnd {
panic("panic at end of main()")
}
if *exit {
return
}
log.Printf("blocking forever ...")
blockAllStacks.wait()
}
const oneMiB = 1024 * 1024
func useMemory(chunkSizeMiB int, touch bool, block *syncEvent) {
mem := make([]byte, chunkSizeMiB*oneMiB)
if touch {
// touch all pages to ensure they are allocated
for i := 0; i < len(mem); i += 4096 {
mem[i] = byte(i)
}
}
block.wait()
// ensure mem is held across the lock
fmt.Println(mem[len(mem)-1])
}
func a1(block *syncEvent) {
a2(block)
}
func a2(block *syncEvent) {
block.wait()
}
func b1(block *syncEvent) {
b2(block)
}
func b2(block *syncEvent) {
block.wait()
}
func burnCPU() int {
total := 0
for i := 0; i < 10000000; i++ {
total += i + total*total/(i+1)
}
return total
}
func running() {
total := 0
for {
// do something to burn CPU so this stays "running"
total = burnCPU()
// with go1.13: necessary since otherwise this is a busy loop and GC can't run
runtime.Gosched()
if total == ^int(0) {
break
}
}
fmt.Println(total)
}