mirror of
https://github.com/golang/go
synced 2024-11-06 17:26:10 -07:00
feee8acb39
Change-Id: I978ad5e1800ebfceb78aaced438331a8341715d4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/194697 Reviewed-by: Toshihiro Shiino <shiino.toshihiro@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
419 lines
11 KiB
Go
419 lines
11 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package debug
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"go/token"
|
|
"html/template"
|
|
"net"
|
|
"net/http"
|
|
"net/http/pprof"
|
|
_ "net/http/pprof" // pull in the standard pprof handlers
|
|
"path"
|
|
"runtime"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/export"
|
|
"golang.org/x/tools/internal/telemetry/export/prometheus"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
|
)
|
|
|
|
type Cache interface {
|
|
ID() string
|
|
FileSet() *token.FileSet
|
|
}
|
|
|
|
type Session interface {
|
|
ID() string
|
|
Cache() Cache
|
|
Files() []*File
|
|
File(hash string) *File
|
|
}
|
|
|
|
type View interface {
|
|
ID() string
|
|
Name() string
|
|
Folder() span.URI
|
|
Session() Session
|
|
}
|
|
|
|
type File struct {
|
|
Session Session
|
|
URI span.URI
|
|
Data string
|
|
Error error
|
|
Hash string
|
|
}
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
data = struct {
|
|
Caches []Cache
|
|
Sessions []Session
|
|
Views []View
|
|
}{}
|
|
)
|
|
|
|
// AddCache adds a cache to the set being served
|
|
func AddCache(cache Cache) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
data.Caches = append(data.Caches, cache)
|
|
}
|
|
|
|
// DropCache drops a cache from the set being served
|
|
func DropCache(cache Cache) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
//find and remove the cache
|
|
if i, _ := findCache(cache.ID()); i >= 0 {
|
|
copy(data.Caches[i:], data.Caches[i+1:])
|
|
data.Caches[len(data.Caches)-1] = nil
|
|
data.Caches = data.Caches[:len(data.Caches)-1]
|
|
}
|
|
}
|
|
|
|
func findCache(id string) (int, Cache) {
|
|
for i, c := range data.Caches {
|
|
if c.ID() == id {
|
|
return i, c
|
|
}
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
func getCache(r *http.Request) interface{} {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
id := path.Base(r.URL.Path)
|
|
result := struct {
|
|
Cache
|
|
Sessions []Session
|
|
}{}
|
|
_, result.Cache = findCache(id)
|
|
|
|
// now find all the views that belong to this session
|
|
for _, v := range data.Sessions {
|
|
if v.Cache().ID() == id {
|
|
result.Sessions = append(result.Sessions, v)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func findSession(id string) Session {
|
|
for _, c := range data.Sessions {
|
|
if c.ID() == id {
|
|
return c
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getSession(r *http.Request) interface{} {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
id := path.Base(r.URL.Path)
|
|
result := struct {
|
|
Session
|
|
Views []View
|
|
}{
|
|
Session: findSession(id),
|
|
}
|
|
// now find all the views that belong to this session
|
|
for _, v := range data.Views {
|
|
if v.Session().ID() == id {
|
|
result.Views = append(result.Views, v)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func findView(id string) View {
|
|
for _, c := range data.Views {
|
|
if c.ID() == id {
|
|
return c
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getView(r *http.Request) interface{} {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
id := path.Base(r.URL.Path)
|
|
return findView(id)
|
|
}
|
|
|
|
func getFile(r *http.Request) interface{} {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
hash := path.Base(r.URL.Path)
|
|
sid := path.Base(path.Dir(r.URL.Path))
|
|
session := findSession(sid)
|
|
return session.File(hash)
|
|
}
|
|
|
|
func getInfo(r *http.Request) interface{} {
|
|
buf := &bytes.Buffer{}
|
|
PrintVersionInfo(buf, true, HTML)
|
|
return template.HTML(buf.String())
|
|
}
|
|
|
|
func getMemory(r *http.Request) interface{} {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
return m
|
|
}
|
|
|
|
// AddSession adds a session to the set being served
|
|
func AddSession(session Session) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
data.Sessions = append(data.Sessions, session)
|
|
}
|
|
|
|
// DropSession drops a session from the set being served
|
|
func DropSession(session Session) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
//find and remove the session
|
|
}
|
|
|
|
// AddView adds a view to the set being served
|
|
func AddView(view View) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
data.Views = append(data.Views, view)
|
|
}
|
|
|
|
// DropView drops a view from the set being served
|
|
func DropView(view View) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
//find and remove the view
|
|
}
|
|
|
|
// Serve starts and runs a debug server in the background.
|
|
// It also logs the port the server starts on, to allow for :0 auto assigned
|
|
// ports.
|
|
func Serve(ctx context.Context, addr string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Print(ctx, "Debug serving", tag.Of("Port", listener.Addr().(*net.TCPAddr).Port))
|
|
prometheus := prometheus.New()
|
|
rpcs := &rpcs{}
|
|
traces := &traces{}
|
|
export.AddExporters(prometheus, rpcs, traces)
|
|
go func() {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data }))
|
|
mux.HandleFunc("/debug/", Render(debugTmpl, nil))
|
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
mux.HandleFunc("/metrics/", prometheus.Serve)
|
|
mux.HandleFunc("/rpc/", Render(rpcTmpl, rpcs.getData))
|
|
mux.HandleFunc("/trace/", Render(traceTmpl, traces.getData))
|
|
mux.HandleFunc("/cache/", Render(cacheTmpl, getCache))
|
|
mux.HandleFunc("/session/", Render(sessionTmpl, getSession))
|
|
mux.HandleFunc("/view/", Render(viewTmpl, getView))
|
|
mux.HandleFunc("/file/", Render(fileTmpl, getFile))
|
|
mux.HandleFunc("/info", Render(infoTmpl, getInfo))
|
|
mux.HandleFunc("/memory", Render(memoryTmpl, getMemory))
|
|
if err := http.Serve(listener, mux); err != nil {
|
|
log.Error(ctx, "Debug server failed", err)
|
|
return
|
|
}
|
|
log.Print(ctx, "Debug server finished")
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func Render(tmpl *template.Template, fun func(*http.Request) interface{}) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
var data interface{}
|
|
if fun != nil {
|
|
data = fun(r)
|
|
}
|
|
if err := tmpl.Execute(w, data); err != nil {
|
|
log.Error(context.Background(), "", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func commas(s string) string {
|
|
for i := len(s); i > 3; {
|
|
i -= 3
|
|
s = s[:i] + "," + s[i:]
|
|
}
|
|
return s
|
|
}
|
|
|
|
func fuint64(v uint64) string {
|
|
return commas(strconv.FormatUint(v, 10))
|
|
}
|
|
|
|
func fuint32(v uint32) string {
|
|
return commas(strconv.FormatUint(uint64(v), 10))
|
|
}
|
|
|
|
var BaseTemplate = template.Must(template.New("").Parse(`
|
|
<html>
|
|
<head>
|
|
<title>{{template "title" .}}</title>
|
|
<style>
|
|
.profile-name{
|
|
display:inline-block;
|
|
width:6rem;
|
|
}
|
|
td.value {
|
|
text-align: right;
|
|
}
|
|
ul.events {
|
|
list-style-type: none;
|
|
}
|
|
|
|
</style>
|
|
{{block "head" .}}{{end}}
|
|
</head>
|
|
<body>
|
|
<a href="/">Main</a>
|
|
<a href="/info">Info</a>
|
|
<a href="/memory">Memory</a>
|
|
<a href="/metrics">Metrics</a>
|
|
<a href="/rpc">RPC</a>
|
|
<a href="/trace">Trace</a>
|
|
<hr>
|
|
<h1>{{template "title" .}}</h1>
|
|
{{block "body" .}}
|
|
Unknown page
|
|
{{end}}
|
|
</body>
|
|
</html>
|
|
|
|
{{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
|
|
{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
|
|
{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
|
|
{{define "filelink"}}<a href="/file/{{.Session.ID}}/{{.Hash}}">{{.URI}}</a>{{end}}
|
|
`)).Funcs(template.FuncMap{
|
|
"fuint64": fuint64,
|
|
"fuint32": fuint32,
|
|
})
|
|
|
|
var mainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}GoPls server information{{end}}
|
|
{{define "body"}}
|
|
<h2>Caches</h2>
|
|
<ul>{{range .Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
|
|
<h2>Sessions</h2>
|
|
<ul>{{range .Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
|
|
<h2>Views</h2>
|
|
<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
|
|
{{end}}
|
|
`))
|
|
|
|
var infoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}GoPls version information{{end}}
|
|
{{define "body"}}
|
|
{{.}}
|
|
{{end}}
|
|
`))
|
|
|
|
var memoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}GoPls memory usage{{end}}
|
|
{{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
|
|
{{define "body"}}
|
|
<h2>Stats</h2>
|
|
<table>
|
|
<tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
|
|
<tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
|
|
<tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
|
|
<tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
|
|
<tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
|
|
<tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
|
|
<tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
|
|
<tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
|
|
<tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
|
|
<tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
|
|
<tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
|
|
<tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
|
|
<tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
|
|
<tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
|
|
<tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
|
|
</table>
|
|
<h2>By size</h2>
|
|
<table>
|
|
<tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
|
|
{{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
|
|
</table>
|
|
{{end}}
|
|
`))
|
|
|
|
var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}GoPls Debug pages{{end}}
|
|
{{define "body"}}
|
|
<a href="/debug/pprof">Profiling</a>
|
|
{{end}}
|
|
`))
|
|
|
|
var cacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}Cache {{.ID}}{{end}}
|
|
{{define "body"}}
|
|
<h2>Sessions</h2>
|
|
<ul>{{range .Sessions}}<li>{{template "sessionlink" .ID}}</li>{{end}}</ul>
|
|
{{end}}
|
|
`))
|
|
|
|
var sessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}Session {{.ID}}{{end}}
|
|
{{define "body"}}
|
|
From: <b>{{template "cachelink" .Cache.ID}}</b><br>
|
|
<h2>Views</h2>
|
|
<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
|
|
<h2>Files</h2>
|
|
<ul>{{range .Files}}<li>{{template "filelink" .}}</li>{{end}}</ul>
|
|
{{end}}
|
|
`))
|
|
|
|
var viewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}View {{.ID}}{{end}}
|
|
{{define "body"}}
|
|
Name: <b>{{.Name}}</b><br>
|
|
Folder: <b>{{.Folder}}</b><br>
|
|
From: <b>{{template "sessionlink" .Session.ID}}</b><br>
|
|
<h2>Environment</h2>
|
|
<ul>{{range .Env}}<li>{{.}}</li>{{end}}</ul>
|
|
{{end}}
|
|
`))
|
|
|
|
var fileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
|
{{define "title"}}File {{.Hash}}{{end}}
|
|
{{define "body"}}
|
|
From: <b>{{template "sessionlink" .Session.ID}}</b><br>
|
|
URI: <b>{{.URI}}</b><br>
|
|
Hash: <b>{{.Hash}}</b><br>
|
|
Error: <b>{{.Error}}</b><br>
|
|
<h3>Contents</h3>
|
|
<pre>{{.Data}}</pre>
|
|
{{end}}
|
|
`))
|