]>
Commit | Line | Data |
---|---|---|
7a938933 ILT |
1 | // Copyright 2010 The Go Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | // Package pprof serves via its HTTP server runtime profiling data | |
6 | // in the format expected by the pprof visualization tool. | |
7 | // For more information about pprof, see | |
8 | // http://code.google.com/p/google-perftools/. | |
9 | // | |
10 | // The package is typically only imported for the side effect of | |
11 | // registering its HTTP handlers. | |
12 | // The handled paths all begin with /debug/pprof/. | |
13 | // | |
14 | // To use pprof, link this package into your program: | |
cbb6491d | 15 | // import _ "net/http/pprof" |
7a938933 | 16 | // |
bd2e46c8 ILT |
17 | // If your application is not already running an http server, you |
18 | // need to start one. Add "net/http" and "log" to your imports and | |
19 | // the following code to your main function: | |
20 | // | |
21 | // go func() { | |
22 | // log.Println(http.ListenAndServe("localhost:6060", nil)) | |
23 | // }() | |
24 | // | |
7a938933 ILT |
25 | // Then use the pprof tool to look at the heap profile: |
26 | // | |
cbb6491d | 27 | // go tool pprof http://localhost:6060/debug/pprof/heap |
7a938933 | 28 | // |
f72f4169 ILT |
29 | // Or to look at a 30-second CPU profile: |
30 | // | |
cbb6491d ILT |
31 | // go tool pprof http://localhost:6060/debug/pprof/profile |
32 | // | |
4ccad563 ILT |
33 | // Or to look at the goroutine blocking profile: |
34 | // | |
35 | // go tool pprof http://localhost:6060/debug/pprof/block | |
36 | // | |
501699af | 37 | // Or to view all available profiles: |
cbb6491d | 38 | // |
501699af | 39 | // go tool pprof http://localhost:6060/debug/pprof/ |
cbb6491d ILT |
40 | // |
41 | // For a study of the facility in action, visit | |
42 | // | |
43 | // http://blog.golang.org/2011/06/profiling-go-programs.html | |
f72f4169 | 44 | // |
7a938933 ILT |
45 | package pprof |
46 | ||
47 | import ( | |
48 | "bufio" | |
9ff56c95 | 49 | "bytes" |
7a938933 | 50 | "fmt" |
501699af | 51 | "html/template" |
2fd401c8 | 52 | "io" |
501699af | 53 | "log" |
9c63abc9 | 54 | "net/http" |
7a938933 ILT |
55 | "os" |
56 | "runtime" | |
57 | "runtime/pprof" | |
58 | "strconv" | |
59 | "strings" | |
f72f4169 | 60 | "time" |
7a938933 ILT |
61 | ) |
62 | ||
63 | func init() { | |
501699af | 64 | http.Handle("/debug/pprof/", http.HandlerFunc(Index)) |
7a938933 | 65 | http.Handle("/debug/pprof/cmdline", http.HandlerFunc(Cmdline)) |
f72f4169 | 66 | http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile)) |
7a938933 ILT |
67 | http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol)) |
68 | } | |
69 | ||
70 | // Cmdline responds with the running program's | |
71 | // command line, with arguments separated by NUL bytes. | |
72 | // The package initialization registers it as /debug/pprof/cmdline. | |
73 | func Cmdline(w http.ResponseWriter, r *http.Request) { | |
f72f4169 | 74 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
7a938933 ILT |
75 | fmt.Fprintf(w, strings.Join(os.Args, "\x00")) |
76 | } | |
77 | ||
f72f4169 ILT |
78 | // Profile responds with the pprof-formatted cpu profile. |
79 | // The package initialization registers it as /debug/pprof/profile. | |
80 | func Profile(w http.ResponseWriter, r *http.Request) { | |
d5363590 | 81 | sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) |
f72f4169 ILT |
82 | if sec == 0 { |
83 | sec = 30 | |
84 | } | |
85 | ||
86 | // Set Content Type assuming StartCPUProfile will work, | |
87 | // because if it does it starts writing. | |
88 | w.Header().Set("Content-Type", "application/octet-stream") | |
89 | if err := pprof.StartCPUProfile(w); err != nil { | |
90 | // StartCPUProfile failed, so no writes yet. | |
91 | // Can change header back to text content | |
92 | // and send error code. | |
93 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
94 | w.WriteHeader(http.StatusInternalServerError) | |
95 | fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err) | |
96 | return | |
97 | } | |
7b1c3dd9 | 98 | time.Sleep(time.Duration(sec) * time.Second) |
f72f4169 ILT |
99 | pprof.StopCPUProfile() |
100 | } | |
101 | ||
7a938933 ILT |
102 | // Symbol looks up the program counters listed in the request, |
103 | // responding with a table mapping program counters to function names. | |
104 | // The package initialization registers it as /debug/pprof/symbol. | |
105 | func Symbol(w http.ResponseWriter, r *http.Request) { | |
f72f4169 | 106 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
7a938933 | 107 | |
9ff56c95 ILT |
108 | // We have to read the whole POST body before |
109 | // writing any output. Buffer the output here. | |
110 | var buf bytes.Buffer | |
111 | ||
7a938933 ILT |
112 | // We don't know how many symbols we have, but we |
113 | // do have symbol information. Pprof only cares whether | |
114 | // this number is 0 (no symbols available) or > 0. | |
9ff56c95 | 115 | fmt.Fprintf(&buf, "num_symbols: 1\n") |
7a938933 ILT |
116 | |
117 | var b *bufio.Reader | |
118 | if r.Method == "POST" { | |
119 | b = bufio.NewReader(r.Body) | |
120 | } else { | |
121 | b = bufio.NewReader(strings.NewReader(r.URL.RawQuery)) | |
122 | } | |
123 | ||
124 | for { | |
125 | word, err := b.ReadSlice('+') | |
126 | if err == nil { | |
127 | word = word[0 : len(word)-1] // trim + | |
128 | } | |
d5363590 | 129 | pc, _ := strconv.ParseUint(string(word), 0, 64) |
7a938933 ILT |
130 | if pc != 0 { |
131 | f := runtime.FuncForPC(uintptr(pc)) | |
132 | if f != nil { | |
9ff56c95 | 133 | fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name()) |
7a938933 ILT |
134 | } |
135 | } | |
136 | ||
137 | // Wait until here to check for err; the last | |
138 | // symbol will have an err because it doesn't end in +. | |
139 | if err != nil { | |
2fd401c8 | 140 | if err != io.EOF { |
9ff56c95 ILT |
141 | fmt.Fprintf(&buf, "reading request: %v\n", err) |
142 | } | |
7a938933 ILT |
143 | break |
144 | } | |
145 | } | |
9ff56c95 ILT |
146 | |
147 | w.Write(buf.Bytes()) | |
7a938933 | 148 | } |
501699af ILT |
149 | |
150 | // Handler returns an HTTP handler that serves the named profile. | |
151 | func Handler(name string) http.Handler { | |
152 | return handler(name) | |
153 | } | |
154 | ||
155 | type handler string | |
156 | ||
157 | func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
158 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
159 | debug, _ := strconv.Atoi(r.FormValue("debug")) | |
160 | p := pprof.Lookup(string(name)) | |
161 | if p == nil { | |
162 | w.WriteHeader(404) | |
163 | fmt.Fprintf(w, "Unknown profile: %s\n", name) | |
164 | return | |
165 | } | |
166 | p.WriteTo(w, debug) | |
167 | return | |
168 | } | |
169 | ||
170 | // Index responds with the pprof-formatted profile named by the request. | |
171 | // For example, "/debug/pprof/heap" serves the "heap" profile. | |
172 | // Index responds to a request for "/debug/pprof/" with an HTML page | |
173 | // listing the available profiles. | |
174 | func Index(w http.ResponseWriter, r *http.Request) { | |
175 | if strings.HasPrefix(r.URL.Path, "/debug/pprof/") { | |
176 | name := r.URL.Path[len("/debug/pprof/"):] | |
177 | if name != "" { | |
178 | handler(name).ServeHTTP(w, r) | |
179 | return | |
180 | } | |
181 | } | |
182 | ||
183 | profiles := pprof.Profiles() | |
184 | if err := indexTmpl.Execute(w, profiles); err != nil { | |
185 | log.Print(err) | |
186 | } | |
187 | } | |
188 | ||
189 | var indexTmpl = template.Must(template.New("index").Parse(`<html> | |
190 | <head> | |
191 | <title>/debug/pprof/</title> | |
192 | </head> | |
193 | /debug/pprof/<br> | |
194 | <br> | |
195 | <body> | |
196 | profiles:<br> | |
197 | <table> | |
198 | {{range .}} | |
199 | <tr><td align=right>{{.Count}}<td><a href="/debug/pprof/{{.Name}}?debug=1">{{.Name}}</a> | |
200 | {{end}} | |
201 | </table> | |
202 | <br> | |
203 | <a href="/debug/pprof/goroutine?debug=2">full goroutine stack dump</a><br> | |
204 | </body> | |
205 | </html> | |
206 | `)) |