// 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(` {{template "title" .}} {{block "head" .}}{{end}} Main Info Memory Metrics RPC Trace

{{template "title" .}}

{{block "body" .}} Unknown page {{end}} {{define "cachelink"}}Cache {{.}}{{end}} {{define "sessionlink"}}Session {{.}}{{end}} {{define "viewlink"}}View {{.}}{{end}} {{define "filelink"}}{{.URI}}{{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"}}

Caches

Sessions

Views

{{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"}}{{end}} {{define "body"}}

Stats

Allocated bytes{{fuint64 .HeapAlloc}}
Total allocated bytes{{fuint64 .TotalAlloc}}
System bytes{{fuint64 .Sys}}
Heap system bytes{{fuint64 .HeapSys}}
Malloc calls{{fuint64 .Mallocs}}
Frees{{fuint64 .Frees}}
Idle heap bytes{{fuint64 .HeapIdle}}
In use bytes{{fuint64 .HeapInuse}}
Released to system bytes{{fuint64 .HeapReleased}}
Heap object count{{fuint64 .HeapObjects}}
Stack in use bytes{{fuint64 .StackInuse}}
Stack from system bytes{{fuint64 .StackSys}}
Bucket hash bytes{{fuint64 .BuckHashSys}}
GC metaata bytes{{fuint64 .GCSys}}
Off heap bytes{{fuint64 .OtherSys}}

By size

{{range .BySize}}{{end}}
SizeMallocsFrees
{{fuint32 .Size}}{{fuint64 .Mallocs}}{{fuint64 .Frees}}
{{end}} `)) var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}GoPls Debug pages{{end}} {{define "body"}} Profiling {{end}} `)) var cacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}Cache {{.ID}}{{end}} {{define "body"}}

Sessions

{{end}} `)) var sessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}Session {{.ID}}{{end}} {{define "body"}} From: {{template "cachelink" .Cache.ID}}

Views

Files

{{end}} `)) var viewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}View {{.ID}}{{end}} {{define "body"}} Name: {{.Name}}
Folder: {{.Folder}}
From: {{template "sessionlink" .Session.ID}}

Environment

{{end}} `)) var fileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}File {{.Hash}}{{end}} {{define "body"}} From: {{template "sessionlink" .Session.ID}}
URI: {{.URI}}
Hash: {{.Hash}}
Error: {{.Error}}

Contents

{{.Data}}
{{end}} `))