// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package pprof serves via its HTTP server runtime profiling data // in the format expected by the pprof visualization tool. // // The package is typically only imported for the side effect of // registering its HTTP handlers. // The handled paths all begin with /debug/pprof/. // // To use pprof, link this package into your program: // import _ "net/http/pprof" // // If your application is not already running an http server, you // need to start one. Add "net/http" and "log" to your imports and // the following code to your main function: // // go func() { // log.Println(http.ListenAndServe("localhost:6060", nil)) // }() // // Then use the pprof tool to look at the heap profile: // // go tool pprof http://localhost:6060/debug/pprof/heap // // Or to look at a 30-second CPU profile: // // go tool pprof http://localhost:6060/debug/pprof/profile // // Or to look at the goroutine blocking profile, after calling // runtime.SetBlockProfileRate in your program: // // go tool pprof http://localhost:6060/debug/pprof/block // // Or to collect a 5-second execution trace: // // wget http://localhost:6060/debug/pprof/trace?seconds=5 // // Or to look at the holders of contended mutexes, after calling // runtime.SetMutexProfileFraction in your program: // // go tool pprof http://localhost:6060/debug/pprof/mutex // // To view all available profiles, open http://localhost:6060/debug/pprof/ // in your browser. // // For a study of the facility in action, visit // // https://blog.golang.org/2011/06/profiling-go-programs.html // package pprof import ( "bufio" "bytes" "fmt" "html/template" "io" "log" "net/http" "os" "runtime" "runtime/pprof" "runtime/trace" "strconv" "strings" "time" ) func init() { http.Handle("/debug/pprof/", http.HandlerFunc(Index)) http.Handle("/debug/pprof/cmdline", http.HandlerFunc(Cmdline)) http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile)) http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol)) http.Handle("/debug/pprof/trace", http.HandlerFunc(Trace)) } // Cmdline responds with the running program's // command line, with arguments separated by NUL bytes. // The package initialization registers it as /debug/pprof/cmdline. func Cmdline(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprintf(w, strings.Join(os.Args, "\x00")) } func sleep(w http.ResponseWriter, d time.Duration) { var clientGone <-chan bool if cn, ok := w.(http.CloseNotifier); ok { clientGone = cn.CloseNotify() } select { case <-time.After(d): case <-clientGone: } } func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool { srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server) return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds() } func serveError(w http.ResponseWriter, status int, txt string) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("X-Go-Pprof", "1") w.Header().Del("Content-Disposition") w.WriteHeader(status) fmt.Fprintln(w, txt) } // Profile responds with the pprof-formatted cpu profile. // The package initialization registers it as /debug/pprof/profile. func Profile(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) if sec == 0 { sec = 30 } if durationExceedsWriteTimeout(r, float64(sec)) { serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout") return } // Set Content Type assuming StartCPUProfile will work, // because if it does it starts writing. w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="profile"`) if err := pprof.StartCPUProfile(w); err != nil { // StartCPUProfile failed, so no writes yet. serveError(w, http.StatusInternalServerError, fmt.Sprintf("Could not enable CPU profiling: %s", err)) return } sleep(w, time.Duration(sec)*time.Second) pprof.StopCPUProfile() } // Trace responds with the execution trace in binary form. // Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified. // The package initialization registers it as /debug/pprof/trace. func Trace(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64) if sec <= 0 || err != nil { sec = 1 } if durationExceedsWriteTimeout(r, sec) { serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout") return } // Set Content Type assuming trace.Start will work, // because if it does it starts writing. w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="trace"`) if err := trace.Start(w); err != nil { // trace.Start failed, so no writes yet. serveError(w, http.StatusInternalServerError, fmt.Sprintf("Could not enable tracing: %s", err)) return } sleep(w, time.Duration(sec*float64(time.Second))) trace.Stop() } // Symbol looks up the program counters listed in the request, // responding with a table mapping program counters to function names. // The package initialization registers it as /debug/pprof/symbol. func Symbol(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Type", "text/plain; charset=utf-8") // We have to read the whole POST body before // writing any output. Buffer the output here. var buf bytes.Buffer // We don't know how many symbols we have, but we // do have symbol information. Pprof only cares whether // this number is 0 (no symbols available) or > 0. fmt.Fprintf(&buf, "num_symbols: 1\n") var b *bufio.Reader if r.Method == "POST" { b = bufio.NewReader(r.Body) } else { b = bufio.NewReader(strings.NewReader(r.URL.RawQuery)) } for { word, err := b.ReadSlice('+') if err == nil { word = word[0 : len(word)-1] // trim + } pc, _ := strconv.ParseUint(string(word), 0, 64) if pc != 0 { f := runtime.FuncForPC(uintptr(pc)) if f != nil { fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name()) } } // Wait until here to check for err; the last // symbol will have an err because it doesn't end in +. if err != nil { if err != io.EOF { fmt.Fprintf(&buf, "reading request: %v\n", err) } break } } w.Write(buf.Bytes()) } // Handler returns an HTTP handler that serves the named profile. func Handler(name string) http.Handler { return handler(name) } type handler string func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") p := pprof.Lookup(string(name)) if p == nil { serveError(w, http.StatusNotFound, "Unknown profile") return } gc, _ := strconv.Atoi(r.FormValue("gc")) if name == "heap" && gc > 0 { runtime.GC() } debug, _ := strconv.Atoi(r.FormValue("debug")) if debug != 0 { w.Header().Set("Content-Type", "text/plain; charset=utf-8") } else { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) } p.WriteTo(w, debug) } // Index responds with the pprof-formatted profile named by the request. // For example, "/debug/pprof/heap" serves the "heap" profile. // Index responds to a request for "/debug/pprof/" with an HTML page // listing the available profiles. func Index(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/debug/pprof/") { name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/") if name != "" { handler(name).ServeHTTP(w, r) return } } profiles := pprof.Profiles() if err := indexTmpl.Execute(w, profiles); err != nil { log.Print(err) } } var indexTmpl = template.Must(template.New("index").Parse(` /debug/pprof/ /debug/pprof/

profiles:
{{range .}}
{{.Count}}{{.Name}} {{end}}

full goroutine stack dump
`))