-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathautopprof.go
172 lines (146 loc) · 4.31 KB
/
autopprof.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package autopprof provides a development-time
// library to collect pprof profiles from Go programs.
//
// This package is experimental and APIs may change.
package autopprof
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"os/signal"
"runtime"
"runtime/pprof"
"syscall"
"time"
)
// Profile represents a pprof profile.
type Profile interface {
Capture() (profile string, err error)
}
// CPUProfile captures the CPU profile.
type CPUProfile struct {
Duration time.Duration // 30 seconds by default
}
// TODO(jbd): Add CPU profiling rate as an option.
func (p CPUProfile) Capture() (string, error) {
dur := p.Duration
if dur == 0 {
dur = 30 * time.Second
}
f := newTemp()
if err := pprof.StartCPUProfile(f); err != nil {
return "", nil
}
time.Sleep(dur)
pprof.StopCPUProfile()
if err := f.Close(); err != nil {
return "", nil
}
return f.Name(), nil
}
// HeapProfile captures the heap profile.
type HeapProfile struct{}
func (p HeapProfile) Capture() (string, error) {
return captureProfile("heap")
}
// MutexProfile captures stack traces of holders of contended mutexes.
type MutexProfile struct{}
func (p MutexProfile) Capture() (string, error) {
return captureProfile("mutex")
}
// BlockProfile captures stack traces that led to blocking on synchronization primitives.
type BlockProfile struct {
// Rate is the fraction of goroutine blocking events that
// are reported in the blocking profile. The profiler aims to
// sample an average of one blocking event per rate nanoseconds spent blocked.
//
// If zero value is provided, it will include every blocking event
// in the profile.
Rate int
}
func (p BlockProfile) Capture() (string, error) {
if p.Rate > 0 {
runtime.SetBlockProfileRate(p.Rate)
}
return captureProfile("block")
}
// GoroutineProfile captures stack traces of all current goroutines.
type GoroutineProfile struct{}
func (p GoroutineProfile) Capture() (string, error) {
return captureProfile("goroutine")
}
// Threadcreate profile captures the stack traces that led to the creation of new OS threads.
type ThreadcreateProfile struct{}
func (p ThreadcreateProfile) Capture() (string, error) {
return captureProfile("threadcreate")
}
func captureProfile(name string) (string, error) {
f := newTemp()
if err := pprof.Lookup(name).WriteTo(f, 2); err != nil {
return "", nil
}
if err := f.Close(); err != nil {
return "", nil
}
return f.Name(), nil
}
// TODO(jbd): Add support for custom.
// Capture captures the given profiles at SIGINT
// and opens a browser with the collected profiles.
//
// Capture should be used in development-time
// and shouldn't be in production binaries.
func Capture(p Profile) {
// TODO(jbd): As a library, we shouldn't be in the
// business of signal handling. Provide a better way
// trigger the capture.
go capture(p)
}
func capture(p Profile) {
c := make(chan os.Signal, 1)
switch runtime.GOOS {
case "windows":
signal.Notify(c, os.Interrupt)
fmt.Println("Send interrupt (CTRL+BREAK) to the process to capture")
fmt.Printf("Use (taskkill /F /PID %d) to end process\n", os.Getpid())
default:
signal.Notify(c, syscall.SIGQUIT)
fmt.Println("Send SIGQUIT (CTRL+\\) to the process to capture...")
}
for {
<-c
log.Println("Starting to capture.")
profile, err := p.Capture()
if err != nil {
log.Printf("Cannot capture profile: %v", err)
}
// Open profile with pprof.
log.Printf("Starting go tool pprof %v", profile)
cmd := exec.Command("go", "tool", "pprof", "-http=:", profile)
if err := cmd.Run(); err != nil {
log.Printf("Cannot start pprof UI: %v", err)
}
}
}
func newTemp() (f *os.File) {
f, err := ioutil.TempFile("", "profile-")
if err != nil {
log.Fatalf("Cannot create new temp profile file: %v", err)
}
return f
}