зеркало из https://github.com/github/vitess-gh.git
servenv: Framework to display information about the running process at /debug/status.
This commit is contained in:
Родитель
fa7051848f
Коммит
2d53c68b1a
|
@ -0,0 +1,173 @@
|
|||
package servenv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
binaryName = filepath.Base(os.Args[0])
|
||||
hostname string
|
||||
serverStart = time.Now()
|
||||
|
||||
statusMu sync.RWMutex
|
||||
statusSections []section
|
||||
statusTmpl = template.Must(reparse(nil))
|
||||
statusFuncMap = make(template.FuncMap)
|
||||
)
|
||||
|
||||
type section struct {
|
||||
Banner string
|
||||
Fragment string
|
||||
F func() interface{}
|
||||
}
|
||||
|
||||
// AddStatusFuncs merges the provided functions into the set of
|
||||
// functions used to render /debug/status. Call this before AddStatusPart
|
||||
// if your template requires custom functions.
|
||||
func AddStatusFuncs(fmap template.FuncMap) {
|
||||
statusMu.Lock()
|
||||
defer statusMu.Unlock()
|
||||
|
||||
for name, fun := range fmap {
|
||||
if _, ok := statusFuncMap[name]; ok {
|
||||
panic("duplicate status func registered: " + name)
|
||||
}
|
||||
statusFuncMap[name] = fun
|
||||
}
|
||||
}
|
||||
|
||||
var statusHTML = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Status for {{.BinaryName}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 120%;
|
||||
background: #eef;
|
||||
}
|
||||
.lefthand {
|
||||
float: left;
|
||||
width: 80%;
|
||||
}
|
||||
.righthand {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<h1>Status for {{.BinaryName}}</h1>
|
||||
|
||||
<div>
|
||||
<div class=lefthand>
|
||||
Started: {{.StartTime}}<br>
|
||||
</div>
|
||||
<div class=righthand>
|
||||
Running on {{.Hostname}}<br>
|
||||
View <a href=/debug/vars>variables</a>,
|
||||
<a href=/debug/pprof>debugging profiles</a>,
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
func reparse(sections []section) (*template.Template, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
io.WriteString(&buf, `{{define "status"}}`)
|
||||
io.WriteString(&buf, statusHTML)
|
||||
|
||||
for i, sec := range sections {
|
||||
fmt.Fprintf(&buf, "<h1>%s</h1>\n", html.EscapeString(sec.Banner))
|
||||
fmt.Fprintf(&buf, "{{$sec := index .Sections %d}}\n", i)
|
||||
fmt.Fprintf(&buf, `{{template "sec-%d" call $sec.F}}`+"\n", i)
|
||||
}
|
||||
fmt.Fprintf(&buf, `</html>`)
|
||||
io.WriteString(&buf, "{{end}}\n")
|
||||
|
||||
for i, sec := range sections {
|
||||
fmt.Fprintf(&buf, `{{define "sec-%d"}}%s{{end}}\n`, i, sec.Fragment)
|
||||
}
|
||||
return template.New("").Funcs(statusFuncMap).Parse(buf.String())
|
||||
}
|
||||
|
||||
// AddStatusPart adds a new section to status. frag is used as a
|
||||
// subtemplate of the template used to render /debug/status, and will
|
||||
// be executed using the value of invoking f at the time of the
|
||||
// /debug/status request. frag is parsed and executed with the
|
||||
// html/template package. Functions registered with AddStatusFuncs
|
||||
// may be used in the template.
|
||||
func AddStatusPart(banner, frag string, f func() interface{}) {
|
||||
statusMu.Lock()
|
||||
defer statusMu.Unlock()
|
||||
|
||||
secs := append(statusSections, section{
|
||||
Banner: banner,
|
||||
Fragment: frag,
|
||||
F: f,
|
||||
})
|
||||
|
||||
var err error
|
||||
statusTmpl, err = reparse(secs)
|
||||
if err != nil {
|
||||
secs[len(secs)-1] = section{
|
||||
Banner: banner,
|
||||
Fragment: "<code>bad statusz template: {{.}}</code>",
|
||||
F: func() interface{} { return err },
|
||||
}
|
||||
}
|
||||
statusTmpl, _ = reparse(secs)
|
||||
statusSections = secs
|
||||
}
|
||||
|
||||
// AddStatusSection registers a function that generates extra
|
||||
// information for /debug/status. If banner is not empty, it will be
|
||||
// used as a header before the information. If more complex output
|
||||
// than a simple string is required use AddStatusPart instead.
|
||||
func AddStatusSection(banner string, f func() string) {
|
||||
AddStatusPart(banner, `{{.}}`, func() interface{} { return f() })
|
||||
}
|
||||
|
||||
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
statusMu.Lock()
|
||||
defer statusMu.Unlock()
|
||||
|
||||
data := struct {
|
||||
Sections []section
|
||||
BinaryName string
|
||||
Hostname string
|
||||
StartTime string
|
||||
}{
|
||||
Sections: statusSections,
|
||||
BinaryName: binaryName,
|
||||
Hostname: hostname,
|
||||
StartTime: serverStart.Format(time.RFC1123),
|
||||
}
|
||||
|
||||
if err := statusTmpl.ExecuteTemplate(w, "status", data); err != nil {
|
||||
log.Errorf("servenv: couldn't execute template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatalf("os.Hostname: %v", err)
|
||||
}
|
||||
http.HandleFunc("/debug/status", statusHandler)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package servenv
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddStatusFuncs(
|
||||
template.FuncMap{
|
||||
"to_upper": strings.ToUpper,
|
||||
})
|
||||
|
||||
AddStatusPart("test_part", `{{ to_upper . }}`, func() interface{} {
|
||||
return "this should be uppercase"
|
||||
})
|
||||
AddStatusSection("test_section", func() string {
|
||||
return "this is a section"
|
||||
})
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
server := httptest.NewServer(nil)
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/debug/status")
|
||||
if err != nil {
|
||||
t.Fatalf("http.Get: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("ioutil.ReadAll: %v", err)
|
||||
}
|
||||
|
||||
cases := []string{
|
||||
`h1.*test_part.*/h1`,
|
||||
`THIS SHOULD BE UPPERCASE`,
|
||||
`h1.*test_section.*/h1`,
|
||||
}
|
||||
for _, cas := range cases {
|
||||
if !regexp.MustCompile(cas).Match(body) {
|
||||
t.Errorf("failed matching: %q", cas)
|
||||
}
|
||||
}
|
||||
t.Logf("body: \n%s", body)
|
||||
}
|
Загрузка…
Ссылка в новой задаче