github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/introspection/pprof/pprof.go (about) 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 is a fork of net/http/pprof modified to communicate 6 // over a unix socket. 7 // 8 // Changes from the original version 9 // 10 // - This fork does not automatically register itself with the default 11 // net/http ServeMux. 12 // - To start the pprof handler, see the Start method in socket.go. 13 // 14 // --------------------------------------------------------------- 15 // 16 // Package pprof serves via its HTTP server runtime profiling data 17 // in the format expected by the pprof visualization tool. 18 // For more information about pprof, see 19 // http://code.google.com/p/google-perftools/. 20 // 21 // The package is typically only imported for the side effect of 22 // registering its HTTP handlers. 23 // The handled paths all begin with /debug/pprof/. 24 // 25 // To use pprof, link this package into your program: 26 // import _ "net/http/pprof" 27 // 28 // If your application is not already running an http server, you 29 // need to start one. Add "net/http" and "log" to your imports and 30 // the following code to your main function: 31 // 32 // go func() { 33 // log.Println(http.ListenAndServe("localhost:6060", nil)) 34 // }() 35 // 36 // Then use the pprof tool to look at the heap profile: 37 // 38 // go tool pprof http://localhost:6060/debug/pprof/heap 39 // 40 // Or to look at a 30-second CPU profile: 41 // 42 // go tool pprof http://localhost:6060/debug/pprof/profile 43 // 44 // Or to look at the goroutine blocking profile: 45 // 46 // go tool pprof http://localhost:6060/debug/pprof/block 47 // 48 // Or to collect a 5-second execution trace: 49 // 50 // wget http://localhost:6060/debug/pprof/trace?seconds=5 51 // 52 // To view all available profiles, open http://localhost:6060/debug/pprof/ 53 // in your browser. 54 // 55 // For a study of the facility in action, visit 56 // 57 // https://blog.golang.org/2011/06/profiling-go-programs.html 58 // 59 package pprof 60 61 import ( 62 "bufio" 63 "bytes" 64 "fmt" 65 "html/template" 66 "io" 67 "log" 68 "net/http" 69 "os" 70 "runtime" 71 "runtime/pprof" 72 "runtime/trace" 73 "strconv" 74 "strings" 75 "time" 76 ) 77 78 // Cmdline responds with the running program's 79 // command line, with arguments separated by NUL bytes. 80 // The package initialization registers it as /debug/pprof/cmdline. 81 func Cmdline(w http.ResponseWriter, r *http.Request) { 82 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 83 fmt.Fprintf(w, strings.Join(os.Args, "\x00")) 84 } 85 86 func sleep(w http.ResponseWriter, d time.Duration) { 87 var clientGone <-chan bool 88 if cn, ok := w.(http.CloseNotifier); ok { 89 clientGone = cn.CloseNotify() 90 } 91 select { 92 case <-time.After(d): 93 case <-clientGone: 94 } 95 } 96 97 // Profile responds with the pprof-formatted cpu profile. 98 // The package initialization registers it as /debug/pprof/profile. 99 func Profile(w http.ResponseWriter, r *http.Request) { 100 sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) 101 if sec == 0 { 102 sec = 30 103 } 104 105 // Set Content Type assuming StartCPUProfile will work, 106 // because if it does it starts writing. 107 w.Header().Set("Content-Type", "application/octet-stream") 108 if err := pprof.StartCPUProfile(w); err != nil { 109 // StartCPUProfile failed, so no writes yet. 110 // Can change header back to text content 111 // and send error code. 112 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 113 w.WriteHeader(http.StatusInternalServerError) 114 fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err) 115 return 116 } 117 sleep(w, time.Duration(sec)*time.Second) 118 pprof.StopCPUProfile() 119 } 120 121 // Symbol looks up the program counters listed in the request, 122 // responding with a table mapping program counters to function names. 123 // The package initialization registers it as /debug/pprof/symbol. 124 func Symbol(w http.ResponseWriter, r *http.Request) { 125 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 126 127 // We have to read the whole POST body before 128 // writing any output. Buffer the output here. 129 var buf bytes.Buffer 130 131 // We don't know how many symbols we have, but we 132 // do have symbol information. Pprof only cares whether 133 // this number is 0 (no symbols available) or > 0. 134 fmt.Fprintf(&buf, "num_symbols: 1\n") 135 136 var b *bufio.Reader 137 if r.Method == "POST" { 138 b = bufio.NewReader(r.Body) 139 } else { 140 b = bufio.NewReader(strings.NewReader(r.URL.RawQuery)) 141 } 142 143 for { 144 word, err := b.ReadSlice('+') 145 if err == nil { 146 word = word[0 : len(word)-1] // trim + 147 } 148 pc, _ := strconv.ParseUint(string(word), 0, 64) 149 if pc != 0 { 150 f := runtime.FuncForPC(uintptr(pc)) 151 if f != nil { 152 fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name()) 153 } 154 } 155 156 // Wait until here to check for err; the last 157 // symbol will have an err because it doesn't end in +. 158 if err != nil { 159 if err != io.EOF { 160 fmt.Fprintf(&buf, "reading request: %v\n", err) 161 } 162 break 163 } 164 } 165 166 w.Write(buf.Bytes()) 167 } 168 169 func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool { 170 srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server) 171 return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds() 172 } 173 174 // Trace responds with the execution trace in binary form. 175 // Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified. 176 // The package initialization registers it as /debug/pprof/trace. 177 func Trace(w http.ResponseWriter, r *http.Request) { 178 sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64) 179 if sec <= 0 || err != nil { 180 sec = 1 181 } 182 183 if durationExceedsWriteTimeout(r, sec) { 184 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 185 w.Header().Set("X-Go-Pprof", "1") 186 w.WriteHeader(http.StatusBadRequest) 187 fmt.Fprintln(w, "profile duration exceeds server's WriteTimeout") 188 return 189 } 190 191 // Set Content Type assuming trace.Start will work, 192 // because if it does it starts writing. 193 w.Header().Set("Content-Type", "application/octet-stream") 194 if err := trace.Start(w); err != nil { 195 // trace.Start failed, so no writes yet. 196 // Can change header back to text content and send error code. 197 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 198 w.Header().Set("X-Go-Pprof", "1") 199 w.WriteHeader(http.StatusInternalServerError) 200 fmt.Fprintf(w, "Could not enable tracing: %s\n", err) 201 return 202 } 203 sleep(w, time.Duration(sec*float64(time.Second))) 204 trace.Stop() 205 } 206 207 // Handler returns an HTTP handler that serves the named profile. 208 func Handler(name string) http.Handler { 209 return handler(name) 210 } 211 212 type handler string 213 214 func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 215 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 216 debug, _ := strconv.Atoi(r.FormValue("debug")) 217 p := pprof.Lookup(string(name)) 218 if p == nil { 219 w.WriteHeader(404) 220 fmt.Fprintf(w, "Unknown profile: %s\n", name) 221 return 222 } 223 gc, _ := strconv.Atoi(r.FormValue("gc")) 224 if name == "heap" && gc > 0 { 225 runtime.GC() 226 } 227 p.WriteTo(w, debug) 228 return 229 } 230 231 // Index responds with the pprof-formatted profile named by the request. 232 // For example, "/debug/pprof/heap" serves the "heap" profile. 233 // Index responds to a request for "/debug/pprof/" with an HTML page 234 // listing the available profiles. 235 func Index(w http.ResponseWriter, r *http.Request) { 236 if strings.HasPrefix(r.URL.Path, "/debug/pprof/") { 237 name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/") 238 if name != "" { 239 handler(name).ServeHTTP(w, r) 240 return 241 } 242 } 243 244 profiles := pprof.Profiles() 245 if err := indexTmpl.Execute(w, profiles); err != nil { 246 log.Print(err) 247 } 248 } 249 250 var indexTmpl = template.Must(template.New("index").Parse(`<html> 251 <head> 252 <title>/debug/pprof/</title> 253 </head> 254 <body> 255 /debug/pprof/<br> 256 <br> 257 profiles:<br> 258 <table> 259 {{range .}} 260 <tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1">{{.Name}}</a> 261 {{end}} 262 </table> 263 <br> 264 <a href="goroutine?debug=2">full goroutine stack dump</a><br> 265 </body> 266 </html> 267 `))