Aucune description

webui.go 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package driver
  15. import (
  16. "bytes"
  17. "fmt"
  18. "html/template"
  19. "io"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "os"
  24. "os/exec"
  25. "regexp"
  26. "strings"
  27. "time"
  28. "github.com/google/pprof/internal/graph"
  29. "github.com/google/pprof/internal/plugin"
  30. "github.com/google/pprof/internal/report"
  31. "github.com/google/pprof/profile"
  32. )
  33. // webInterface holds the state needed for serving a browser based interface.
  34. type webInterface struct {
  35. prof *profile.Profile
  36. options *plugin.Options
  37. }
  38. // errorCatcher is a UI that captures errors for reporting to the browser.
  39. type errorCatcher struct {
  40. plugin.UI
  41. errors []string
  42. }
  43. func (ec *errorCatcher) PrintErr(args ...interface{}) {
  44. ec.errors = append(ec.errors, strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
  45. ec.UI.PrintErr(args...)
  46. }
  47. func serveWebInterface(port int, p *profile.Profile, o *plugin.Options) error {
  48. interactiveMode = true
  49. ui := &webInterface{
  50. prof: p,
  51. options: o,
  52. }
  53. // authorization wrapper
  54. wrap := o.HTTPWrapper
  55. if wrap == nil {
  56. // only allow requests from local host
  57. wrap = checkLocalHost
  58. }
  59. http.Handle("/", wrap(http.HandlerFunc(ui.dot)))
  60. http.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
  61. http.Handle("/weblist", wrap(http.HandlerFunc(ui.weblist)))
  62. go openBrowser(port, o)
  63. return http.ListenAndServe(fmt.Sprint(":", port), nil)
  64. }
  65. func openBrowser(port int, o *plugin.Options) {
  66. // Construct URL.
  67. u, _ := url.Parse(fmt.Sprint("http://localhost:", port))
  68. q := u.Query()
  69. for _, p := range []struct{ param, key string }{
  70. {"f", "focus"},
  71. {"s", "show"},
  72. {"i", "ignore"},
  73. {"h", "hide"},
  74. } {
  75. if v := pprofVariables[p.key].value; v != "" {
  76. q.Set(p.param, v)
  77. }
  78. }
  79. u.RawQuery = q.Encode()
  80. // Give server a little time to get ready.
  81. time.Sleep(time.Millisecond * 500)
  82. for _, b := range browsers() {
  83. args := strings.Split(b, " ")
  84. if len(args) == 0 {
  85. continue
  86. }
  87. viewer := exec.Command(args[0], append(args[1:], u.String())...)
  88. viewer.Stderr = os.Stderr
  89. if err := viewer.Start(); err == nil {
  90. return
  91. }
  92. }
  93. // No visualizer succeeded, so just print URL.
  94. o.UI.PrintErr(u.String())
  95. }
  96. func checkLocalHost(h http.Handler) http.Handler {
  97. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  98. host, _, err := net.SplitHostPort(req.RemoteAddr)
  99. if err != nil || ((host != "127.0.0.1") && (host != "::1")) {
  100. http.Error(w, "permission denied", http.StatusForbidden)
  101. return
  102. }
  103. h.ServeHTTP(w, req)
  104. })
  105. }
  106. // dot generates a web page containing an svg diagram.
  107. func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
  108. if req.URL.Path != "/" {
  109. http.NotFound(w, req)
  110. return
  111. }
  112. // Capture any error messages generated while generating a report.
  113. catcher := &errorCatcher{UI: ui.options.UI}
  114. options := *ui.options
  115. options.UI = catcher
  116. // Generate dot graph.
  117. args := []string{"svg"}
  118. vars := pprofVariables.makeCopy()
  119. vars["focus"].value = req.URL.Query().Get("f")
  120. vars["show"].value = req.URL.Query().Get("s")
  121. vars["ignore"].value = req.URL.Query().Get("i")
  122. vars["hide"].value = req.URL.Query().Get("h")
  123. _, rpt, err := generateRawReport(ui.prof, args, vars, &options)
  124. if err != nil {
  125. http.Error(w, err.Error(), http.StatusBadRequest)
  126. ui.options.UI.PrintErr(err)
  127. return
  128. }
  129. g, config := report.GetDOT(rpt)
  130. legend := config.Labels
  131. config.Labels = nil
  132. dot := &bytes.Buffer{}
  133. graph.ComposeDot(dot, g, &graph.DotAttributes{}, config)
  134. // Convert to svg.
  135. svg, err := dotToSvg(dot.Bytes())
  136. if err != nil {
  137. http.Error(w, "Could not execute dot; may need to install graphviz.",
  138. http.StatusNotImplemented)
  139. ui.options.UI.PrintErr("Failed to execute dot. Is Graphviz installed?\n", err)
  140. return
  141. }
  142. // Get regular expression for each node.
  143. nodes := []string{""}
  144. for _, n := range g.Nodes {
  145. nodes = append(nodes, regexp.QuoteMeta(n.Info.Name))
  146. }
  147. // Embed in html.
  148. file := getFromLegend(legend, "File: ", "unknown")
  149. profile := getFromLegend(legend, "Type: ", "unknown")
  150. data := struct {
  151. Title string
  152. Errors []string
  153. Svg template.HTML
  154. Legend []string
  155. Nodes []string
  156. }{
  157. Title: file + " " + profile,
  158. Errors: catcher.errors,
  159. Svg: template.HTML(string(svg)),
  160. Legend: legend,
  161. Nodes: nodes,
  162. }
  163. html := &bytes.Buffer{}
  164. if err := graphTemplate.Execute(html, data); err != nil {
  165. http.Error(w, "internal template error", http.StatusInternalServerError)
  166. ui.options.UI.PrintErr(err)
  167. return
  168. }
  169. w.Header().Set("Content-Type", "text/html")
  170. w.Write(html.Bytes())
  171. }
  172. func dotToSvg(dot []byte) ([]byte, error) {
  173. cmd := exec.Command("dot", "-Tsvg")
  174. out := &bytes.Buffer{}
  175. cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr
  176. if err := cmd.Run(); err != nil {
  177. return nil, err
  178. }
  179. // Fix dot bug related to unquoted amperands.
  180. svg := bytes.Replace(out.Bytes(), []byte("&;"), []byte("&;"), -1)
  181. // Cleanup for embedding by dropping stuff before the <svg> start.
  182. if pos := bytes.Index(svg, []byte("<svg")); pos >= 0 {
  183. svg = svg[pos:]
  184. }
  185. return svg, nil
  186. }
  187. // disasm generates a web page containing disassembly.
  188. func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
  189. ui.output(w, req, "disasm", "text/plain")
  190. }
  191. // weblist generates a web page containing disassembly.
  192. func (ui *webInterface) weblist(w http.ResponseWriter, req *http.Request) {
  193. ui.output(w, req, "weblist", "text/html")
  194. }
  195. // output generates a webpage that contains the output of the specified pprof cmd.
  196. func (ui *webInterface) output(w http.ResponseWriter, req *http.Request, cmd, ctype string) {
  197. focus := req.URL.Query().Get("f")
  198. if focus == "" {
  199. fmt.Fprintln(w, "no argument supplied for "+cmd)
  200. return
  201. }
  202. // Capture any error messages generated while generating a report.
  203. catcher := &errorCatcher{UI: ui.options.UI}
  204. options := *ui.options
  205. options.UI = catcher
  206. args := []string{cmd, focus}
  207. vars := pprofVariables.makeCopy()
  208. _, rpt, err := generateRawReport(ui.prof, args, vars, &options)
  209. if err != nil {
  210. http.Error(w, err.Error(), http.StatusBadRequest)
  211. ui.options.UI.PrintErr(err)
  212. return
  213. }
  214. out := &bytes.Buffer{}
  215. if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
  216. http.Error(w, err.Error(), http.StatusBadRequest)
  217. ui.options.UI.PrintErr(err)
  218. return
  219. }
  220. if len(catcher.errors) > 0 {
  221. w.Header().Set("Content-Type", "text/plain")
  222. for _, msg := range catcher.errors {
  223. fmt.Println(w, msg)
  224. }
  225. return
  226. }
  227. w.Header().Set("Content-Type", ctype)
  228. io.Copy(w, out)
  229. }
  230. // getFromLegend returns the suffix of an entry in legend that starts
  231. // with param. It returns def if no such entry is found.
  232. func getFromLegend(legend []string, param, def string) string {
  233. for _, s := range legend {
  234. if strings.HasPrefix(s, param) {
  235. return s[len(param):]
  236. }
  237. }
  238. return def
  239. }