vitess.io/vitess@v0.16.2/go/vt/servenv/status.go (about)

     1  /*
     2  Copyright 2020 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package servenv
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"html"
    23  	"html/template"
    24  	"io"
    25  	"net"
    26  	"net/http"
    27  	"os"
    28  	"path/filepath"
    29  	"runtime"
    30  	"strconv"
    31  	"strings"
    32  	"sync"
    33  	"time"
    34  
    35  	"vitess.io/vitess/go/acl"
    36  	"vitess.io/vitess/go/vt/log"
    37  )
    38  
    39  // AddStatusPart adds a new section to status. fragment is used as a
    40  // subtemplate of the template used to render /debug/status, and will
    41  // be executed using the value of invoking f at the time of the
    42  // /debug/status request. fragment is parsed and executed with the
    43  // html/template package. Functions registered with AddStatusFuncs
    44  // may be used in the template.
    45  func AddStatusPart(banner, fragment string, f func() any) {
    46  	globalStatus.addStatusPart(banner, fragment, f)
    47  }
    48  
    49  // AddStatusFuncs merges the provided functions into the set of
    50  // functions used to render /debug/status. Call this before AddStatusPart
    51  // if your template requires custom functions.
    52  func AddStatusFuncs(fmap template.FuncMap) {
    53  	globalStatus.addStatusFuncs(fmap)
    54  }
    55  
    56  // AddStatusSection registers a function that generates extra
    57  // information for the global status page, it will be
    58  // used as a header before the information. If more complex output
    59  // than a simple string is required use AddStatusPart instead.
    60  func AddStatusSection(banner string, f func() string) {
    61  	globalStatus.addStatusSection(banner, f)
    62  }
    63  
    64  // StatusURLPath returns the path to the status page.
    65  func StatusURLPath() string {
    66  	return "/debug/status"
    67  }
    68  
    69  //-----------------------------------------------------------------
    70  
    71  var (
    72  	binaryName       = filepath.Base(os.Args[0])
    73  	hostname         string
    74  	serverStart      = time.Now()
    75  	blockProfileRate = 0
    76  
    77  	globalStatus = newStatusPage("")
    78  )
    79  
    80  var statusHTML = `<!DOCTYPE html>
    81  <html>
    82  <head>
    83  <title>Status for {{.BinaryName}}</title>
    84  <style>
    85  body {
    86  font-family: sans-serif;
    87  }
    88  h1 {
    89  clear: both;
    90  width: 100%;
    91  text-align: center;
    92  font-size: 120%;
    93  background: #eef;
    94  }
    95  .lefthand {
    96  float: left;
    97  width: 80%;
    98  }
    99  .righthand {
   100  text-align: right;
   101  }
   102  </style>
   103  </head>
   104  
   105  <h1>Status for {{.BinaryName}}</h1>
   106  
   107  <div>
   108  <div class=lefthand>
   109  Started: {{.StartTime}}<br>
   110  </div>
   111  <div class=righthand>
   112  Running on {{.Hostname}}<br>
   113  View: <a href=/debug/vars>variables</a>,
   114       <a href=/debug/pprof>debugging profiles</a>
   115  </div>
   116  <br>
   117  <div class=righthand>
   118  Toggle (careful!): <a href=/debug/blockprofilerate>block profiling</a>,
   119       <a href=/debug/mutexprofilefraction>mutex profiling</a>
   120  </div>
   121  </div>`
   122  
   123  type statusPage struct {
   124  	mu       sync.RWMutex
   125  	sections []section
   126  	tmpl     *template.Template
   127  	funcMap  template.FuncMap
   128  }
   129  
   130  type section struct {
   131  	Banner   string
   132  	Fragment string
   133  	F        func() any
   134  }
   135  
   136  func newStatusPage(name string) *statusPage {
   137  	sp := &statusPage{
   138  		funcMap: make(template.FuncMap),
   139  	}
   140  	sp.tmpl = template.Must(sp.reparse(nil))
   141  	if name == "" {
   142  		http.HandleFunc(StatusURLPath(), sp.statusHandler)
   143  		// Debug profiles are only supported for the top level status page.
   144  		registerDebugBlockProfileRate()
   145  		registerDebugMutexProfileFraction()
   146  	} else {
   147  		http.HandleFunc("/"+name+StatusURLPath(), sp.statusHandler)
   148  	}
   149  	return sp
   150  }
   151  
   152  func (sp *statusPage) reset() {
   153  	sp.mu.Lock()
   154  	defer sp.mu.Unlock()
   155  
   156  	sp.sections = nil
   157  	sp.tmpl = template.Must(sp.reparse(nil))
   158  	sp.funcMap = make(template.FuncMap)
   159  }
   160  
   161  func (sp *statusPage) addStatusFuncs(fmap template.FuncMap) {
   162  	sp.mu.Lock()
   163  	defer sp.mu.Unlock()
   164  
   165  	for name, fun := range fmap {
   166  		if !strings.HasPrefix(name, "github_com_vitessio_vitess_") {
   167  			panic("status func registered without proper prefix, need github_com_vitessio_vitess_:" + name)
   168  		}
   169  		if _, ok := sp.funcMap[name]; ok {
   170  			panic("duplicate status func registered: " + name)
   171  		}
   172  		sp.funcMap[name] = fun
   173  	}
   174  }
   175  
   176  func (sp *statusPage) addStatusPart(banner, fragment string, f func() any) {
   177  	sp.mu.Lock()
   178  	defer sp.mu.Unlock()
   179  
   180  	secs := append(sp.sections, section{
   181  		Banner:   banner,
   182  		Fragment: fragment,
   183  		F:        f,
   184  	})
   185  
   186  	var err error
   187  	sp.tmpl, err = sp.reparse(secs)
   188  	if err != nil {
   189  		secs[len(secs)-1] = section{
   190  			Banner:   banner,
   191  			Fragment: "<code>bad status template: {{.}}</code>",
   192  			F:        func() any { return err },
   193  		}
   194  	}
   195  	sp.tmpl, _ = sp.reparse(secs)
   196  	sp.sections = secs
   197  }
   198  
   199  func (sp *statusPage) addStatusSection(banner string, f func() string) {
   200  	sp.addStatusPart(banner, `{{.}}`, func() any { return f() })
   201  }
   202  
   203  func (sp *statusPage) statusHandler(w http.ResponseWriter, r *http.Request) {
   204  	if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
   205  		acl.SendError(w, err)
   206  		return
   207  	}
   208  	sp.mu.Lock()
   209  	defer sp.mu.Unlock()
   210  
   211  	data := struct {
   212  		Sections   []section
   213  		BinaryName string
   214  		Hostname   string
   215  		StartTime  string
   216  	}{
   217  		Sections:   sp.sections,
   218  		BinaryName: binaryName,
   219  		Hostname:   hostname,
   220  		StartTime:  serverStart.Format(time.RFC1123),
   221  	}
   222  
   223  	if err := sp.tmpl.ExecuteTemplate(w, "status", data); err != nil {
   224  		if _, ok := err.(net.Error); !ok {
   225  			log.Errorf("servenv: couldn't execute template: %v", err)
   226  		}
   227  	}
   228  }
   229  
   230  func (sp *statusPage) reparse(sections []section) (*template.Template, error) {
   231  	var buf bytes.Buffer
   232  
   233  	io.WriteString(&buf, `{{define "status"}}`)
   234  	io.WriteString(&buf, statusHTML)
   235  
   236  	for i, sec := range sections {
   237  		fmt.Fprintf(&buf, "<h1>%s</h1>\n", html.EscapeString(sec.Banner))
   238  		fmt.Fprintf(&buf, "{{$sec := index .Sections %d}}\n", i)
   239  		fmt.Fprintf(&buf, `{{template "sec-%d" call $sec.F}}`+"\n", i)
   240  	}
   241  	fmt.Fprintf(&buf, `</html>`)
   242  	io.WriteString(&buf, "{{end}}\n")
   243  
   244  	for i, sec := range sections {
   245  		fmt.Fprintf(&buf, `{{define "sec-%d"}}%s{{end}}\n`, i, sec.Fragment)
   246  	}
   247  	return template.New("").Funcs(sp.funcMap).Parse(buf.String())
   248  }
   249  
   250  // Toggle the block profile rate to/from 100%, unless specific rate is passed in
   251  func registerDebugBlockProfileRate() {
   252  	http.HandleFunc("/debug/blockprofilerate", func(w http.ResponseWriter, r *http.Request) {
   253  		if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
   254  			acl.SendError(w, err)
   255  			return
   256  		}
   257  
   258  		rate, err := strconv.ParseInt(r.FormValue("rate"), 10, 32)
   259  		message := "block profiling enabled"
   260  		if rate < 0 || err != nil {
   261  			// We can't get the current profiling rate
   262  			// from runtime, so we depend on a global var
   263  			if blockProfileRate == 0 {
   264  				rate = 1
   265  			} else {
   266  				rate = 0
   267  				message = "block profiling disabled"
   268  			}
   269  		} else {
   270  			message = fmt.Sprintf("Block profiling rate set to %d", rate)
   271  		}
   272  		blockProfileRate = int(rate)
   273  		runtime.SetBlockProfileRate(int(rate))
   274  		log.Infof("Set block profile rate to: %d", rate)
   275  		w.Header().Set("Content-Type", "text/plain")
   276  		w.Write([]byte(message))
   277  	})
   278  }
   279  
   280  // Toggle the mutex profiling fraction to/from 100%, unless specific fraction is passed in
   281  func registerDebugMutexProfileFraction() {
   282  	http.HandleFunc("/debug/mutexprofilefraction", func(w http.ResponseWriter, r *http.Request) {
   283  		if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
   284  			acl.SendError(w, err)
   285  			return
   286  		}
   287  
   288  		currentFraction := runtime.SetMutexProfileFraction(-1)
   289  		fraction, err := strconv.ParseInt(r.FormValue("fraction"), 10, 32)
   290  		message := "mutex profiling enabled"
   291  		if fraction < 0 || err != nil {
   292  			if currentFraction == 0 {
   293  				fraction = 1
   294  			} else {
   295  				fraction = 0
   296  				message = "mutex profiling disabled"
   297  			}
   298  		} else {
   299  			message = fmt.Sprintf("Mutex profiling set to fraction %d", fraction)
   300  		}
   301  		runtime.SetMutexProfileFraction(int(fraction))
   302  		log.Infof("Set mutex profiling fraction to: %d", fraction)
   303  		w.Header().Set("Content-Type", "text/plain")
   304  		w.Write([]byte(message))
   305  	})
   306  }
   307  
   308  func init() {
   309  	var err error
   310  	hostname, err = os.Hostname()
   311  	if err != nil {
   312  		log.Exitf("os.Hostname: %v", err)
   313  	}
   314  }