/
ppdump.go
132 lines (113 loc) · 2.6 KB
/
ppdump.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
package ppdump
import (
"runtime/pprof"
"sync"
"time"
)
const (
_defaultInterval = time.Second
_defaultThrottle = time.Minute
)
var std *Dumper
// Config contains information required for creating a new Dumper.
type Config struct {
Profiles map[string]ProfileOpts // map of profile names to properties
PollInterval time.Duration // how often to poll for goroutines. defaults to one second
Throttle time.Duration // time to wait before writing another dump. defaults to one minute
}
// An ActionFunc is a function that will be called when a profile's threshold
// has been exceeded.
type ActionFunc func(profile *pprof.Profile)
// ProfileOpts establishes behavior of when to trigger a dump and what to do
// when that occurs.
type ProfileOpts struct {
Action ActionFunc
Threshold int
}
// Start begins the profiling and dumping procedure using the top level dumper.
func Start(c Config) {
std = New(c)
std.Start()
}
// Stop stops the profiling and dumping procedure
func Stop() {
if std != nil {
std.Stop()
}
}
// A Dumper dumps.
// TODO actually describe what it does
type Dumper struct {
sync.Mutex
interval time.Duration
throttle time.Duration
lim int
thr float64
avg float64
nv int64
profiles map[string]ProfileOpts
stop chan struct{}
lastDumps map[string]time.Time
}
// New returns a new Dumper
func New(c Config) *Dumper {
if c.PollInterval == 0 {
c.PollInterval = _defaultInterval
}
if c.Throttle == 0 {
c.Throttle = _defaultThrottle
}
return &Dumper{
interval: c.PollInterval,
throttle: c.Throttle,
profiles: c.Profiles,
lastDumps: make(map[string]time.Time),
}
}
// Start starts the routine of dumping.
func (d *Dumper) Start() {
d.Lock()
defer d.Unlock()
d.stop = make(chan struct{})
go d.runLoop()
}
func (d *Dumper) runLoop() {
t := time.NewTicker(d.interval)
for {
select {
case <-t.C:
d.checkAndDump()
case <-d.stop:
return
}
}
}
func (d *Dumper) dump(p ProfileOpts, pp *pprof.Profile) {
d.Lock()
defer d.Unlock()
now := time.Now()
lastDump := d.lastDumps[pp.Name()]
if now.Before(lastDump.Add(d.throttle)) {
return
}
d.lastDumps[pp.Name()] = now
if p.Action != nil {
p.Action(pp)
}
}
// Stop stops the runloop. No-op if called more than once.
func (d *Dumper) Stop() {
d.Lock()
defer d.Unlock()
defer func() { recover() }() // doesn't matter if the channel is closed twice
close(d.stop)
}
func (d *Dumper) checkAndDump() {
for name, p := range d.profiles {
if profile := pprof.Lookup(name); profile != nil {
if profile.Count() > p.Threshold {
d.dump(p, profile)
}
}
}
}