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