debug.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package tsweb
  5. import (
  6. "expvar"
  7. "fmt"
  8. "html"
  9. "io"
  10. "net/http"
  11. "net/http/pprof"
  12. "net/url"
  13. "os"
  14. "runtime"
  15. "tailscale.com/version"
  16. )
  17. // DebugHandler is an http.Handler that serves a debugging "homepage",
  18. // and provides helpers to register more debug endpoints and reports.
  19. //
  20. // The rendered page consists of three sections: informational
  21. // key/value pairs, links to other pages, and additional
  22. // program-specific HTML. Callers can add to these sections using the
  23. // KV, URL and Section helpers respectively.
  24. //
  25. // Additionally, the Handle method offers a shorthand for correctly
  26. // registering debug handlers and cross-linking them from /debug/.
  27. type DebugHandler struct {
  28. mux *http.ServeMux // where this handler is registered
  29. kvs []func(io.Writer) // output one <li>...</li> each, see KV()
  30. urls []string // one <li>...</li> block with link each
  31. sections []func(io.Writer, *http.Request) // invoked in registration order prior to outputting </body>
  32. }
  33. // Debugger returns the DebugHandler registered on mux at /debug/,
  34. // creating it if necessary.
  35. func Debugger(mux *http.ServeMux) *DebugHandler {
  36. h, pat := mux.Handler(&http.Request{URL: &url.URL{Path: "/debug/"}})
  37. if d, ok := h.(*DebugHandler); ok && pat == "/debug/" {
  38. return d
  39. }
  40. ret := &DebugHandler{
  41. mux: mux,
  42. }
  43. mux.Handle("/debug/", ret)
  44. // Register this one directly on mux, rather than using
  45. // ret.URL/etc, as we don't need another line of output on the
  46. // index page. The /pprof/ index already covers it.
  47. mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
  48. ret.KVFunc("Uptime", func() interface{} { return Uptime() })
  49. ret.KV("Version", version.Long)
  50. ret.Handle("vars", "Metrics (Go)", expvar.Handler())
  51. ret.Handle("varz", "Metrics (Prometheus)", http.HandlerFunc(VarzHandler))
  52. ret.Handle("pprof/", "pprof", http.HandlerFunc(pprof.Index))
  53. ret.URL("/debug/pprof/goroutine?debug=1", "Goroutines (collapsed)")
  54. ret.URL("/debug/pprof/goroutine?debug=2", "Goroutines (full)")
  55. ret.Handle("gc", "force GC", http.HandlerFunc(gcHandler))
  56. hostname, err := os.Hostname()
  57. if err == nil {
  58. ret.KV("Machine", hostname)
  59. }
  60. return ret
  61. }
  62. // ServeHTTP implements http.Handler.
  63. func (d *DebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  64. if !AllowDebugAccess(r) {
  65. http.Error(w, "debug access denied", http.StatusForbidden)
  66. return
  67. }
  68. if r.URL.Path != "/debug/" {
  69. // Sub-handlers are handled by the parent mux directly.
  70. http.NotFound(w, r)
  71. return
  72. }
  73. f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
  74. f("<html><body><h1>%s debug</h1><ul>", version.CmdName())
  75. for _, kv := range d.kvs {
  76. kv(w)
  77. }
  78. for _, url := range d.urls {
  79. io.WriteString(w, url)
  80. }
  81. for _, section := range d.sections {
  82. section(w, r)
  83. }
  84. }
  85. // Handle registers handler at /debug/<slug> and creates a descriptive
  86. // entry in /debug/ for it.
  87. func (d *DebugHandler) Handle(slug, desc string, handler http.Handler) {
  88. href := "/debug/" + slug
  89. d.mux.Handle(href, Protected(handler))
  90. d.URL(href, desc)
  91. }
  92. // KV adds a key/value list item to /debug/.
  93. func (d *DebugHandler) KV(k string, v interface{}) {
  94. val := html.EscapeString(fmt.Sprintf("%v", v))
  95. d.kvs = append(d.kvs, func(w io.Writer) {
  96. fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
  97. })
  98. }
  99. // KVFunc adds a key/value list item to /debug/. v is called on every
  100. // render of /debug/.
  101. func (d *DebugHandler) KVFunc(k string, v func() interface{}) {
  102. d.kvs = append(d.kvs, func(w io.Writer) {
  103. val := html.EscapeString(fmt.Sprintf("%v", v()))
  104. fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
  105. })
  106. }
  107. // URL adds a URL and description list item to /debug/.
  108. func (d *DebugHandler) URL(url, desc string) {
  109. if desc != "" {
  110. desc = " (" + desc + ")"
  111. }
  112. d.urls = append(d.urls, fmt.Sprintf(`<li><a href="%s">%s</a>%s</li>`, url, url, html.EscapeString(desc)))
  113. }
  114. // Section invokes f on every render of /debug/ to add supplemental
  115. // HTML to the page body.
  116. func (d *DebugHandler) Section(f func(w io.Writer, r *http.Request)) {
  117. d.sections = append(d.sections, f)
  118. }
  119. func gcHandler(w http.ResponseWriter, r *http.Request) {
  120. w.Write([]byte("running GC...\n"))
  121. if f, ok := w.(http.Flusher); ok {
  122. f.Flush()
  123. }
  124. runtime.GC()
  125. w.Write([]byte("Done.\n"))
  126. }