| // Copyright 2020 Marc-Antoine Ruel. All rights reserved. | |
| // Use of this source code is governed under the Apache License, Version 2.0 | |
| // that can be found in the LICENSE file. | |
| // Package webstack provides a http.HandlerFunc that serves a snapshot similar | |
| // to net/http/pprof.Index(). | |
| // | |
| // Contrary to net/http/pprof, the handler is not automatically registered. | |
| package webstack | |
| import ( | |
| "bytes" | |
| "io" | |
| "net/http" | |
| "runtime" | |
| "strconv" | |
| "github.com/maruel/panicparse/v2/stack" | |
| ) | |
| // SnapshotHandler implements http.HandlerFunc to returns a panicparse HTML | |
| // format for a snapshot of the current goroutines. | |
| // | |
| // For best results, compile the executable with optimization (-N) and inlining | |
| // (-l) disabled with -gcflags '-N -l'. | |
| // | |
| // Arguments are passed as form values. If you want to change the default, | |
| // override the form values in a wrapper as shown in the example. | |
| // | |
| // The implementation is designed to be reasonably fast, it currently does a | |
| // small amount of disk I/O only for file presence. | |
| // | |
| // It is a direct replacement for "/debug/pprof/goroutine?debug=2" handler in | |
| // net/http/pprof. | |
| // | |
| // augment: (default: 1) When set to 1, panicparse tries to find the sources on | |
| // disk to improve the display of arguments based on type information. This is | |
| // slower and should be avoided on high utilization server. | |
| // | |
| // maxmem: (default: 67108864) maximum amount of temporary memory to use to | |
| // generate a snapshot. In practice at least the double of this is used. | |
| // Minimum is 1048576. | |
| // | |
| // similarity: (default: "anypointer") Can be one of stack.Similarity value in | |
| // lowercase: "exactflags", "exactlines", "anypointer" or "anyvalue". | |
| func SnapshotHandler(w http.ResponseWriter, req *http.Request) { | |
| if req.Method != "GET" { | |
| http.Error(w, "invalid method", http.StatusMethodNotAllowed) | |
| return | |
| } | |
| maxmem := 64 << 20 | |
| if s := req.FormValue("maxmem"); s != "" { | |
| var err error | |
| if maxmem, err = strconv.Atoi(s); err != nil { | |
| http.Error(w, "invalid maxmem value", http.StatusBadRequest) | |
| return | |
| } | |
| } | |
| opts := stack.DefaultOpts() | |
| if s := req.FormValue("augment"); s != "" { | |
| v, err := strconv.Atoi(s) | |
| if err != nil || v < 0 || v > 1 { | |
| http.Error(w, "invalid augment value", http.StatusBadRequest) | |
| return | |
| } | |
| if v == 0 { | |
| opts.AnalyzeSources = false | |
| } | |
| } | |
| c, err := snapshot(maxmem, opts) | |
| if err != nil { | |
| http.Error(w, "failed to process the snapshot, try a larger maxmem value", http.StatusInternalServerError) | |
| return | |
| } | |
| var s stack.Similarity | |
| switch req.FormValue("similarity") { | |
| case "exactflags": | |
| s = stack.ExactFlags | |
| case "exactlines": | |
| s = stack.ExactLines | |
| case "anypointer", "": | |
| s = stack.AnyPointer | |
| case "anyvalue": | |
| s = stack.AnyValue | |
| default: | |
| http.Error(w, "invalid similarity value", http.StatusBadRequest) | |
| return | |
| } | |
| w.Header().Set("Content-Type", "text/html; charset=utf-8") | |
| _ = c.Aggregate(s).ToHTML(w, "") | |
| } | |
| // snapshot returns a Context based on the snapshot of the stacks of the | |
| // current process. | |
| func snapshot(maxmem int, opts *stack.Opts) (*stack.Snapshot, error) { | |
| // We don't know how big the buffer needs to be to collect all the | |
| // goroutines. Start with 1 MB and try a few times, doubling each time. Give | |
| // up and use a truncated trace if maxmem is not enough. | |
| buf := make([]byte, 1<<20) | |
| if maxmem < len(buf) { | |
| maxmem = len(buf) | |
| } | |
| for i := 0; ; i++ { | |
| n := runtime.Stack(buf, true) | |
| if n < len(buf) { | |
| buf = buf[:n] | |
| break | |
| } | |
| if len(buf) >= maxmem { | |
| break | |
| } | |
| l := len(buf) * 2 | |
| if l > maxmem { | |
| l = maxmem | |
| } | |
| buf = make([]byte, l) | |
| } | |
| s, _, err := stack.ScanSnapshot(bytes.NewReader(buf), io.Discard, opts) | |
| // That's expected. | |
| if err == io.EOF { | |
| err = nil | |
| } | |
| return s, err | |
| } |