├── ppdump.out ├── ppdump_test.go ├── README.md └── ppdump.go /ppdump.out: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ppdump_test.go: -------------------------------------------------------------------------------- 1 | package ppdump 2 | 3 | import ( 4 | "os" 5 | "runtime/pprof" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestNewA(t *testing.T) { 13 | f, err := os.Create("./ppdump.out") 14 | require.NoError(t, err) 15 | defer f.Close() 16 | 17 | d := New(Config{ 18 | PollInterval: time.Second, 19 | Profiles: map[string]ProfileOpts{ 20 | "goroutine": { 21 | Threshold: 500, 22 | Action: func(p *pprof.Profile) { 23 | }, 24 | }, 25 | }, 26 | }) 27 | require.NoError(t, err) 28 | 29 | d.Start() 30 | doSomething() 31 | time.Sleep(time.Second * 5) 32 | d.Stop() 33 | } 34 | 35 | func doSomething() { 36 | for i := 0; i < 500; i++ { 37 | go doWork() 38 | } 39 | } 40 | 41 | func doWork() { 42 | time.Sleep(time.Second * 2) 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ppdump 2 | ======== 3 | set triggers for pprof dumps 4 | 5 | how to get 6 | ------------ 7 | 8 | go get github.com/i/ppdump 9 | 10 | how to do 11 | --------- 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | 20 | "github.com/i/ppdump" 21 | ) 22 | 23 | func main() { 24 | ppdump.Start(ppdump.Config{ 25 | PollInterval: time.Second, 26 | Throttle: time.Minute, 27 | Profiles: map[string]ppdump.ProfileOpts{ 28 | "goroutine": { 29 | Threshold: 10000, 30 | Action: dumpToDisk, 31 | }, 32 | }, 33 | }) 34 | 35 | // trigger a dump 36 | for i := 0; i < 501; i++ { 37 | go time.Sleep(time.Second * 5) 38 | } 39 | 40 | time.Sleep(time.Second) // give ppdump some time to check goroutines 41 | } 42 | 43 | func dumpToDisk(p *pprof.Profile) { 44 | f, err := os.Create(fmt.Sprintf("%s-%d.dump", p.Name(), time.Unix())) 45 | if err != nil { 46 | // handle err 47 | } 48 | defer f.Close() 49 | if _, err := p.WriteTo(f, 0); err != nil { 50 | // handle err 51 | } 52 | } 53 | 54 | ``` 55 | -------------------------------------------------------------------------------- /ppdump.go: -------------------------------------------------------------------------------- 1 | package ppdump 2 | 3 | import ( 4 | "runtime/pprof" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | const ( 10 | _defaultInterval = time.Second 11 | _defaultThrottle = time.Minute 12 | ) 13 | 14 | var std *Dumper 15 | 16 | // Config contains information required for creating a new Dumper. 17 | type Config struct { 18 | Profiles map[string]ProfileOpts // map of profile names to properties 19 | PollInterval time.Duration // how often to poll for goroutines. defaults to one second 20 | Throttle time.Duration // time to wait before writing another dump. defaults to one minute 21 | } 22 | 23 | // An ActionFunc is a function that will be called when a profile's threshold 24 | // has been exceeded. 25 | type ActionFunc func(profile *pprof.Profile) 26 | 27 | // ProfileOpts establishes behavior of when to trigger a dump and what to do 28 | // when that occurs. 29 | type ProfileOpts struct { 30 | Action ActionFunc 31 | Threshold int 32 | } 33 | 34 | // Start begins the profiling and dumping procedure using the top level dumper. 35 | func Start(c Config) { 36 | std = New(c) 37 | std.Start() 38 | } 39 | 40 | // Stop stops the profiling and dumping procedure 41 | func Stop() { 42 | if std != nil { 43 | std.Stop() 44 | } 45 | } 46 | 47 | // A Dumper dumps. 48 | // TODO actually describe what it does 49 | type Dumper struct { 50 | sync.Mutex 51 | 52 | interval time.Duration 53 | throttle time.Duration 54 | lim int 55 | thr float64 56 | avg float64 57 | nv int64 58 | profiles map[string]ProfileOpts 59 | stop chan struct{} 60 | lastDumps map[string]time.Time 61 | } 62 | 63 | // New returns a new Dumper 64 | func New(c Config) *Dumper { 65 | if c.PollInterval == 0 { 66 | c.PollInterval = _defaultInterval 67 | } 68 | if c.Throttle == 0 { 69 | c.Throttle = _defaultThrottle 70 | } 71 | return &Dumper{ 72 | interval: c.PollInterval, 73 | throttle: c.Throttle, 74 | profiles: c.Profiles, 75 | lastDumps: make(map[string]time.Time), 76 | } 77 | } 78 | 79 | // Start starts the routine of dumping. 80 | func (d *Dumper) Start() { 81 | d.Lock() 82 | defer d.Unlock() 83 | 84 | d.stop = make(chan struct{}) 85 | 86 | go d.runLoop() 87 | } 88 | 89 | func (d *Dumper) runLoop() { 90 | t := time.NewTicker(d.interval) 91 | for { 92 | select { 93 | case <-t.C: 94 | d.checkAndDump() 95 | case <-d.stop: 96 | return 97 | } 98 | } 99 | } 100 | 101 | func (d *Dumper) dump(p ProfileOpts, pp *pprof.Profile) { 102 | d.Lock() 103 | defer d.Unlock() 104 | 105 | now := time.Now() 106 | lastDump := d.lastDumps[pp.Name()] 107 | if now.Before(lastDump.Add(d.throttle)) { 108 | return 109 | } 110 | d.lastDumps[pp.Name()] = now 111 | if p.Action != nil { 112 | p.Action(pp) 113 | } 114 | } 115 | 116 | // Stop stops the runloop. No-op if called more than once. 117 | func (d *Dumper) Stop() { 118 | d.Lock() 119 | defer d.Unlock() 120 | defer func() { recover() }() // doesn't matter if the channel is closed twice 121 | close(d.stop) 122 | } 123 | 124 | func (d *Dumper) checkAndDump() { 125 | for name, p := range d.profiles { 126 | if profile := pprof.Lookup(name); profile != nil { 127 | if profile.Count() > p.Threshold { 128 | d.dump(p, profile) 129 | } 130 | } 131 | } 132 | } 133 | --------------------------------------------------------------------------------