github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/grail/go/net/http/pprof/pprof.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache-2.0 3 // license that can be found in the LICENSE file. 4 5 package pprof 6 7 // Copyright 2010 The Go Authors. All rights reserved. 8 // Use of this source code is governed by a BSD-style 9 // license that can be found in the LICENSE file. 10 // 11 // The original for this file is found in net/http/pprof/pprof.go 12 // 13 // This version disables the automatic creation of the /debug/* URIs on import. 14 15 import ( 16 "bufio" 17 "bytes" 18 "fmt" 19 "html/template" 20 "io" 21 "net/http" 22 "os" 23 "runtime" 24 "runtime/pprof" 25 "runtime/trace" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/Schaudge/grailbase/log" 31 ) 32 33 // Cmdline responds with the running program's 34 // command line, with arguments separated by NUL bytes. 35 // The package initialization registers it as /debug/pprof/cmdline. 36 func Cmdline(w http.ResponseWriter, r *http.Request) { 37 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 38 fmt.Fprintf(w, strings.Join(os.Args, "\x00")) 39 } 40 41 func sleep(w http.ResponseWriter, d time.Duration) { 42 var clientGone <-chan bool 43 if cn, ok := w.(http.CloseNotifier); ok { 44 clientGone = cn.CloseNotify() 45 } 46 select { 47 case <-time.After(d): 48 case <-clientGone: 49 } 50 } 51 52 // Profile responds with the pprof-formatted cpu profile. 53 // The package initialization registers it as /debug/pprof/profile. 54 func Profile(w http.ResponseWriter, r *http.Request) { 55 sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) 56 if sec == 0 { 57 sec = 30 58 } 59 60 // Set Content Type assuming StartCPUProfile will work, 61 // because if it does it starts writing. 62 w.Header().Set("Content-Type", "application/octet-stream") 63 if err := pprof.StartCPUProfile(w); err != nil { 64 // StartCPUProfile failed, so no writes yet. 65 // Can change header back to text content 66 // and send error code. 67 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 68 w.WriteHeader(http.StatusInternalServerError) 69 fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err) 70 return 71 } 72 sleep(w, time.Duration(sec)*time.Second) 73 pprof.StopCPUProfile() 74 } 75 76 // Trace responds with the execution trace in binary form. 77 // Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified. 78 // The package initialization registers it as /debug/pprof/trace. 79 func Trace(w http.ResponseWriter, r *http.Request) { 80 sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64) 81 if sec <= 0 || err != nil { 82 sec = 1 83 } 84 85 // Set Content Type assuming trace.Start will work, 86 // because if it does it starts writing. 87 w.Header().Set("Content-Type", "application/octet-stream") 88 if err := trace.Start(w); err != nil { 89 // trace.Start failed, so no writes yet. 90 // Can change header back to text content and send error code. 91 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 92 w.WriteHeader(http.StatusInternalServerError) 93 fmt.Fprintf(w, "Could not enable tracing: %s\n", err) 94 return 95 } 96 sleep(w, time.Duration(sec*float64(time.Second))) 97 trace.Stop() 98 } 99 100 // Symbol looks up the program counters listed in the request, 101 // responding with a table mapping program counters to function names. 102 // The package initialization registers it as /debug/pprof/symbol. 103 func Symbol(w http.ResponseWriter, r *http.Request) { 104 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 105 106 // We have to read the whole POST body before 107 // writing any output. Buffer the output here. 108 var buf bytes.Buffer 109 110 // We don't know how many symbols we have, but we 111 // do have symbol information. Pprof only cares whether 112 // this number is 0 (no symbols available) or > 0. 113 fmt.Fprintf(&buf, "num_symbols: 1\n") 114 115 var b *bufio.Reader 116 if r.Method == "POST" { 117 b = bufio.NewReader(r.Body) 118 } else { 119 b = bufio.NewReader(strings.NewReader(r.URL.RawQuery)) 120 } 121 122 for { 123 word, err := b.ReadSlice('+') 124 if err == nil { 125 word = word[0 : len(word)-1] // trim + 126 } 127 pc, _ := strconv.ParseUint(string(word), 0, 64) 128 if pc != 0 { 129 f := runtime.FuncForPC(uintptr(pc)) 130 if f != nil { 131 fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name()) 132 } 133 } 134 135 // Wait until here to check for err; the last 136 // symbol will have an err because it doesn't end in +. 137 if err != nil { 138 if err != io.EOF { 139 fmt.Fprintf(&buf, "reading request: %v\n", err) 140 } 141 break 142 } 143 } 144 145 w.Write(buf.Bytes()) 146 } 147 148 // Handler returns an HTTP handler that serves the named profile. 149 func Handler(name string) http.Handler { 150 return handler(name) 151 } 152 153 type handler string 154 155 func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 156 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 157 debug, _ := strconv.Atoi(r.FormValue("debug")) 158 p := pprof.Lookup(string(name)) 159 if p == nil { 160 w.WriteHeader(404) 161 fmt.Fprintf(w, "Unknown profile: %s\n", name) 162 return 163 } 164 gc, _ := strconv.Atoi(r.FormValue("gc")) 165 if name == "heap" && gc > 0 { 166 runtime.GC() 167 } 168 p.WriteTo(w, debug) 169 return 170 } 171 172 // Index responds with the pprof-formatted profile named by the request. 173 // For example, "/debug/pprof/heap" serves the "heap" profile. 174 // Index responds to a request for "/debug/pprof/" with an HTML page 175 // listing the available profiles. 176 func Index(w http.ResponseWriter, r *http.Request) { 177 if strings.HasPrefix(r.URL.Path, "/debug/pprof/") { 178 name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/") 179 if name != "" { 180 handler(name).ServeHTTP(w, r) 181 return 182 } 183 } 184 185 profiles := pprof.Profiles() 186 if err := indexTmpl.Execute(w, profiles); err != nil { 187 log.Error.Print(err) 188 } 189 } 190 191 var indexTmpl = template.Must(template.New("index").Parse(`<html> 192 <head> 193 <title>/debug/pprof/</title> 194 </head> 195 <body> 196 /debug/pprof/<br> 197 <br> 198 profiles:<br> 199 <table> 200 {{range .}} 201 <tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1">{{.Name}}</a> 202 {{end}} 203 </table> 204 <br> 205 <a href="goroutine?debug=2">full goroutine stack dump</a><br> 206 </body> 207 </html> 208 `))