Black Lives Matter. Support the Equal Justice Initiative.

Source file src/net/http/pprof/pprof.go

Documentation: net/http/pprof

     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  //
     8  // The package is typically only imported for the side effect of
     9  // registering its HTTP handlers.
    10  // The handled paths all begin with /debug/pprof/.
    11  //
    12  // To use pprof, link this package into your program:
    13  //	import _ "net/http/pprof"
    14  //
    15  // If your application is not already running an http server, you
    16  // need to start one. Add "net/http" and "log" to your imports and
    17  // the following code to your main function:
    18  //
    19  // 	go func() {
    20  // 		log.Println(http.ListenAndServe("localhost:6060", nil))
    21  // 	}()
    22  //
    23  // If you are not using DefaultServeMux, you will have to register handlers
    24  // with the mux you are using.
    25  //
    26  // Then use the pprof tool to look at the heap profile:
    27  //
    28  //	go tool pprof http://localhost:6060/debug/pprof/heap
    29  //
    30  // Or to look at a 30-second CPU profile:
    31  //
    32  //	go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
    33  //
    34  // Or to look at the goroutine blocking profile, after calling
    35  // runtime.SetBlockProfileRate in your program:
    36  //
    37  //	go tool pprof http://localhost:6060/debug/pprof/block
    38  //
    39  // Or to look at the holders of contended mutexes, after calling
    40  // runtime.SetMutexProfileFraction in your program:
    41  //
    42  //	go tool pprof http://localhost:6060/debug/pprof/mutex
    43  //
    44  // The package also exports a handler that serves execution trace data
    45  // for the "go tool trace" command. To collect a 5-second execution trace:
    46  //
    47  //	wget -O trace.out http://localhost:6060/debug/pprof/trace?seconds=5
    48  //	go tool trace trace.out
    49  //
    50  // To view all available profiles, open http://localhost:6060/debug/pprof/
    51  // in your browser.
    52  //
    53  // For a study of the facility in action, visit
    54  //
    55  //	https://blog.golang.org/2011/06/profiling-go-programs.html
    56  //
    57  package pprof
    58  
    59  import (
    60  	"bufio"
    61  	"bytes"
    62  	"context"
    63  	"fmt"
    64  	"html/template"
    65  	"internal/profile"
    66  	"io"
    67  	"log"
    68  	"net/http"
    69  	"os"
    70  	"runtime"
    71  	"runtime/pprof"
    72  	"runtime/trace"
    73  	"sort"
    74  	"strconv"
    75  	"strings"
    76  	"time"
    77  )
    78  
    79  func init() {
    80  	http.HandleFunc("/debug/pprof/", Index)
    81  	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
    82  	http.HandleFunc("/debug/pprof/profile", Profile)
    83  	http.HandleFunc("/debug/pprof/symbol", Symbol)
    84  	http.HandleFunc("/debug/pprof/trace", Trace)
    85  }
    86  
    87  // Cmdline responds with the running program's
    88  // command line, with arguments separated by NUL bytes.
    89  // The package initialization registers it as /debug/pprof/cmdline.
    90  func Cmdline(w http.ResponseWriter, r *http.Request) {
    91  	w.Header().Set("X-Content-Type-Options", "nosniff")
    92  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    93  	fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
    94  }
    95  
    96  func sleep(w http.ResponseWriter, d time.Duration) {
    97  	var clientGone <-chan bool
    98  	if cn, ok := w.(http.CloseNotifier); ok {
    99  		clientGone = cn.CloseNotify()
   100  	}
   101  	select {
   102  	case <-time.After(d):
   103  	case <-clientGone:
   104  	}
   105  }
   106  
   107  func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
   108  	srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
   109  	return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
   110  }
   111  
   112  func serveError(w http.ResponseWriter, status int, txt string) {
   113  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   114  	w.Header().Set("X-Go-Pprof", "1")
   115  	w.Header().Del("Content-Disposition")
   116  	w.WriteHeader(status)
   117  	fmt.Fprintln(w, txt)
   118  }
   119  
   120  // Profile responds with the pprof-formatted cpu profile.
   121  // Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
   122  // The package initialization registers it as /debug/pprof/profile.
   123  func Profile(w http.ResponseWriter, r *http.Request) {
   124  	w.Header().Set("X-Content-Type-Options", "nosniff")
   125  	sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
   126  	if sec <= 0 || err != nil {
   127  		sec = 30
   128  	}
   129  
   130  	if durationExceedsWriteTimeout(r, float64(sec)) {
   131  		serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
   132  		return
   133  	}
   134  
   135  	// Set Content Type assuming StartCPUProfile will work,
   136  	// because if it does it starts writing.
   137  	w.Header().Set("Content-Type", "application/octet-stream")
   138  	w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
   139  	if err := pprof.StartCPUProfile(w); err != nil {
   140  		// StartCPUProfile failed, so no writes yet.
   141  		serveError(w, http.StatusInternalServerError,
   142  			fmt.Sprintf("Could not enable CPU profiling: %s", err))
   143  		return
   144  	}
   145  	sleep(w, time.Duration(sec)*time.Second)
   146  	pprof.StopCPUProfile()
   147  }
   148  
   149  // Trace responds with the execution trace in binary form.
   150  // Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.
   151  // The package initialization registers it as /debug/pprof/trace.
   152  func Trace(w http.ResponseWriter, r *http.Request) {
   153  	w.Header().Set("X-Content-Type-Options", "nosniff")
   154  	sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
   155  	if sec <= 0 || err != nil {
   156  		sec = 1
   157  	}
   158  
   159  	if durationExceedsWriteTimeout(r, sec) {
   160  		serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
   161  		return
   162  	}
   163  
   164  	// Set Content Type assuming trace.Start will work,
   165  	// because if it does it starts writing.
   166  	w.Header().Set("Content-Type", "application/octet-stream")
   167  	w.Header().Set("Content-Disposition", `attachment; filename="trace"`)
   168  	if err := trace.Start(w); err != nil {
   169  		// trace.Start failed, so no writes yet.
   170  		serveError(w, http.StatusInternalServerError,
   171  			fmt.Sprintf("Could not enable tracing: %s", err))
   172  		return
   173  	}
   174  	sleep(w, time.Duration(sec*float64(time.Second)))
   175  	trace.Stop()
   176  }
   177  
   178  // Symbol looks up the program counters listed in the request,
   179  // responding with a table mapping program counters to function names.
   180  // The package initialization registers it as /debug/pprof/symbol.
   181  func Symbol(w http.ResponseWriter, r *http.Request) {
   182  	w.Header().Set("X-Content-Type-Options", "nosniff")
   183  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   184  
   185  	// We have to read the whole POST body before
   186  	// writing any output. Buffer the output here.
   187  	var buf bytes.Buffer
   188  
   189  	// We don't know how many symbols we have, but we
   190  	// do have symbol information. Pprof only cares whether
   191  	// this number is 0 (no symbols available) or > 0.
   192  	fmt.Fprintf(&buf, "num_symbols: 1\n")
   193  
   194  	var b *bufio.Reader
   195  	if r.Method == "POST" {
   196  		b = bufio.NewReader(r.Body)
   197  	} else {
   198  		b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
   199  	}
   200  
   201  	for {
   202  		word, err := b.ReadSlice('+')
   203  		if err == nil {
   204  			word = word[0 : len(word)-1] // trim +
   205  		}
   206  		pc, _ := strconv.ParseUint(string(word), 0, 64)
   207  		if pc != 0 {
   208  			f := runtime.FuncForPC(uintptr(pc))
   209  			if f != nil {
   210  				fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
   211  			}
   212  		}
   213  
   214  		// Wait until here to check for err; the last
   215  		// symbol will have an err because it doesn't end in +.
   216  		if err != nil {
   217  			if err != io.EOF {
   218  				fmt.Fprintf(&buf, "reading request: %v\n", err)
   219  			}
   220  			break
   221  		}
   222  	}
   223  
   224  	w.Write(buf.Bytes())
   225  }
   226  
   227  // Handler returns an HTTP handler that serves the named profile.
   228  func Handler(name string) http.Handler {
   229  	return handler(name)
   230  }
   231  
   232  type handler string
   233  
   234  func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   235  	w.Header().Set("X-Content-Type-Options", "nosniff")
   236  	p := pprof.Lookup(string(name))
   237  	if p == nil {
   238  		serveError(w, http.StatusNotFound, "Unknown profile")
   239  		return
   240  	}
   241  	if sec := r.FormValue("seconds"); sec != "" {
   242  		name.serveDeltaProfile(w, r, p, sec)
   243  		return
   244  	}
   245  	gc, _ := strconv.Atoi(r.FormValue("gc"))
   246  	if name == "heap" && gc > 0 {
   247  		runtime.GC()
   248  	}
   249  	debug, _ := strconv.Atoi(r.FormValue("debug"))
   250  	if debug != 0 {
   251  		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   252  	} else {
   253  		w.Header().Set("Content-Type", "application/octet-stream")
   254  		w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
   255  	}
   256  	p.WriteTo(w, debug)
   257  }
   258  
   259  func (name handler) serveDeltaProfile(w http.ResponseWriter, r *http.Request, p *pprof.Profile, secStr string) {
   260  	sec, err := strconv.ParseInt(secStr, 10, 64)
   261  	if err != nil || sec <= 0 {
   262  		serveError(w, http.StatusBadRequest, `invalid value for "seconds" - must be a positive integer`)
   263  		return
   264  	}
   265  	if !profileSupportsDelta[name] {
   266  		serveError(w, http.StatusBadRequest, `"seconds" parameter is not supported for this profile type`)
   267  		return
   268  	}
   269  	// 'name' should be a key in profileSupportsDelta.
   270  	if durationExceedsWriteTimeout(r, float64(sec)) {
   271  		serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
   272  		return
   273  	}
   274  	debug, _ := strconv.Atoi(r.FormValue("debug"))
   275  	if debug != 0 {
   276  		serveError(w, http.StatusBadRequest, "seconds and debug params are incompatible")
   277  		return
   278  	}
   279  	p0, err := collectProfile(p)
   280  	if err != nil {
   281  		serveError(w, http.StatusInternalServerError, "failed to collect profile")
   282  		return
   283  	}
   284  
   285  	t := time.NewTimer(time.Duration(sec) * time.Second)
   286  	defer t.Stop()
   287  
   288  	select {
   289  	case <-r.Context().Done():
   290  		err := r.Context().Err()
   291  		if err == context.DeadlineExceeded {
   292  			serveError(w, http.StatusRequestTimeout, err.Error())
   293  		} else { // TODO: what's a good status code for cancelled requests? 400?
   294  			serveError(w, http.StatusInternalServerError, err.Error())
   295  		}
   296  		return
   297  	case <-t.C:
   298  	}
   299  
   300  	p1, err := collectProfile(p)
   301  	if err != nil {
   302  		serveError(w, http.StatusInternalServerError, "failed to collect profile")
   303  		return
   304  	}
   305  	ts := p1.TimeNanos
   306  	dur := p1.TimeNanos - p0.TimeNanos
   307  
   308  	p0.Scale(-1)
   309  
   310  	p1, err = profile.Merge([]*profile.Profile{p0, p1})
   311  	if err != nil {
   312  		serveError(w, http.StatusInternalServerError, "failed to compute delta")
   313  		return
   314  	}
   315  
   316  	p1.TimeNanos = ts // set since we don't know what profile.Merge set for TimeNanos.
   317  	p1.DurationNanos = dur
   318  
   319  	w.Header().Set("Content-Type", "application/octet-stream")
   320  	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s-delta"`, name))
   321  	p1.Write(w)
   322  }
   323  
   324  func collectProfile(p *pprof.Profile) (*profile.Profile, error) {
   325  	var buf bytes.Buffer
   326  	if err := p.WriteTo(&buf, 0); err != nil {
   327  		return nil, err
   328  	}
   329  	ts := time.Now().UnixNano()
   330  	p0, err := profile.Parse(&buf)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	p0.TimeNanos = ts
   335  	return p0, nil
   336  }
   337  
   338  var profileSupportsDelta = map[handler]bool{
   339  	"allocs":       true,
   340  	"block":        true,
   341  	"goroutine":    true,
   342  	"heap":         true,
   343  	"mutex":        true,
   344  	"threadcreate": true,
   345  }
   346  
   347  var profileDescriptions = map[string]string{
   348  	"allocs":       "A sampling of all past memory allocations",
   349  	"block":        "Stack traces that led to blocking on synchronization primitives",
   350  	"cmdline":      "The command line invocation of the current program",
   351  	"goroutine":    "Stack traces of all current goroutines",
   352  	"heap":         "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.",
   353  	"mutex":        "Stack traces of holders of contended mutexes",
   354  	"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.",
   355  	"threadcreate": "Stack traces that led to the creation of new OS threads",
   356  	"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.",
   357  }
   358  
   359  // Index responds with the pprof-formatted profile named by the request.
   360  // For example, "/debug/pprof/heap" serves the "heap" profile.
   361  // Index responds to a request for "/debug/pprof/" with an HTML page
   362  // listing the available profiles.
   363  func Index(w http.ResponseWriter, r *http.Request) {
   364  	if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
   365  		name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
   366  		if name != "" {
   367  			handler(name).ServeHTTP(w, r)
   368  			return
   369  		}
   370  	}
   371  
   372  	w.Header().Set("X-Content-Type-Options", "nosniff")
   373  	w.Header().Set("Content-Type", "text/html; charset=utf-8")
   374  
   375  	type profile struct {
   376  		Name  string
   377  		Href  string
   378  		Desc  string
   379  		Count int
   380  	}
   381  	var profiles []profile
   382  	for _, p := range pprof.Profiles() {
   383  		profiles = append(profiles, profile{
   384  			Name:  p.Name(),
   385  			Href:  p.Name() + "?debug=1",
   386  			Desc:  profileDescriptions[p.Name()],
   387  			Count: p.Count(),
   388  		})
   389  	}
   390  
   391  	// Adding other profiles exposed from within this package
   392  	for _, p := range []string{"cmdline", "profile", "trace"} {
   393  		profiles = append(profiles, profile{
   394  			Name: p,
   395  			Href: p,
   396  			Desc: profileDescriptions[p],
   397  		})
   398  	}
   399  
   400  	sort.Slice(profiles, func(i, j int) bool {
   401  		return profiles[i].Name < profiles[j].Name
   402  	})
   403  
   404  	if err := indexTmpl.Execute(w, profiles); err != nil {
   405  		log.Print(err)
   406  	}
   407  }
   408  
   409  var indexTmpl = template.Must(template.New("index").Parse(`<html>
   410  <head>
   411  <title>/debug/pprof/</title>
   412  <style>
   413  .profile-name{
   414  	display:inline-block;
   415  	width:6rem;
   416  }
   417  </style>
   418  </head>
   419  <body>
   420  /debug/pprof/<br>
   421  <br>
   422  Types of profiles available:
   423  <table>
   424  <thead><td>Count</td><td>Profile</td></thead>
   425  {{range .}}
   426  	<tr>
   427  	<td>{{.Count}}</td><td><a href={{.Href}}>{{.Name}}</a></td>
   428  	</tr>
   429  {{end}}
   430  </table>
   431  <a href="goroutine?debug=2">full goroutine stack dump</a>
   432  <br/>
   433  <p>
   434  Profile Descriptions:
   435  <ul>
   436  {{range .}}
   437  <li><div class=profile-name>{{.Name}}:</div> {{.Desc}}</li>
   438  {{end}}
   439  </ul>
   440  </p>
   441  </body>
   442  </html>
   443  `))
   444  

View as plain text