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 }