Source file src/net/http/pprof/pprof.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 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 "sort"
67 "strconv"
68 "strings"
69 "time"
70 )
71
72 func init() {
73 http.HandleFunc("/debug/pprof/", Index)
74 http.HandleFunc("/debug/pprof/cmdline", Cmdline)
75 http.HandleFunc("/debug/pprof/profile", Profile)
76 http.HandleFunc("/debug/pprof/symbol", Symbol)
77 http.HandleFunc("/debug/pprof/trace", Trace)
78 }
79
80
81
82
83 func Cmdline(w http.ResponseWriter, r *http.Request) {
84 w.Header().Set("X-Content-Type-Options", "nosniff")
85 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
86 fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
87 }
88
89 func sleep(w http.ResponseWriter, d time.Duration) {
90 var clientGone <-chan bool
91 if cn, ok := w.(http.CloseNotifier); ok {
92 clientGone = cn.CloseNotify()
93 }
94 select {
95 case <-time.After(d):
96 case <-clientGone:
97 }
98 }
99
100 func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
101 srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
102 return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
103 }
104
105 func serveError(w http.ResponseWriter, status int, txt string) {
106 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
107 w.Header().Set("X-Go-Pprof", "1")
108 w.Header().Del("Content-Disposition")
109 w.WriteHeader(status)
110 fmt.Fprintln(w, txt)
111 }
112
113
114
115
116 func Profile(w http.ResponseWriter, r *http.Request) {
117 w.Header().Set("X-Content-Type-Options", "nosniff")
118 sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
119 if sec <= 0 || err != nil {
120 sec = 30
121 }
122
123 if durationExceedsWriteTimeout(r, float64(sec)) {
124 serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
125 return
126 }
127
128
129
130 w.Header().Set("Content-Type", "application/octet-stream")
131 w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
132 if err := pprof.StartCPUProfile(w); err != nil {
133
134 serveError(w, http.StatusInternalServerError,
135 fmt.Sprintf("Could not enable CPU profiling: %s", err))
136 return
137 }
138 sleep(w, time.Duration(sec)*time.Second)
139 pprof.StopCPUProfile()
140 }
141
142
143
144
145 func Trace(w http.ResponseWriter, r *http.Request) {
146 w.Header().Set("X-Content-Type-Options", "nosniff")
147 sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
148 if sec <= 0 || err != nil {
149 sec = 1
150 }
151
152 if durationExceedsWriteTimeout(r, sec) {
153 serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
154 return
155 }
156
157
158
159 w.Header().Set("Content-Type", "application/octet-stream")
160 w.Header().Set("Content-Disposition", `attachment; filename="trace"`)
161 if err := trace.Start(w); err != nil {
162
163 serveError(w, http.StatusInternalServerError,
164 fmt.Sprintf("Could not enable tracing: %s", err))
165 return
166 }
167 sleep(w, time.Duration(sec*float64(time.Second)))
168 trace.Stop()
169 }
170
171
172
173
174 func Symbol(w http.ResponseWriter, r *http.Request) {
175 w.Header().Set("X-Content-Type-Options", "nosniff")
176 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
177
178
179
180 var buf bytes.Buffer
181
182
183
184
185 fmt.Fprintf(&buf, "num_symbols: 1\n")
186
187 var b *bufio.Reader
188 if r.Method == "POST" {
189 b = bufio.NewReader(r.Body)
190 } else {
191 b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
192 }
193
194 for {
195 word, err := b.ReadSlice('+')
196 if err == nil {
197 word = word[0 : len(word)-1]
198 }
199 pc, _ := strconv.ParseUint(string(word), 0, 64)
200 if pc != 0 {
201 f := runtime.FuncForPC(uintptr(pc))
202 if f != nil {
203 fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
204 }
205 }
206
207
208
209 if err != nil {
210 if err != io.EOF {
211 fmt.Fprintf(&buf, "reading request: %v\n", err)
212 }
213 break
214 }
215 }
216
217 w.Write(buf.Bytes())
218 }
219
220
221 func Handler(name string) http.Handler {
222 return handler(name)
223 }
224
225 type handler string
226
227 func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
228 w.Header().Set("X-Content-Type-Options", "nosniff")
229 p := pprof.Lookup(string(name))
230 if p == nil {
231 serveError(w, http.StatusNotFound, "Unknown profile")
232 return
233 }
234 gc, _ := strconv.Atoi(r.FormValue("gc"))
235 if name == "heap" && gc > 0 {
236 runtime.GC()
237 }
238 debug, _ := strconv.Atoi(r.FormValue("debug"))
239 if debug != 0 {
240 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
241 } else {
242 w.Header().Set("Content-Type", "application/octet-stream")
243 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
244 }
245 p.WriteTo(w, debug)
246 }
247
248 var profileDescriptions = map[string]string{
249 "allocs": "A sampling of all past memory allocations",
250 "block": "Stack traces that led to blocking on synchronization primitives",
251 "cmdline": "The command line invocation of the current program",
252 "goroutine": "Stack traces of all current goroutines",
253 "heap": "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.",
254 "mutex": "Stack traces of holders of contended mutexes",
255 "profile": "CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.",
256 "threadcreate": "Stack traces that led to the creation of new OS threads",
257 "trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
258 }
259
260
261
262
263
264 func Index(w http.ResponseWriter, r *http.Request) {
265 if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
266 name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
267 if name != "" {
268 handler(name).ServeHTTP(w, r)
269 return
270 }
271 }
272
273 type profile struct {
274 Name string
275 Href string
276 Desc string
277 Count int
278 }
279 var profiles []profile
280 for _, p := range pprof.Profiles() {
281 profiles = append(profiles, profile{
282 Name: p.Name(),
283 Href: p.Name() + "?debug=1",
284 Desc: profileDescriptions[p.Name()],
285 Count: p.Count(),
286 })
287 }
288
289
290 for _, p := range []string{"cmdline", "profile", "trace"} {
291 profiles = append(profiles, profile{
292 Name: p,
293 Href: p,
294 Desc: profileDescriptions[p],
295 })
296 }
297
298 sort.Slice(profiles, func(i, j int) bool {
299 return profiles[i].Name < profiles[j].Name
300 })
301
302 if err := indexTmpl.Execute(w, profiles); err != nil {
303 log.Print(err)
304 }
305 }
306
307 var indexTmpl = template.Must(template.New("index").Parse(`<html>
308 <head>
309 <title>/debug/pprof/</title>
310 <style>
311 .profile-name{
312 display:inline-block;
313 width:6rem;
314 }
315 </style>
316 </head>
317 <body>
318 /debug/pprof/<br>
319 <br>
320 Types of profiles available:
321 <table>
322 <thead><td>Count</td><td>Profile</td></thead>
323 {{range .}}
324 <tr>
325 <td>{{.Count}}</td><td><a href={{.Href}}>{{.Name}}</a></td>
326 </tr>
327 {{end}}
328 </table>
329 <a href="goroutine?debug=2">full goroutine stack dump</a>
330 <br/>
331 <p>
332 Profile Descriptions:
333 <ul>
334 {{range .}}
335 <li><div class=profile-name>{{.Name}}:</div> {{.Desc}}</li>
336 {{end}}
337 </ul>
338 </p>
339 </body>
340 </html>
341 `))
342
View as plain text