1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package pprof 72 73 import ( 74 "bufio" 75 "bytes" 76 "context" 77 "fmt" 78 "html" 79 "internal/godebug" 80 "internal/profile" 81 "io" 82 "log" 83 "net/http" 84 "net/url" 85 "os" 86 "runtime" 87 "runtime/pprof" 88 "runtime/trace" 89 "slices" 90 "strconv" 91 "strings" 92 "time" 93 ) 94 95 func init() { 96 prefix := "" 97 if godebug.New("httpmuxgo121").Value() != "1" { 98 prefix = "GET " 99 } 100 http.HandleFunc(prefix+"/debug/pprof/", Index) 101 http.HandleFunc(prefix+"/debug/pprof/cmdline", Cmdline) 102 http.HandleFunc(prefix+"/debug/pprof/profile", Profile) 103 http.HandleFunc(prefix+"/debug/pprof/symbol", Symbol) 104 http.HandleFunc(prefix+"/debug/pprof/trace", Trace) 105 } 106 107 108 109 110 func Cmdline(w http.ResponseWriter, r *http.Request) { 111 w.Header().Set("X-Content-Type-Options", "nosniff") 112 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 113 fmt.Fprint(w, strings.Join(os.Args, "\x00")) 114 } 115 116 func sleep(r *http.Request, d time.Duration) { 117 select { 118 case <-time.After(d): 119 case <-r.Context().Done(): 120 } 121 } 122 123 func configureWriteDeadline(w http.ResponseWriter, r *http.Request, seconds float64) { 124 srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server) 125 if ok && srv.WriteTimeout > 0 { 126 timeout := srv.WriteTimeout + time.Duration(seconds*float64(time.Second)) 127 128 rc := http.NewResponseController(w) 129 rc.SetWriteDeadline(time.Now().Add(timeout)) 130 } 131 } 132 133 func serveError(w http.ResponseWriter, status int, txt string) { 134 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 135 w.Header().Set("X-Go-Pprof", "1") 136 w.Header().Del("Content-Disposition") 137 w.WriteHeader(status) 138 fmt.Fprintln(w, txt) 139 } 140 141 142 143 144 func Profile(w http.ResponseWriter, r *http.Request) { 145 w.Header().Set("X-Content-Type-Options", "nosniff") 146 sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64) 147 if sec <= 0 || err != nil { 148 sec = 30 149 } 150 151 configureWriteDeadline(w, r, float64(sec)) 152 153 154 155 w.Header().Set("Content-Type", "application/octet-stream") 156 w.Header().Set("Content-Disposition", `attachment; filename="profile"`) 157 if err := pprof.StartCPUProfile(w); err != nil { 158 159 serveError(w, http.StatusInternalServerError, 160 fmt.Sprintf("Could not enable CPU profiling: %s", err)) 161 return 162 } 163 sleep(r, time.Duration(sec)*time.Second) 164 pprof.StopCPUProfile() 165 } 166 167 168 169 170 func Trace(w http.ResponseWriter, r *http.Request) { 171 w.Header().Set("X-Content-Type-Options", "nosniff") 172 sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64) 173 if sec <= 0 || err != nil { 174 sec = 1 175 } 176 177 configureWriteDeadline(w, r, sec) 178 179 180 181 w.Header().Set("Content-Type", "application/octet-stream") 182 w.Header().Set("Content-Disposition", `attachment; filename="trace"`) 183 if err := trace.Start(w); err != nil { 184 185 serveError(w, http.StatusInternalServerError, 186 fmt.Sprintf("Could not enable tracing: %s", err)) 187 return 188 } 189 sleep(r, time.Duration(sec*float64(time.Second))) 190 trace.Stop() 191 } 192 193 194 195 196 func Symbol(w http.ResponseWriter, r *http.Request) { 197 w.Header().Set("X-Content-Type-Options", "nosniff") 198 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 199 200 201 202 var buf bytes.Buffer 203 204 205 206 207 fmt.Fprintf(&buf, "num_symbols: 1\n") 208 209 var b *bufio.Reader 210 if r.Method == "POST" { 211 b = bufio.NewReader(r.Body) 212 } else { 213 b = bufio.NewReader(strings.NewReader(r.URL.RawQuery)) 214 } 215 216 for { 217 word, err := b.ReadSlice('+') 218 if err == nil { 219 word = word[0 : len(word)-1] 220 } 221 pc, _ := strconv.ParseUint(string(word), 0, 64) 222 if pc != 0 { 223 f := runtime.FuncForPC(uintptr(pc)) 224 if f != nil { 225 fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name()) 226 } 227 } 228 229 230 231 if err != nil { 232 if err != io.EOF { 233 fmt.Fprintf(&buf, "reading request: %v\n", err) 234 } 235 break 236 } 237 } 238 239 w.Write(buf.Bytes()) 240 } 241 242 243 244 func Handler(name string) http.Handler { 245 return handler(name) 246 } 247 248 type handler string 249 250 func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 251 w.Header().Set("X-Content-Type-Options", "nosniff") 252 p := pprof.Lookup(string(name)) 253 if p == nil { 254 serveError(w, http.StatusNotFound, "Unknown profile") 255 return 256 } 257 if sec := r.FormValue("seconds"); sec != "" { 258 name.serveDeltaProfile(w, r, p, sec) 259 return 260 } 261 gc, _ := strconv.Atoi(r.FormValue("gc")) 262 if name == "heap" && gc > 0 { 263 runtime.GC() 264 } 265 debug, _ := strconv.Atoi(r.FormValue("debug")) 266 if debug != 0 { 267 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 268 } else { 269 w.Header().Set("Content-Type", "application/octet-stream") 270 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) 271 } 272 p.WriteTo(w, debug) 273 } 274 275 func (name handler) serveDeltaProfile(w http.ResponseWriter, r *http.Request, p *pprof.Profile, secStr string) { 276 sec, err := strconv.ParseInt(secStr, 10, 64) 277 if err != nil || sec <= 0 { 278 serveError(w, http.StatusBadRequest, `invalid value for "seconds" - must be a positive integer`) 279 return 280 } 281 282 if !profileSupportsDelta[name] { 283 serveError(w, http.StatusBadRequest, `"seconds" parameter is not supported for this profile type`) 284 return 285 } 286 287 configureWriteDeadline(w, r, float64(sec)) 288 289 debug, _ := strconv.Atoi(r.FormValue("debug")) 290 if debug != 0 { 291 serveError(w, http.StatusBadRequest, "seconds and debug params are incompatible") 292 return 293 } 294 p0, err := collectProfile(p) 295 if err != nil { 296 serveError(w, http.StatusInternalServerError, "failed to collect profile") 297 return 298 } 299 300 t := time.NewTimer(time.Duration(sec) * time.Second) 301 defer t.Stop() 302 303 select { 304 case <-r.Context().Done(): 305 err := r.Context().Err() 306 if err == context.DeadlineExceeded { 307 serveError(w, http.StatusRequestTimeout, err.Error()) 308 } else { 309 serveError(w, http.StatusInternalServerError, err.Error()) 310 } 311 return 312 case <-t.C: 313 } 314 315 p1, err := collectProfile(p) 316 if err != nil { 317 serveError(w, http.StatusInternalServerError, "failed to collect profile") 318 return 319 } 320 ts := p1.TimeNanos 321 dur := p1.TimeNanos - p0.TimeNanos 322 323 p0.Scale(-1) 324 325 p1, err = profile.Merge([]*profile.Profile{p0, p1}) 326 if err != nil { 327 serveError(w, http.StatusInternalServerError, "failed to compute delta") 328 return 329 } 330 331 p1.TimeNanos = ts 332 p1.DurationNanos = dur 333 334 w.Header().Set("Content-Type", "application/octet-stream") 335 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s-delta"`, name)) 336 p1.Write(w) 337 } 338 339 func collectProfile(p *pprof.Profile) (*profile.Profile, error) { 340 var buf bytes.Buffer 341 if err := p.WriteTo(&buf, 0); err != nil { 342 return nil, err 343 } 344 ts := time.Now().UnixNano() 345 p0, err := profile.Parse(&buf) 346 if err != nil { 347 return nil, err 348 } 349 p0.TimeNanos = ts 350 return p0, nil 351 } 352 353 var profileSupportsDelta = map[handler]bool{ 354 "allocs": true, 355 "block": true, 356 "goroutine": true, 357 "heap": true, 358 "mutex": true, 359 "threadcreate": true, 360 } 361 362 var profileDescriptions = map[string]string{ 363 "allocs": "A sampling of all past memory allocations", 364 "block": "Stack traces that led to blocking on synchronization primitives", 365 "cmdline": "The command line invocation of the current program", 366 "goroutine": "Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.", 367 "heap": "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.", 368 "mutex": "Stack traces of holders of contended mutexes", 369 "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.", 370 "symbol": "Maps given program counters to function names. Counters can be specified in a GET raw query or POST body, multiple counters are separated by '+'.", 371 "threadcreate": "Stack traces that led to the creation of new OS threads", 372 "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.", 373 } 374 375 type profileEntry struct { 376 Name string 377 Href string 378 Desc string 379 Count int 380 } 381 382 383 384 385 386 func Index(w http.ResponseWriter, r *http.Request) { 387 if name, found := strings.CutPrefix(r.URL.Path, "/debug/pprof/"); found { 388 if name != "" { 389 handler(name).ServeHTTP(w, r) 390 return 391 } 392 } 393 394 w.Header().Set("X-Content-Type-Options", "nosniff") 395 w.Header().Set("Content-Type", "text/html; charset=utf-8") 396 397 var profiles []profileEntry 398 for _, p := range pprof.Profiles() { 399 profiles = append(profiles, profileEntry{ 400 Name: p.Name(), 401 Href: p.Name(), 402 Desc: profileDescriptions[p.Name()], 403 Count: p.Count(), 404 }) 405 } 406 407 408 for _, p := range []string{"cmdline", "profile", "symbol", "trace"} { 409 profiles = append(profiles, profileEntry{ 410 Name: p, 411 Href: p, 412 Desc: profileDescriptions[p], 413 }) 414 } 415 416 slices.SortFunc(profiles, func(a, b profileEntry) int { 417 return strings.Compare(a.Name, b.Name) 418 }) 419 420 if err := indexTmplExecute(w, profiles); err != nil { 421 log.Print(err) 422 } 423 } 424 425 func indexTmplExecute(w io.Writer, profiles []profileEntry) error { 426 var b bytes.Buffer 427 b.WriteString(`<html> 428 <head> 429 <title>/debug/pprof/</title> 430 <style> 431 .profile-name{ 432 display:inline-block; 433 width:6rem; 434 } 435 </style> 436 </head> 437 <body> 438 /debug/pprof/ 439 <br> 440 <p>Set debug=1 as a query parameter to export in legacy text format</p> 441 <br> 442 Types of profiles available: 443 <table> 444 <thead><td>Count</td><td>Profile</td></thead> 445 `) 446 447 for _, profile := range profiles { 448 link := &url.URL{Path: profile.Href, RawQuery: "debug=1"} 449 fmt.Fprintf(&b, "<tr><td>%d</td><td><a href='%s'>%s</a></td></tr>\n", profile.Count, link, html.EscapeString(profile.Name)) 450 } 451 452 b.WriteString(`</table> 453 <a href="goroutine?debug=2">full goroutine stack dump</a> 454 <br> 455 <p> 456 Profile Descriptions: 457 <ul> 458 `) 459 for _, profile := range profiles { 460 fmt.Fprintf(&b, "<li><div class=profile-name>%s: </div> %s</li>\n", html.EscapeString(profile.Name), html.EscapeString(profile.Desc)) 461 } 462 b.WriteString(`</ul> 463 </p> 464 </body> 465 </html>`) 466 467 _, err := w.Write(b.Bytes()) 468 return err 469 } 470
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4