-
Notifications
You must be signed in to change notification settings - Fork 99
/
webstack.go
127 lines (119 loc) · 3.63 KB
/
webstack.go
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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
}