Source file src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "bytes"
19 "fmt"
20 "html/template"
21 "net"
22 "net/http"
23 gourl "net/url"
24 "os"
25 "os/exec"
26 "strconv"
27 "strings"
28 "time"
29
30 "github.com/google/pprof/internal/graph"
31 "github.com/google/pprof/internal/plugin"
32 "github.com/google/pprof/internal/report"
33 "github.com/google/pprof/profile"
34 )
35
36
37 type webInterface struct {
38 prof *profile.Profile
39 options *plugin.Options
40 help map[string]string
41 templates *template.Template
42 }
43
44 func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface {
45 templates := template.New("templategroup")
46 addTemplates(templates)
47 report.AddSourceTemplates(templates)
48 return &webInterface{
49 prof: p,
50 options: opt,
51 help: make(map[string]string),
52 templates: templates,
53 }
54 }
55
56
57 const maxEntries = 50
58
59
60 type errorCatcher struct {
61 plugin.UI
62 errors []string
63 }
64
65 func (ec *errorCatcher) PrintErr(args ...interface{}) {
66 ec.errors = append(ec.errors, strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
67 ec.UI.PrintErr(args...)
68 }
69
70
71 type webArgs struct {
72 Title string
73 Errors []string
74 Total int64
75 SampleTypes []string
76 Legend []string
77 Help map[string]string
78 Nodes []string
79 HTMLBody template.HTML
80 TextBody string
81 Top []report.TextItem
82 FlameGraph template.JS
83 }
84
85 func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {
86 host, port, err := getHostAndPort(hostport)
87 if err != nil {
88 return err
89 }
90 interactiveMode = true
91 ui := makeWebInterface(p, o)
92 for n, c := range pprofCommands {
93 ui.help[n] = c.description
94 }
95 for n, v := range pprofVariables {
96 ui.help[n] = v.help
97 }
98 ui.help["details"] = "Show information about the profile and this view"
99 ui.help["graph"] = "Display profile as a directed graph"
100 ui.help["reset"] = "Show the entire profile"
101
102 server := o.HTTPServer
103 if server == nil {
104 server = defaultWebServer
105 }
106 args := &plugin.HTTPServerArgs{
107 Hostport: net.JoinHostPort(host, strconv.Itoa(port)),
108 Host: host,
109 Port: port,
110 Handlers: map[string]http.Handler{
111 "/": http.HandlerFunc(ui.dot),
112 "/top": http.HandlerFunc(ui.top),
113 "/disasm": http.HandlerFunc(ui.disasm),
114 "/source": http.HandlerFunc(ui.source),
115 "/peek": http.HandlerFunc(ui.peek),
116 "/flamegraph": http.HandlerFunc(ui.flamegraph),
117 },
118 }
119
120 url := "http://" + args.Hostport
121
122 o.UI.Print("Serving web UI on ", url)
123
124 if o.UI.WantBrowser() && !disableBrowser {
125 go openBrowser(url, o)
126 }
127 return server(args)
128 }
129
130 func getHostAndPort(hostport string) (string, int, error) {
131 host, portStr, err := net.SplitHostPort(hostport)
132 if err != nil {
133 return "", 0, fmt.Errorf("could not split http address: %v", err)
134 }
135 if host == "" {
136 host = "localhost"
137 }
138 var port int
139 if portStr == "" {
140 ln, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
141 if err != nil {
142 return "", 0, fmt.Errorf("could not generate random port: %v", err)
143 }
144 port = ln.Addr().(*net.TCPAddr).Port
145 err = ln.Close()
146 if err != nil {
147 return "", 0, fmt.Errorf("could not generate random port: %v", err)
148 }
149 } else {
150 port, err = strconv.Atoi(portStr)
151 if err != nil {
152 return "", 0, fmt.Errorf("invalid port number: %v", err)
153 }
154 }
155 return host, port, nil
156 }
157 func defaultWebServer(args *plugin.HTTPServerArgs) error {
158 ln, err := net.Listen("tcp", args.Hostport)
159 if err != nil {
160 return err
161 }
162 isLocal := isLocalhost(args.Host)
163 handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
164 if isLocal {
165
166 host, _, err := net.SplitHostPort(req.RemoteAddr)
167 if err != nil || !isLocalhost(host) {
168 http.Error(w, "permission denied", http.StatusForbidden)
169 return
170 }
171 }
172 h := args.Handlers[req.URL.Path]
173 if h == nil {
174
175 h = http.DefaultServeMux
176 }
177 h.ServeHTTP(w, req)
178 })
179
180
181
182
183
184 mux := http.NewServeMux()
185 mux.Handle("/ui/", http.StripPrefix("/ui", handler))
186 mux.Handle("/", redirectWithQuery("/ui"))
187 s := &http.Server{Handler: mux}
188 return s.Serve(ln)
189 }
190
191 func redirectWithQuery(path string) http.HandlerFunc {
192 return func(w http.ResponseWriter, r *http.Request) {
193 pathWithQuery := &gourl.URL{Path: path, RawQuery: r.URL.RawQuery}
194 http.Redirect(w, r, pathWithQuery.String(), http.StatusTemporaryRedirect)
195 }
196 }
197
198 func isLocalhost(host string) bool {
199 for _, v := range []string{"localhost", "127.0.0.1", "[::1]", "::1"} {
200 if host == v {
201 return true
202 }
203 }
204 return false
205 }
206
207 func openBrowser(url string, o *plugin.Options) {
208
209 u, _ := gourl.Parse(url)
210 q := u.Query()
211 for _, p := range []struct{ param, key string }{
212 {"f", "focus"},
213 {"s", "show"},
214 {"sf", "show_from"},
215 {"i", "ignore"},
216 {"h", "hide"},
217 {"si", "sample_index"},
218 } {
219 if v := pprofVariables[p.key].value; v != "" {
220 q.Set(p.param, v)
221 }
222 }
223 u.RawQuery = q.Encode()
224
225
226 time.Sleep(time.Millisecond * 500)
227
228 for _, b := range browsers() {
229 args := strings.Split(b, " ")
230 if len(args) == 0 {
231 continue
232 }
233 viewer := exec.Command(args[0], append(args[1:], u.String())...)
234 viewer.Stderr = os.Stderr
235 if err := viewer.Start(); err == nil {
236 return
237 }
238 }
239
240 o.UI.PrintErr(u.String())
241 }
242
243 func varsFromURL(u *gourl.URL) variables {
244 vars := pprofVariables.makeCopy()
245 vars["focus"].value = u.Query().Get("f")
246 vars["show"].value = u.Query().Get("s")
247 vars["show_from"].value = u.Query().Get("sf")
248 vars["ignore"].value = u.Query().Get("i")
249 vars["hide"].value = u.Query().Get("h")
250 vars["sample_index"].value = u.Query().Get("si")
251 return vars
252 }
253
254
255 func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
256 cmd []string, vars ...string) (*report.Report, []string) {
257 v := varsFromURL(req.URL)
258 for i := 0; i+1 < len(vars); i += 2 {
259 v[vars[i]].value = vars[i+1]
260 }
261 catcher := &errorCatcher{UI: ui.options.UI}
262 options := *ui.options
263 options.UI = catcher
264 _, rpt, err := generateRawReport(ui.prof, cmd, v, &options)
265 if err != nil {
266 http.Error(w, err.Error(), http.StatusBadRequest)
267 ui.options.UI.PrintErr(err)
268 return nil, nil
269 }
270 return rpt, catcher.errors
271 }
272
273
274 func (ui *webInterface) render(w http.ResponseWriter, tmpl string,
275 rpt *report.Report, errList, legend []string, data webArgs) {
276 file := getFromLegend(legend, "File: ", "unknown")
277 profile := getFromLegend(legend, "Type: ", "unknown")
278 data.Title = file + " " + profile
279 data.Errors = errList
280 data.Total = rpt.Total()
281 data.SampleTypes = sampleTypes(ui.prof)
282 data.Legend = legend
283 data.Help = ui.help
284 html := &bytes.Buffer{}
285 if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil {
286 http.Error(w, "internal template error", http.StatusInternalServerError)
287 ui.options.UI.PrintErr(err)
288 return
289 }
290 w.Header().Set("Content-Type", "text/html")
291 w.Write(html.Bytes())
292 }
293
294
295 func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
296 rpt, errList := ui.makeReport(w, req, []string{"svg"})
297 if rpt == nil {
298 return
299 }
300
301
302 g, config := report.GetDOT(rpt)
303 legend := config.Labels
304 config.Labels = nil
305 dot := &bytes.Buffer{}
306 graph.ComposeDot(dot, g, &graph.DotAttributes{}, config)
307
308
309 svg, err := dotToSvg(dot.Bytes())
310 if err != nil {
311 http.Error(w, "Could not execute dot; may need to install graphviz.",
312 http.StatusNotImplemented)
313 ui.options.UI.PrintErr("Failed to execute dot. Is Graphviz installed?\n", err)
314 return
315 }
316
317
318 nodes := []string{""}
319 for _, n := range g.Nodes {
320 nodes = append(nodes, n.Info.Name)
321 }
322
323 ui.render(w, "graph", rpt, errList, legend, webArgs{
324 HTMLBody: template.HTML(string(svg)),
325 Nodes: nodes,
326 })
327 }
328
329 func dotToSvg(dot []byte) ([]byte, error) {
330 cmd := exec.Command("dot", "-Tsvg")
331 out := &bytes.Buffer{}
332 cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr
333 if err := cmd.Run(); err != nil {
334 return nil, err
335 }
336
337
338 svg := bytes.Replace(out.Bytes(), []byte("&;"), []byte("&;"), -1)
339
340
341 if pos := bytes.Index(svg, []byte("<svg")); pos >= 0 {
342 svg = svg[pos:]
343 }
344 return svg, nil
345 }
346
347 func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
348 rpt, errList := ui.makeReport(w, req, []string{"top"}, "nodecount", "500")
349 if rpt == nil {
350 return
351 }
352 top, legend := report.TextItems(rpt)
353 var nodes []string
354 for _, item := range top {
355 nodes = append(nodes, item.Name)
356 }
357
358 ui.render(w, "top", rpt, errList, legend, webArgs{
359 Top: top,
360 Nodes: nodes,
361 })
362 }
363
364
365 func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
366 args := []string{"disasm", req.URL.Query().Get("f")}
367 rpt, errList := ui.makeReport(w, req, args)
368 if rpt == nil {
369 return
370 }
371
372 out := &bytes.Buffer{}
373 if err := report.PrintAssembly(out, rpt, ui.options.Obj, maxEntries); err != nil {
374 http.Error(w, err.Error(), http.StatusBadRequest)
375 ui.options.UI.PrintErr(err)
376 return
377 }
378
379 legend := report.ProfileLabels(rpt)
380 ui.render(w, "plaintext", rpt, errList, legend, webArgs{
381 TextBody: out.String(),
382 })
383
384 }
385
386
387
388 func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
389 args := []string{"weblist", req.URL.Query().Get("f")}
390 rpt, errList := ui.makeReport(w, req, args)
391 if rpt == nil {
392 return
393 }
394
395
396 var body bytes.Buffer
397 if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil {
398 http.Error(w, err.Error(), http.StatusBadRequest)
399 ui.options.UI.PrintErr(err)
400 return
401 }
402
403 legend := report.ProfileLabels(rpt)
404 ui.render(w, "sourcelisting", rpt, errList, legend, webArgs{
405 HTMLBody: template.HTML(body.String()),
406 })
407 }
408
409
410 func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
411 args := []string{"peek", req.URL.Query().Get("f")}
412 rpt, errList := ui.makeReport(w, req, args, "lines", "t")
413 if rpt == nil {
414 return
415 }
416
417 out := &bytes.Buffer{}
418 if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
419 http.Error(w, err.Error(), http.StatusBadRequest)
420 ui.options.UI.PrintErr(err)
421 return
422 }
423
424 legend := report.ProfileLabels(rpt)
425 ui.render(w, "plaintext", rpt, errList, legend, webArgs{
426 TextBody: out.String(),
427 })
428 }
429
430
431
432 func getFromLegend(legend []string, param, def string) string {
433 for _, s := range legend {
434 if strings.HasPrefix(s, param) {
435 return s[len(param):]
436 }
437 }
438 return def
439 }
440
View as plain text