tsweb.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package tsweb contains code used in various Tailscale webservers.
  4. package tsweb
  5. import (
  6. "bufio"
  7. "bytes"
  8. "context"
  9. "errors"
  10. "expvar"
  11. "fmt"
  12. "io"
  13. "net"
  14. "net/http"
  15. _ "net/http/pprof"
  16. "net/netip"
  17. "os"
  18. "path/filepath"
  19. "reflect"
  20. "runtime"
  21. "sort"
  22. "strconv"
  23. "strings"
  24. "sync"
  25. "time"
  26. "go4.org/mem"
  27. "tailscale.com/envknob"
  28. "tailscale.com/metrics"
  29. "tailscale.com/net/tsaddr"
  30. "tailscale.com/types/logger"
  31. "tailscale.com/util/vizerror"
  32. "tailscale.com/version"
  33. )
  34. func init() {
  35. expvar.Publish("process_start_unix_time", expvar.Func(func() any { return timeStart.Unix() }))
  36. expvar.Publish("version", expvar.Func(func() any { return version.Long() }))
  37. expvar.Publish("go_version", expvar.Func(func() any { return runtime.Version() }))
  38. expvar.Publish("counter_uptime_sec", expvar.Func(func() any { return int64(Uptime().Seconds()) }))
  39. expvar.Publish("gauge_goroutines", expvar.Func(func() any { return runtime.NumGoroutine() }))
  40. }
  41. const (
  42. gaugePrefix = "gauge_"
  43. counterPrefix = "counter_"
  44. labelMapPrefix = "labelmap_"
  45. )
  46. // prefixesToTrim contains key prefixes to remove when exporting and sorting metrics.
  47. var prefixesToTrim = []string{gaugePrefix, counterPrefix, labelMapPrefix}
  48. // DevMode controls whether extra output in shown, for when the binary is being run in dev mode.
  49. var DevMode bool
  50. func DefaultCertDir(leafDir string) string {
  51. cacheDir, err := os.UserCacheDir()
  52. if err == nil {
  53. return filepath.Join(cacheDir, "tailscale", leafDir)
  54. }
  55. return ""
  56. }
  57. // IsProd443 reports whether addr is a Go listen address for port 443.
  58. func IsProd443(addr string) bool {
  59. _, port, _ := net.SplitHostPort(addr)
  60. return port == "443" || port == "https"
  61. }
  62. // AllowDebugAccess reports whether r should be permitted to access
  63. // various debug endpoints.
  64. func AllowDebugAccess(r *http.Request) bool {
  65. if r.Header.Get("X-Forwarded-For") != "" {
  66. // TODO if/when needed. For now, conservative:
  67. return false
  68. }
  69. ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
  70. if err != nil {
  71. return false
  72. }
  73. ip, err := netip.ParseAddr(ipStr)
  74. if err != nil {
  75. return false
  76. }
  77. if tsaddr.IsTailscaleIP(ip) || ip.IsLoopback() || ipStr == envknob.String("TS_ALLOW_DEBUG_IP") {
  78. return true
  79. }
  80. if r.Method == "GET" {
  81. urlKey := r.FormValue("debugkey")
  82. keyPath := envknob.String("TS_DEBUG_KEY_PATH")
  83. if urlKey != "" && keyPath != "" {
  84. slurp, err := os.ReadFile(keyPath)
  85. if err == nil && string(bytes.TrimSpace(slurp)) == urlKey {
  86. return true
  87. }
  88. }
  89. }
  90. return false
  91. }
  92. // AcceptsEncoding reports whether r accepts the named encoding
  93. // ("gzip", "br", etc).
  94. func AcceptsEncoding(r *http.Request, enc string) bool {
  95. h := r.Header.Get("Accept-Encoding")
  96. if h == "" {
  97. return false
  98. }
  99. if !strings.Contains(h, enc) && !mem.ContainsFold(mem.S(h), mem.S(enc)) {
  100. return false
  101. }
  102. remain := h
  103. for len(remain) > 0 {
  104. var part string
  105. part, remain, _ = strings.Cut(remain, ",")
  106. part = strings.TrimSpace(part)
  107. part, _, _ = strings.Cut(part, ";")
  108. if part == enc {
  109. return true
  110. }
  111. }
  112. return false
  113. }
  114. // Protected wraps a provided debug handler, h, returning a Handler
  115. // that enforces AllowDebugAccess and returns forbidden replies for
  116. // unauthorized requests.
  117. func Protected(h http.Handler) http.Handler {
  118. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  119. if !AllowDebugAccess(r) {
  120. msg := "debug access denied"
  121. if DevMode {
  122. ipStr, _, _ := net.SplitHostPort(r.RemoteAddr)
  123. msg += fmt.Sprintf("; to permit access, set TS_ALLOW_DEBUG_IP=%v", ipStr)
  124. }
  125. http.Error(w, msg, http.StatusForbidden)
  126. return
  127. }
  128. h.ServeHTTP(w, r)
  129. })
  130. }
  131. var timeStart = time.Now()
  132. func Uptime() time.Duration { return time.Since(timeStart).Round(time.Second) }
  133. // Port80Handler is the handler to be given to
  134. // autocert.Manager.HTTPHandler. The inner handler is the mux
  135. // returned by NewMux containing registered /debug handlers.
  136. type Port80Handler struct {
  137. Main http.Handler
  138. // FQDN is used to redirect incoming requests to https://<FQDN>.
  139. // If it is not set, the hostname is calculated from the incoming
  140. // request.
  141. FQDN string
  142. }
  143. func (h Port80Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  144. path := r.RequestURI
  145. if path == "/debug" || strings.HasPrefix(path, "/debug") {
  146. h.Main.ServeHTTP(w, r)
  147. return
  148. }
  149. if r.Method != "GET" && r.Method != "HEAD" {
  150. http.Error(w, "Use HTTPS", http.StatusBadRequest)
  151. return
  152. }
  153. if path == "/" && AllowDebugAccess(r) {
  154. // Redirect authorized user to the debug handler.
  155. path = "/debug/"
  156. }
  157. host := h.FQDN
  158. if host == "" {
  159. host = r.Host
  160. }
  161. target := "https://" + host + path
  162. http.Redirect(w, r, target, http.StatusFound)
  163. }
  164. // ReturnHandler is like net/http.Handler, but the handler can return an
  165. // error instead of writing to its ResponseWriter.
  166. type ReturnHandler interface {
  167. // ServeHTTPReturn is like http.Handler.ServeHTTP, except that
  168. // it can choose to return an error instead of writing to its
  169. // http.ResponseWriter.
  170. //
  171. // If ServeHTTPReturn returns an error, it caller should handle
  172. // an error by serving an HTTP 500 response to the user. The
  173. // error details should not be sent to the client, as they may
  174. // contain sensitive information. If the error is an
  175. // HTTPError, though, callers should use the HTTP response
  176. // code and message as the response to the client.
  177. ServeHTTPReturn(http.ResponseWriter, *http.Request) error
  178. }
  179. type HandlerOptions struct {
  180. QuietLoggingIfSuccessful bool // if set, do not log successfully handled HTTP requests (200 and 304 status codes)
  181. Logf logger.Logf
  182. Now func() time.Time // if nil, defaults to time.Now
  183. // If non-nil, StatusCodeCounters maintains counters
  184. // of status codes for handled responses.
  185. // The keys are "1xx", "2xx", "3xx", "4xx", and "5xx".
  186. StatusCodeCounters *expvar.Map
  187. // If non-nil, StatusCodeCountersFull maintains counters of status
  188. // codes for handled responses.
  189. // The keys are HTTP numeric response codes e.g. 200, 404, ...
  190. StatusCodeCountersFull *expvar.Map
  191. // OnError is called if the handler returned a HTTPError. This
  192. // is intended to be used to present pretty error pages if
  193. // the user agent is determined to be a browser.
  194. OnError ErrorHandlerFunc
  195. }
  196. // ErrorHandlerFunc is called to present a error response.
  197. type ErrorHandlerFunc func(http.ResponseWriter, *http.Request, HTTPError)
  198. // ReturnHandlerFunc is an adapter to allow the use of ordinary
  199. // functions as ReturnHandlers. If f is a function with the
  200. // appropriate signature, ReturnHandlerFunc(f) is a ReturnHandler that
  201. // calls f.
  202. type ReturnHandlerFunc func(http.ResponseWriter, *http.Request) error
  203. // ServeHTTPReturn calls f(w, r).
  204. func (f ReturnHandlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request) error {
  205. return f(w, r)
  206. }
  207. // StdHandler converts a ReturnHandler into a standard http.Handler.
  208. // Handled requests are logged using opts.Logf, as are any errors.
  209. // Errors are handled as specified by the Handler interface.
  210. func StdHandler(h ReturnHandler, opts HandlerOptions) http.Handler {
  211. if opts.Now == nil {
  212. opts.Now = time.Now
  213. }
  214. if opts.Logf == nil {
  215. opts.Logf = logger.Discard
  216. }
  217. return retHandler{h, opts}
  218. }
  219. // retHandler is an http.Handler that wraps a Handler and handles errors.
  220. type retHandler struct {
  221. rh ReturnHandler
  222. opts HandlerOptions
  223. }
  224. // ServeHTTP implements the http.Handler interface.
  225. func (h retHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  226. msg := AccessLogRecord{
  227. When: h.opts.Now(),
  228. RemoteAddr: r.RemoteAddr,
  229. Proto: r.Proto,
  230. TLS: r.TLS != nil,
  231. Host: r.Host,
  232. Method: r.Method,
  233. RequestURI: r.URL.RequestURI(),
  234. UserAgent: r.UserAgent(),
  235. Referer: r.Referer(),
  236. }
  237. lw := &loggingResponseWriter{ResponseWriter: w, logf: h.opts.Logf}
  238. err := h.rh.ServeHTTPReturn(lw, r)
  239. var hErr HTTPError
  240. var hErrOK bool
  241. if errors.As(err, &hErr) {
  242. hErrOK = true
  243. } else if vizErr, ok := vizerror.As(err); ok {
  244. hErrOK = true
  245. hErr = HTTPError{Msg: vizErr.Error()}
  246. }
  247. if lw.code == 0 && err == nil && !lw.hijacked {
  248. // If the handler didn't write and didn't send a header, that still means 200.
  249. // (See https://play.golang.org/p/4P7nx_Tap7p)
  250. lw.code = 200
  251. }
  252. msg.Seconds = h.opts.Now().Sub(msg.When).Seconds()
  253. msg.Code = lw.code
  254. msg.Bytes = lw.bytes
  255. switch {
  256. case lw.hijacked:
  257. // Connection no longer belongs to us, just log that we
  258. // switched protocols away from HTTP.
  259. if msg.Code == 0 {
  260. msg.Code = http.StatusSwitchingProtocols
  261. }
  262. case err != nil && r.Context().Err() == context.Canceled:
  263. msg.Code = 499 // nginx convention: Client Closed Request
  264. msg.Err = context.Canceled.Error()
  265. case hErrOK:
  266. // Handler asked us to send an error. Do so, if we haven't
  267. // already sent a response.
  268. msg.Err = hErr.Msg
  269. if hErr.Err != nil {
  270. if msg.Err == "" {
  271. msg.Err = hErr.Err.Error()
  272. } else {
  273. msg.Err = msg.Err + ": " + hErr.Err.Error()
  274. }
  275. }
  276. if lw.code != 0 {
  277. h.opts.Logf("[unexpected] handler returned HTTPError %v, but already sent a response with code %d", hErr, lw.code)
  278. break
  279. }
  280. msg.Code = hErr.Code
  281. if msg.Code == 0 {
  282. h.opts.Logf("[unexpected] HTTPError %v did not contain an HTTP status code, sending internal server error", hErr)
  283. msg.Code = http.StatusInternalServerError
  284. }
  285. if h.opts.OnError != nil {
  286. h.opts.OnError(lw, r, hErr)
  287. } else {
  288. // Default headers set by http.Error.
  289. lw.Header().Set("Content-Type", "text/plain; charset=utf-8")
  290. lw.Header().Set("X-Content-Type-Options", "nosniff")
  291. for k, vs := range hErr.Header {
  292. lw.Header()[k] = vs
  293. }
  294. lw.WriteHeader(msg.Code)
  295. fmt.Fprintln(lw, hErr.Msg)
  296. }
  297. case err != nil:
  298. // Handler returned a generic error. Serve an internal server
  299. // error, if necessary.
  300. msg.Err = err.Error()
  301. if lw.code == 0 {
  302. msg.Code = http.StatusInternalServerError
  303. http.Error(lw, "internal server error", msg.Code)
  304. }
  305. }
  306. if !h.opts.QuietLoggingIfSuccessful || (msg.Code != http.StatusOK && msg.Code != http.StatusNotModified) {
  307. h.opts.Logf("%s", msg)
  308. }
  309. if h.opts.StatusCodeCounters != nil {
  310. h.opts.StatusCodeCounters.Add(responseCodeString(msg.Code/100), 1)
  311. }
  312. if h.opts.StatusCodeCountersFull != nil {
  313. h.opts.StatusCodeCountersFull.Add(responseCodeString(msg.Code), 1)
  314. }
  315. }
  316. func responseCodeString(code int) string {
  317. if v, ok := responseCodeCache.Load(code); ok {
  318. return v.(string)
  319. }
  320. var ret string
  321. if code < 10 {
  322. ret = fmt.Sprintf("%dxx", code)
  323. } else {
  324. ret = strconv.Itoa(code)
  325. }
  326. responseCodeCache.Store(code, ret)
  327. return ret
  328. }
  329. // responseCodeCache memoizes the string form of HTTP response codes,
  330. // so that the hot request-handling codepath doesn't have to allocate
  331. // in strconv/fmt for every request.
  332. //
  333. // Keys are either full HTTP response code ints (200, 404) or "family"
  334. // ints representing entire families (e.g. 2 for 2xx codes). Values
  335. // are the string form of that code/family.
  336. var responseCodeCache sync.Map
  337. // loggingResponseWriter wraps a ResponseWriter and record the HTTP
  338. // response code that gets sent, if any.
  339. type loggingResponseWriter struct {
  340. http.ResponseWriter
  341. code int
  342. bytes int
  343. hijacked bool
  344. logf logger.Logf
  345. }
  346. // WriteHeader implements http.Handler.
  347. func (l *loggingResponseWriter) WriteHeader(statusCode int) {
  348. if l.code != 0 {
  349. l.logf("[unexpected] HTTP handler set statusCode twice (%d and %d)", l.code, statusCode)
  350. return
  351. }
  352. l.code = statusCode
  353. l.ResponseWriter.WriteHeader(statusCode)
  354. }
  355. // Write implements http.Handler.
  356. func (l *loggingResponseWriter) Write(bs []byte) (int, error) {
  357. if l.code == 0 {
  358. l.code = 200
  359. }
  360. n, err := l.ResponseWriter.Write(bs)
  361. l.bytes += n
  362. return n, err
  363. }
  364. // Hijack implements http.Hijacker. Note that hijacking can still fail
  365. // because the wrapped ResponseWriter is not required to implement
  366. // Hijacker, as this breaks HTTP/2.
  367. func (l *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  368. h, ok := l.ResponseWriter.(http.Hijacker)
  369. if !ok {
  370. return nil, nil, errors.New("ResponseWriter is not a Hijacker")
  371. }
  372. conn, buf, err := h.Hijack()
  373. if err == nil {
  374. l.hijacked = true
  375. }
  376. return conn, buf, err
  377. }
  378. func (l loggingResponseWriter) Flush() {
  379. f, _ := l.ResponseWriter.(http.Flusher)
  380. if f == nil {
  381. l.logf("[unexpected] tried to Flush a ResponseWriter that can't flush")
  382. return
  383. }
  384. f.Flush()
  385. }
  386. // HTTPError is an error with embedded HTTP response information.
  387. //
  388. // It is the error type to be (optionally) used by Handler.ServeHTTPReturn.
  389. type HTTPError struct {
  390. Code int // HTTP response code to send to client; 0 means 500
  391. Msg string // Response body to send to client
  392. Err error // Detailed error to log on the server
  393. Header http.Header // Optional set of HTTP headers to set in the response
  394. }
  395. // Error implements the error interface.
  396. func (e HTTPError) Error() string { return fmt.Sprintf("httperror{%d, %q, %v}", e.Code, e.Msg, e.Err) }
  397. func (e HTTPError) Unwrap() error { return e.Err }
  398. // Error returns an HTTPError containing the given information.
  399. func Error(code int, msg string, err error) HTTPError {
  400. return HTTPError{Code: code, Msg: msg, Err: err}
  401. }
  402. // PrometheusVar is a value that knows how to format itself into
  403. // Prometheus metric syntax.
  404. type PrometheusVar interface {
  405. // WritePrometheus writes the value of the var to w, in Prometheus
  406. // metric syntax. All variables names written out must start with
  407. // prefix (or write out a single variable named exactly prefix)
  408. WritePrometheus(w io.Writer, prefix string)
  409. }
  410. // WritePrometheusExpvar writes kv to w in Prometheus metrics format.
  411. //
  412. // See VarzHandler for conventions. This is exported primarily for
  413. // people to test their varz.
  414. func WritePrometheusExpvar(w io.Writer, kv expvar.KeyValue) {
  415. writePromExpVar(w, "", kv)
  416. }
  417. type prometheusMetricDetails struct {
  418. Name string
  419. Type string
  420. Label string
  421. }
  422. var prometheusMetricCache sync.Map // string => *prometheusMetricDetails
  423. func prometheusMetric(prefix string, key string) (string, string, string) {
  424. cachekey := prefix + key
  425. if v, ok := prometheusMetricCache.Load(cachekey); ok {
  426. d := v.(*prometheusMetricDetails)
  427. return d.Name, d.Type, d.Label
  428. }
  429. var typ string
  430. var label string
  431. switch {
  432. case strings.HasPrefix(key, gaugePrefix):
  433. typ = "gauge"
  434. key = strings.TrimPrefix(key, gaugePrefix)
  435. case strings.HasPrefix(key, counterPrefix):
  436. typ = "counter"
  437. key = strings.TrimPrefix(key, counterPrefix)
  438. }
  439. if strings.HasPrefix(key, labelMapPrefix) {
  440. key = strings.TrimPrefix(key, labelMapPrefix)
  441. if a, b, ok := strings.Cut(key, "_"); ok {
  442. label, key = a, b
  443. }
  444. }
  445. d := &prometheusMetricDetails{
  446. Name: strings.ReplaceAll(prefix+key, "-", "_"),
  447. Type: typ,
  448. Label: label,
  449. }
  450. prometheusMetricCache.Store(cachekey, d)
  451. return d.Name, d.Type, d.Label
  452. }
  453. func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) {
  454. key := kv.Key
  455. name, typ, label := prometheusMetric(prefix, key)
  456. switch v := kv.Value.(type) {
  457. case PrometheusVar:
  458. v.WritePrometheus(w, name)
  459. return
  460. case *expvar.Int:
  461. if typ == "" {
  462. typ = "counter"
  463. }
  464. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", name, typ, name, v.Value())
  465. return
  466. case *expvar.Float:
  467. if typ == "" {
  468. typ = "gauge"
  469. }
  470. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", name, typ, name, v.Value())
  471. return
  472. case *metrics.Set:
  473. v.Do(func(kv expvar.KeyValue) {
  474. writePromExpVar(w, name+"_", kv)
  475. })
  476. return
  477. case PrometheusMetricsReflectRooter:
  478. root := v.PrometheusMetricsReflectRoot()
  479. rv := reflect.ValueOf(root)
  480. if rv.Type().Kind() == reflect.Ptr {
  481. if rv.IsNil() {
  482. return
  483. }
  484. rv = rv.Elem()
  485. }
  486. if rv.Type().Kind() != reflect.Struct {
  487. fmt.Fprintf(w, "# skipping expvar %q; unknown root type\n", name)
  488. return
  489. }
  490. foreachExportedStructField(rv, func(fieldOrJSONName, metricType string, rv reflect.Value) {
  491. mname := name + "_" + fieldOrJSONName
  492. switch rv.Kind() {
  493. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  494. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", mname, metricType, mname, rv.Int())
  495. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  496. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", mname, metricType, mname, rv.Uint())
  497. case reflect.Float32, reflect.Float64:
  498. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", mname, metricType, mname, rv.Float())
  499. case reflect.Struct:
  500. if rv.CanAddr() {
  501. // Slight optimization, not copying big structs if they're addressable:
  502. writePromExpVar(w, name+"_", expvar.KeyValue{Key: fieldOrJSONName, Value: expVarPromStructRoot{rv.Addr().Interface()}})
  503. } else {
  504. writePromExpVar(w, name+"_", expvar.KeyValue{Key: fieldOrJSONName, Value: expVarPromStructRoot{rv.Interface()}})
  505. }
  506. }
  507. return
  508. })
  509. return
  510. }
  511. if typ == "" {
  512. var funcRet string
  513. if f, ok := kv.Value.(expvar.Func); ok {
  514. v := f()
  515. if ms, ok := v.(runtime.MemStats); ok && name == "memstats" {
  516. writeMemstats(w, &ms)
  517. return
  518. }
  519. if vs, ok := v.(string); ok && strings.HasSuffix(name, "version") {
  520. fmt.Fprintf(w, "%s{version=%q} 1\n", name, vs)
  521. return
  522. }
  523. switch v := v.(type) {
  524. case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64:
  525. fmt.Fprintf(w, "%s %v\n", name, v)
  526. return
  527. }
  528. funcRet = fmt.Sprintf(" returning %T", v)
  529. }
  530. switch kv.Value.(type) {
  531. default:
  532. fmt.Fprintf(w, "# skipping expvar %q (Go type %T%s) with undeclared Prometheus type\n", name, kv.Value, funcRet)
  533. return
  534. case *metrics.LabelMap, *expvar.Map:
  535. // Permit typeless LabelMap and expvar.Map for
  536. // compatibility with old expvar-registered
  537. // metrics.LabelMap.
  538. }
  539. }
  540. switch v := kv.Value.(type) {
  541. case expvar.Func:
  542. val := v()
  543. switch val.(type) {
  544. case float64, int64, int:
  545. fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", name, typ, name, val)
  546. default:
  547. fmt.Fprintf(w, "# skipping expvar func %q returning unknown type %T\n", name, val)
  548. }
  549. case *metrics.LabelMap:
  550. if typ != "" {
  551. fmt.Fprintf(w, "# TYPE %s %s\n", name, typ)
  552. }
  553. // IntMap uses expvar.Map on the inside, which presorts
  554. // keys. The output ordering is deterministic.
  555. v.Do(func(kv expvar.KeyValue) {
  556. fmt.Fprintf(w, "%s{%s=%q} %v\n", name, v.Label, kv.Key, kv.Value)
  557. })
  558. case *expvar.Map:
  559. if label != "" && typ != "" {
  560. fmt.Fprintf(w, "# TYPE %s %s\n", name, typ)
  561. v.Do(func(kv expvar.KeyValue) {
  562. fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value)
  563. })
  564. } else {
  565. v.Do(func(kv expvar.KeyValue) {
  566. fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value)
  567. })
  568. }
  569. }
  570. }
  571. var sortedKVsPool = &sync.Pool{New: func() any { return new(sortedKVs) }}
  572. // sortedKV is a KeyValue with a sort key.
  573. type sortedKV struct {
  574. expvar.KeyValue
  575. sortKey string // KeyValue.Key with type prefix removed
  576. }
  577. type sortedKVs struct {
  578. kvs []sortedKV
  579. }
  580. // VarzHandler is an HTTP handler to write expvar values into the
  581. // prometheus export format:
  582. //
  583. // https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md
  584. //
  585. // It makes the following assumptions:
  586. //
  587. // - *expvar.Int are counters (unless marked as a gauge_; see below)
  588. // - a *tailscale/metrics.Set is descended into, joining keys with
  589. // underscores. So use underscores as your metric names.
  590. // - an expvar named starting with "gauge_" or "counter_" is of that
  591. // Prometheus type, and has that prefix stripped.
  592. // - anything else is untyped and thus not exported.
  593. // - expvar.Func can return an int or int64 (for now) and anything else
  594. // is not exported.
  595. //
  596. // This will evolve over time, or perhaps be replaced.
  597. func VarzHandler(w http.ResponseWriter, r *http.Request) {
  598. w.Header().Set("Content-Type", "text/plain; version=0.0.4")
  599. s := sortedKVsPool.Get().(*sortedKVs)
  600. defer sortedKVsPool.Put(s)
  601. s.kvs = s.kvs[:0]
  602. expvarDo(func(kv expvar.KeyValue) {
  603. s.kvs = append(s.kvs, sortedKV{kv, removeTypePrefixes(kv.Key)})
  604. })
  605. sort.Slice(s.kvs, func(i, j int) bool {
  606. return s.kvs[i].sortKey < s.kvs[j].sortKey
  607. })
  608. for _, e := range s.kvs {
  609. writePromExpVar(w, "", e.KeyValue)
  610. }
  611. }
  612. // PrometheusMetricsReflectRooter is an optional interface that expvar.Var implementations
  613. // can implement to indicate that they should be walked recursively with reflect to find
  614. // sets of fields to export.
  615. type PrometheusMetricsReflectRooter interface {
  616. expvar.Var
  617. // PrometheusMetricsReflectRoot returns the struct or struct pointer to walk.
  618. PrometheusMetricsReflectRoot() any
  619. }
  620. var expvarDo = expvar.Do // pulled out for tests
  621. func writeMemstats(w io.Writer, ms *runtime.MemStats) {
  622. out := func(name, typ string, v uint64, help string) {
  623. if help != "" {
  624. fmt.Fprintf(w, "# HELP memstats_%s %s\n", name, help)
  625. }
  626. fmt.Fprintf(w, "# TYPE memstats_%s %s\nmemstats_%s %v\n", name, typ, name, v)
  627. }
  628. g := func(name string, v uint64, help string) { out(name, "gauge", v, help) }
  629. c := func(name string, v uint64, help string) { out(name, "counter", v, help) }
  630. g("heap_alloc", ms.HeapAlloc, "current bytes of allocated heap objects (up/down smoothly)")
  631. c("total_alloc", ms.TotalAlloc, "cumulative bytes allocated for heap objects")
  632. g("sys", ms.Sys, "total bytes of memory obtained from the OS")
  633. c("mallocs", ms.Mallocs, "cumulative count of heap objects allocated")
  634. c("frees", ms.Frees, "cumulative count of heap objects freed")
  635. c("num_gc", uint64(ms.NumGC), "number of completed GC cycles")
  636. }
  637. // sortedStructField is metadata about a struct field used both for sorting once
  638. // (by structTypeSortedFields) and at serving time (by
  639. // foreachExportedStructField).
  640. type sortedStructField struct {
  641. Index int // index of struct field in struct
  642. Name string // struct field name, or "json" name
  643. SortName string // Name with "foo_" type prefixes removed
  644. MetricType string // the "metrictype" struct tag
  645. StructFieldType *reflect.StructField
  646. }
  647. var structSortedFieldsCache sync.Map // reflect.Type => []sortedStructField
  648. // structTypeSortedFields returns the sorted fields of t, caching as needed.
  649. func structTypeSortedFields(t reflect.Type) []sortedStructField {
  650. if v, ok := structSortedFieldsCache.Load(t); ok {
  651. return v.([]sortedStructField)
  652. }
  653. fields := make([]sortedStructField, 0, t.NumField())
  654. for i, n := 0, t.NumField(); i < n; i++ {
  655. sf := t.Field(i)
  656. name := sf.Name
  657. if v := sf.Tag.Get("json"); v != "" {
  658. v, _, _ = strings.Cut(v, ",")
  659. if v == "-" {
  660. // Skip it, regardless of its metrictype.
  661. continue
  662. }
  663. if v != "" {
  664. name = v
  665. }
  666. }
  667. fields = append(fields, sortedStructField{
  668. Index: i,
  669. Name: name,
  670. SortName: removeTypePrefixes(name),
  671. MetricType: sf.Tag.Get("metrictype"),
  672. StructFieldType: &sf,
  673. })
  674. }
  675. sort.Slice(fields, func(i, j int) bool {
  676. return fields[i].SortName < fields[j].SortName
  677. })
  678. structSortedFieldsCache.Store(t, fields)
  679. return fields
  680. }
  681. // removeTypePrefixes returns s with the first "foo_" prefix in prefixesToTrim
  682. // removed.
  683. func removeTypePrefixes(s string) string {
  684. for _, prefix := range prefixesToTrim {
  685. if trimmed, ok := strings.CutPrefix(s, prefix); ok {
  686. return trimmed
  687. }
  688. }
  689. return s
  690. }
  691. // foreachExportedStructField iterates over the fields in sorted order of
  692. // their name, after removing metric prefixes. This is not necessarily the
  693. // order they were declared in the struct
  694. func foreachExportedStructField(rv reflect.Value, f func(fieldOrJSONName, metricType string, rv reflect.Value)) {
  695. t := rv.Type()
  696. for _, ssf := range structTypeSortedFields(t) {
  697. sf := ssf.StructFieldType
  698. if ssf.MetricType != "" || sf.Type.Kind() == reflect.Struct {
  699. f(ssf.Name, ssf.MetricType, rv.Field(ssf.Index))
  700. } else if sf.Type.Kind() == reflect.Ptr && sf.Type.Elem().Kind() == reflect.Struct {
  701. fv := rv.Field(ssf.Index)
  702. if !fv.IsNil() {
  703. f(ssf.Name, ssf.MetricType, fv.Elem())
  704. }
  705. }
  706. }
  707. }
  708. type expVarPromStructRoot struct{ v any }
  709. func (r expVarPromStructRoot) PrometheusMetricsReflectRoot() any { return r.v }
  710. func (r expVarPromStructRoot) String() string { panic("unused") }
  711. var (
  712. _ PrometheusMetricsReflectRooter = expVarPromStructRoot{}
  713. _ expvar.Var = expVarPromStructRoot{}
  714. )