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 package pprof
53
54 import (
55 "bufio"
56 "bytes"
57 "fmt"
58 "html/template"
59 "io"
60 "log"
61 "net/http"
62 "os"
63 "runtime"
64 "runtime/pprof"
65 "runtime/trace"
66 "strconv"
67 "strings"
68 "time"
69 )
70
71 func init() {
72 http.HandleFunc("/debug/pprof/", Index)
73 http.HandleFunc("/debug/pprof/cmdline", Cmdline)
74 http.HandleFunc("/debug/pprof/profile", Profile)
75 http.HandleFunc("/debug/pprof/symbol", Symbol)
76 http.HandleFunc("/debug/pprof/trace", Trace)
77 }
78
79
80
81
82 func Cmdline(w http.ResponseWriter, r *http.Request) {
83 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
84 fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
85 }
86
87 func sleep(w http.ResponseWriter, d time.Duration) {
88 var clientGone <-chan bool
89 if cn, ok := w.(http.CloseNotifier); ok {
90 clientGone = cn.CloseNotify()
91 }
92 select {
93 case <-time.After(d):
94 case <-clientGone:
95 }
96 }
97
98 func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
99 srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
100 return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
101 }
102
103
104
105 func Profile(w http.ResponseWriter, r *http.Request) {
106 sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
107 if sec == 0 {
108 sec = 30
109 }
110
111 if durationExceedsWriteTimeout(r, float64(sec)) {
112 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
113 w.Header().Set("X-Go-Pprof", "1")
114 w.WriteHeader(http.StatusBadRequest)
115 fmt.Fprintln(w, "profile duration exceeds server's WriteTimeout")
116 return
117 }
118
119
120
121 w.Header().Set("Content-Type", "application/octet-stream")
122 if err := pprof.StartCPUProfile(w); err != nil {
123
124
125
126 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
127 w.Header().Set("X-Go-Pprof", "1")
128 w.WriteHeader(http.StatusInternalServerError)
129 fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err)
130 return
131 }
132 sleep(w, time.Duration(sec)*time.Second)
133 pprof.StopCPUProfile()
134 }
135
136
137
138
139 func Trace(w http.ResponseWriter, r *http.Request) {
140 sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
141 if sec <= 0 || err != nil {
142 sec = 1
143 }
144
145 if durationExceedsWriteTimeout(r, sec) {
146 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
147 w.Header().Set("X-Go-Pprof", "1")
148 w.WriteHeader(http.StatusBadRequest)
149 fmt.Fprintln(w, "profile duration exceeds server's WriteTimeout")
150 return
151 }
152
153
154
155 w.Header().Set("Content-Type", "application/octet-stream")
156 if err := trace.Start(w); err != nil {
157
158
159 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
160 w.Header().Set("X-Go-Pprof", "1")
161 w.WriteHeader(http.StatusInternalServerError)
162 fmt.Fprintf(w, "Could not enable tracing: %s\n", err)
163 return
164 }
165 sleep(w, time.Duration(sec*float64(time.Second)))
166 trace.Stop()
167 }
168
169
170
171
172 func Symbol(w http.ResponseWriter, r *http.Request) {
173 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
174
175
176
177 var buf bytes.Buffer
178
179
180
181
182 fmt.Fprintf(&buf, "num_symbols: 1\n")
183
184 var b *bufio.Reader
185 if r.Method == "POST" {
186 b = bufio.NewReader(r.Body)
187 } else {
188 b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
189 }
190
191 for {
192 word, err := b.ReadSlice('+')
193 if err == nil {
194 word = word[0 : len(word)-1]
195 }
196 pc, _ := strconv.ParseUint(string(word), 0, 64)
197 if pc != 0 {
198 f := runtime.FuncForPC(uintptr(pc))
199 if f != nil {
200 fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
201 }
202 }
203
204
205
206 if err != nil {
207 if err != io.EOF {
208 fmt.Fprintf(&buf, "reading request: %v\n", err)
209 }
210 break
211 }
212 }
213
214 w.Write(buf.Bytes())
215 }
216
217
218 func Handler(name string) http.Handler {
219 return handler(name)
220 }
221
222 type handler string
223
224 func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
225 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
226 debug, _ := strconv.Atoi(r.FormValue("debug"))
227 p := pprof.Lookup(string(name))
228 if p == nil {
229 w.WriteHeader(404)
230 fmt.Fprintf(w, "Unknown profile: %s\n", name)
231 return
232 }
233 gc, _ := strconv.Atoi(r.FormValue("gc"))
234 if name == "heap" && gc > 0 {
235 runtime.GC()
236 }
237 p.WriteTo(w, debug)
238 }
239
240
241
242
243
244 func Index(w http.ResponseWriter, r *http.Request) {
245 if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
246 name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
247 if name != "" {
248 handler(name).ServeHTTP(w, r)
249 return
250 }
251 }
252
253 profiles := pprof.Profiles()
254 if err := indexTmpl.Execute(w, profiles); err != nil {
255 log.Print(err)
256 }
257 }
258
259 var indexTmpl = template.Must(template.New("index").Parse(`<html>
260 <head>
261 <title>/debug/pprof/</title>
262 </head>
263 <body>
264 /debug/pprof/<br>
265 <br>
266 profiles:<br>
267 <table>
268 {{range .}}
269 <tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1">{{.Name}}</a>
270 {{end}}
271 </table>
272 <br>
273 <a href="goroutine?debug=2">full goroutine stack dump</a><br>
274 </body>
275 </html>
276 `))
277
View as plain text