303 lines
9.1 KiB
Go
303 lines
9.1 KiB
Go
// 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.
|
|
//
|
|
// See Go's net/http/pprof for docs.
|
|
//
|
|
// This is a fork of net/http/pprof that doesn't use init side effects
|
|
// and doesn't use html/template (which ends up calling
|
|
// reflect.Value.MethodByName, which disables some linker deadcode
|
|
// optimizations).
|
|
package pprof
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"html"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"runtime/trace"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func AddHandlers(mux *http.ServeMux) {
|
|
mux.HandleFunc("/debug/pprof/", Index)
|
|
mux.HandleFunc("/debug/pprof/cmdline", Cmdline)
|
|
mux.HandleFunc("/debug/pprof/profile", Profile)
|
|
mux.HandleFunc("/debug/pprof/symbol", Symbol)
|
|
mux.HandleFunc("/debug/pprof/trace", 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.Fprint(w, strings.Join(os.Args, "\x00"))
|
|
}
|
|
|
|
func sleep(w http.ResponseWriter, d time.Duration) {
|
|
var clientGone <-chan bool
|
|
//lint:ignore SA1019 CloseNotifier is deprecated but it functions and this is a temporary fork
|
|
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.
|
|
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
|
|
// 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, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
|
|
if sec <= 0 || err != nil {
|
|
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)
|
|
}
|
|
|
|
var profileDescriptions = map[string]string{
|
|
"allocs": "A sampling of all past memory allocations",
|
|