-
Notifications
You must be signed in to change notification settings - Fork 99
/
debug.go
142 lines (128 loc) · 3.77 KB
/
debug.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package debug
import (
"html/template"
"io/ioutil"
"net"
"net/http"
nhpprof "net/http/pprof"
"net/url"
"runtime/pprof"
"strings"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/google/uuid"
"github.com/pkg/errors"
)
const debugPrefix = "/debug/"
func startDebugServer(addrPath string, logger log.Logger) (*http.Server, error) {
// Generate new (random) token to use for debug server auth
token, err := uuid.NewRandom()
if err != nil {
return nil, errors.Wrap(err, "generating debug token")
}
// Start the debug server
r := http.NewServeMux()
registerAuthHandler(token.String(), r, logger)
serv := http.Server{
Handler: r,
}
// Allow the OS to pick an open port. Not intended to be a security
// mechanism, only intended to ensure we don't try to bind to an
// already used port.
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
return nil, errors.Wrap(err, "opening socket")
}
go func() {
if err := serv.Serve(listener); err != nil && err != http.ErrServerClosed {
level.Info(logger).Log("msg", "debug server failed", "err", err)
}
}()
url := url.URL{
Scheme: "http",
Host: listener.Addr().String(),
Path: "/debug/",
RawQuery: "token=" + token.String(),
}
addr := url.String()
// Write the address to a file for easy access by users
if err := ioutil.WriteFile(addrPath, []byte(addr), 0600); err != nil {
return nil, errors.Wrap(err, "writing debug address")
}
level.Info(logger).Log(
"msg", "debug server started",
"addr", addr,
)
return &serv, nil
}
// The below handler code is adapted from MIT licensed github.com/e-dard/netbug
func handler(token string, logger log.Logger) http.HandlerFunc {
info := struct {
Profiles []*pprof.Profile
Token string
}{
Profiles: pprof.Profiles(),
Token: url.QueryEscape(token),
}
return func(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/")
switch name {
case "":
// Index page.
if err := indexTmpl.Execute(w, info); err != nil {
level.Info(logger).Log(
"msg", "error rendering debug template",
"err", err,
)
return
}
case "cmdline":
nhpprof.Cmdline(w, r)
case "profile":
nhpprof.Profile(w, r)
case "trace":
nhpprof.Trace(w, r)
case "symbol":
nhpprof.Symbol(w, r)
default:
// Provides access to all profiles under runtime/pprof
nhpprof.Handler(name).ServeHTTP(w, r)
}
}
}
func authHandler(token string, logger log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.FormValue("token") == token {
handler(token, logger).ServeHTTP(w, r)
} else {
http.Error(w, "Request must include valid token.", http.StatusUnauthorized)
}
}
}
func registerAuthHandler(token string, mux *http.ServeMux, logger log.Logger) {
mux.Handle(debugPrefix, http.StripPrefix(debugPrefix, http.HandlerFunc(authHandler(token, logger))))
}
var indexTmpl = template.Must(template.New("index").Parse(`<html>
<head>
<title>Debug Information</title>
</head>
<br>
<body>
Profiles:<br>
<table>
{{range .Profiles}}
<tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1&token={{$.Token}}">{{.Name}}</a>
{{end}}
<tr><td align=right><td><a href="profile?token={{.Token}}">CPU</a>
<tr><td align=right><td><a href="trace?seconds=5&token={{.Token}}">5-second trace</a>
<tr><td align=right><td><a href="trace?seconds=30&token={{.Token}}">30-second trace</a>
</table>
<br>
Debug information:<br>
<table>
<tr><td align=right><td><a href="cmdline?token={{.Token}}">cmdline</a>
<tr><td align=right><td><a href="symbol?token={{.Token}}">symbol</a>
<tr><td align=right><td><a href="goroutine?debug=2&token={{.Token}}">full goroutine stack dump</a><br>
<table>
</body>
</html>`))